lib/blather/stanza/message.rb in blather-0.4.7 vs lib/blather/stanza/message.rb in blather-0.4.8

- old
+ new

@@ -1,177 +1,332 @@ module Blather class Stanza - # Exchanging messages is a basic use of XMPP and occurs when a user generates a message stanza - # that is addressed to another entity. The sender's server is responsible for delivering the - # message to the intended recipient (if the recipient is on the same local server) or for routing - # the message to the recipient's server (if the recipient is on a remote server). Thus a message - # stanza is used to "push" information to another entity. + # # Message Stanza # - # == To Attribute + # [RFC 3921 Section 2.1 - Message Syntax](http://xmpp.org/rfcs/rfc3921.html#rfc.section.2.1) # - # An instant messaging client specifies an intended recipient for a message by providing the JID - # of an entity other than the sender in the +to+ attribute of the Message stanza. If the message - # is being sent outside the context of any existing chat session or received message, the value - # of the +to+ address SHOULD be of the form "user@domain" rather than of the form "user@domain/resource". + # Exchanging messages is a basic use of XMPP and occurs when a user + # generates a message stanza that is addressed to another entity. The + # sender's server is responsible for delivering the message to the intended + # recipient (if the recipient is on the same local server) or for routing + # the message to the recipient's server (if the recipient is on a remote + # server). Thus a message stanza is used to "push" information to another + # entity. # - # msg = Message.new 'user@domain.tld/resource' - # msg.to == 'user@domain.tld/resource' + # ## "To" Attribute # - # msg.to = 'another-user@some-domain.tld/resource' - # msg.to == 'another-user@some-domain.tld/resource' + # An instant messaging client specifies an intended recipient for a message + # by providing the JID of an entity other than the sender in the `to` + # attribute of the Message stanza. If the message is being sent outside the + # context of any existing chat session or received message, the value of the + # `to` address SHOULD be of the form "user@domain" rather than of the form + # "user@domain/resource". # - # The +to+ attribute on a Message stanza works like any regular ruby object attribute + # msg = Message.new 'user@domain.tld/resource' + # msg.to == 'user@domain.tld/resource' # - # == Type Attribute + # msg.to = 'another-user@some-domain.tld/resource' + # msg.to == 'another-user@some-domain.tld/resource' # - # Common uses of the message stanza in instant messaging applications include: single messages; - # messages sent in the context of a one-to-one chat session; messages sent in the context of a - # multi-user chat room; alerts, notifications, or other information to which no reply is expected; - # and errors. These uses are differentiated via the +type+ attribute. If included, the +type+ - # attribute MUST have one of the following values: + # The `to` attribute on a Message stanza works like any regular ruby object + # attribute # - # * +:chat+ -- The message is sent in the context of a one-to-one chat session. Typically a receiving - # client will present message of type +chat+ in an interface that enables one-to-one chat between - # the two parties, including an appropriate conversation history. - # * +:error+ -- The message is generated by an entity that experiences an error in processing a message - # received from another entity. A client that receives a message of type +error+ SHOULD present an - # appropriate interface informing the sender of the nature of the error. - # * +:groupchat+ -- The message is sent in the context of a multi-user chat environment (similar to that - # of [IRC]). Typically a receiving client will present a message of type +groupchat+ in an interface - # that enables many-to-many chat between the parties, including a roster of parties in the chatroom - # and an appropriate conversation history. - # * +:headline+ -- The message provides an alert, a notification, or other information to which no reply - # is expected (e.g., news headlines, sports updates, near-real-time market data, and syndicated content). - # Because no reply to the message is expected, typically a receiving client will present a message of - # type "headline" in an interface that appropriately differentiates the message from standalone messages, - # chat messages, or groupchat messages (e.g., by not providing the recipient with the ability to reply). - # * +:normal+ -- The message is a standalone message that is sent outside the context of a one-to-one - # conversation or groupchat, and to which it is expected that the recipient will reply. Typically a receiving - # client will present a message of type +normal+ in an interface that enables the recipient to reply, but - # without a conversation history. The default value of the +type+ attribute is +normal+. + # ## "Type" Attribute # + # Common uses of the message stanza in instant messaging applications + # include: single messages; messages sent in the context of a one-to-one + # chat session; messages sent in the context of a multi-user chat room; + # alerts, notifications, or other information to which no reply is expected; + # and errors. These uses are differentiated via the `type` attribute. If + # included, the `type` attribute MUST have one of the following values: + # + # * `:chat` -- The message is sent in the context of a one-to-one chat + # session. Typically a receiving client will present message of type + # `chat` in an interface that enables one-to-one chat between the two + # parties, including an appropriate conversation history. + # + # * `:error` -- The message is generated by an entity that experiences an + # error in processing a message received from another entity. A client + # that receives a message of type `error` SHOULD present an appropriate + # interface informing the sender of the nature of the error. + # + # * `:groupchat` -- The message is sent in the context of a multi-user chat + # environment (similar to that of [IRC]). Typically a receiving client + # will present a message of type `groupchat` in an interface that enables + # many-to-many chat between the parties, including a roster of parties in + # the chatroom and an appropriate conversation history. + # + # * `:headline` -- The message provides an alert, a notification, or other + # information to which no reply is expected (e.g., news headlines, sports + # updates, near-real-time market data, and syndicated content). Because no + # reply to the message is expected, typically a receiving client will + # present a message of type "headline" in an interface that appropriately + # differentiates the message from standalone messages, chat messages, or + # groupchat messages (e.g., by not providing the recipient with the + # ability to reply). + # + # * `:normal` -- The message is a standalone message that is sent outside + # the context of a one-to-one conversation or groupchat, and to which it + # is expected that the recipient will reply. Typically a receiving client + # will present a message of type `normal` in an interface that enables the + # recipient to reply, but without a conversation history. The default + # value of the `type` attribute is `normal`. + # # Blather provides a helper for each possible type: # - # Message#chat? - # Message#error? - # Message#groupchat? - # Message#headline? - # Message#normal? + # Message#chat? + # Message#error? + # Message#groupchat? + # Message#headline? + # Message#normal? # - # Blather treats the +type+ attribute like a normal ruby object attribute providing a getter and setter. - # The default +type+ is +chat+. + # Blather treats the `type` attribute like a normal ruby object attribute + # providing a getter and setter. The default `type` is `chat`. # - # msg = Message.new - # msg.type # => :chat - # msg.chat? # => true - # msg.type = :normal - # msg.normal? # => true - # msg.chat? # => false + # msg = Message.new + # msg.type # => :chat + # msg.chat? # => true + # msg.type = :normal + # msg.normal? # => true + # msg.chat? # => false # - # msg.type = :invalid # => RuntimeError + # msg.type = :invalid # => RuntimeError # - # == Body Element # - # The +body+ element contains human-readable XML character data that specifies the textual contents of the message; - # this child element is normally included but is optional. + # ## "Body" Element # - # Blather provides an attribute-like syntax for Message +body+ elements. + # The `body` element contains human-readable XML character data that + # specifies the textual contents of the message; this child element is + # normally included but is optional. # - # msg = Message.new 'user@domain.tld', 'message body' - # msg.body # => 'message body' + # Blather provides an attribute-like syntax for Message `body` elements. # - # msg.body = 'other message' - # msg.body # => 'other message' + # msg = Message.new 'user@domain.tld', 'message body' + # msg.body # => 'message body' # - # == Subject Element + # msg.body = 'other message' + # msg.body # => 'other message' # - # The +subject+ element contains human-readable XML character data that specifies the topic of the message. + # ## "Subject" Element # - # Blather provides an attribute-like syntax for Message +subject+ elements. + # The `subject` element contains human-readable XML character data that + # specifies the topic of the message. # - # msg = Message.new 'user@domain.tld', 'message subject' - # msg.subject # => 'message subject' + # Blather provides an attribute-like syntax for Message `subject` elements. # - # msg.subject = 'other subject' - # msg.subject # => 'other subject' + # msg = Message.new 'user@domain.tld', 'message body' + # msg.subject = 'message subject' + # msg.subject # => 'message subject' # - # == Thread Element + # ## "Thread" Element # - # The primary use of the XMPP +thread+ element is to uniquely identify a conversation thread or "chat session" - # between two entities instantiated by Message stanzas of type +chat+. However, the XMPP thread element can - # also be used to uniquely identify an analogous thread between two entities instantiated by Message stanzas - # of type +headline+ or +normal+, or among multiple entities in the context of a multi-user chat room instantiated - # by Message stanzas of type +groupchat+. It MAY also be used for Message stanzas not related to a human - # conversation, such as a game session or an interaction between plugins. The +thread+ element is not used to - # identify individual messages, only conversations or messagingg sessions. The inclusion of the +thread+ element - # is optional. + # The primary use of the XMPP `thread` element is to uniquely identify a + # conversation thread or "chat session" between two entities instantiated by + # Message stanzas of type `chat`. However, the XMPP thread element can also + # be used to uniquely identify an analogous thread between two entities + # instantiated by Message stanzas of type `headline` or `normal`, or among + # multiple entities in the context of a multi-user chat room instantiated by + # Message stanzas of type `groupchat`. It MAY also be used for Message + # stanzas not related to a human conversation, such as a game session or an + # interaction between plugins. The `thread` element is not used to identify + # individual messages, only conversations or messagingg sessions. The + # inclusion of the `thread` element is optional. # - # The value of the +thread+ element is not human-readable and MUST be treated as opaque by entities; no semantic - # meaning can be derived from it, and only exact comparisons can be made against it. The value of the +thread+ - # element MUST be a universally unique identifier (UUID) as described in [UUID]. + # The value of the `thread` element is not human-readable and MUST be + # treated as opaque by entities; no semantic meaning can be derived from it, + # and only exact comparisons can be made against it. The value of the + # `thread` element MUST be a universally unique identifier (UUID) as + # described in [UUID]. # - # The +thread+ element MAY possess a 'parent' attribute that identifies another thread of which the current - # thread is an offshoot or child; the value of the 'parent' must conform to the syntax of the +thread+ element itself. + # The `thread` element MAY possess a 'parent' attribute that identifies + # another thread of which the current thread is an offshoot or child; the + # value of the 'parent' must conform to the syntax of the `thread` element + # itself. # - # Blather provides an attribute-like syntax for Message +thread+ elements. - # - # msg = Message.new - # msg.thread = '12345' - # msg.thread # => '12345' + # Blather provides an attribute-like syntax for Message `thread` elements. # + # msg = Message.new + # msg.thread = '12345' + # msg.thread # => '12345' + # # Parent threads can be set using a hash: # - # msg.thread = {'parent-id' => 'thread-id'} - # msg.thread # => 'thread-id' - # msg.parent_thread # => 'parent-id' + # msg.thread = {'parent-id' => 'thread-id'} + # msg.thread # => 'thread-id' + # msg.parent_thread # => 'parent-id' # + # @handler :message class Message < Stanza - VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal] # :nodoc: + VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal].freeze + HTML_NS = 'http://jabber.org/protocol/xhtml-im'.freeze + HTML_BODY_NS = 'http://www.w3.org/1999/xhtml'.freeze + register :message - def self.import(node) # :nodoc: + # @private + def self.import(node) klass = nil - node.children.each { |e| break if klass = class_from_registration(e.element_name, (e.namespace.href if e.namespace)) } + node.children.detect do |e| + ns = e.namespace ? e.namespace.href : nil + klass = class_from_registration(e.element_name, ns) + end if klass && klass != self klass.import(node) else new(node[:type]).inherit(node) end end + # Create a new Message stanza + # + # @param [#to_s] to the JID to send the message to + # @param [#to_s] body the body of the message + # @param [Symbol] type the message type. Must be one of VALID_TYPES def self.new(to = nil, body = nil, type = :chat) node = super :message node.to = to node.type = type node.body = body node end - attribute_helpers_for :type, VALID_TYPES + # Check if the Message is of type :chat + # + # @return [true, false] + def chat? + self.type == :chat + end - def type=(type) # :nodoc: - raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym) + # Check if the Message is of type :error + # + # @return [true, false] + def error? + self.type == :error + end + + # Check if the Message is of type :groupchat + # + # @return [true, false] + def groupchat? + self.type == :groupchat + end + + # Check if the Message is of type :headline + # + # @return [true, false] + def headline? + self.type == :headline + end + + # Check if the Message is of type :normal + # + # @return [true, false] + def normal? + self.type == :normal + end + + # Ensures type is :get, :set, :result or :error + # + # @param [#to_sym] type the Message type. Must be one of VALID_TYPES + def type=(type) + if type && !VALID_TYPES.include?(type.to_sym) + raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" + end super end - content_attr_accessor :body - content_attr_accessor :subject + # Get the message body + # + # @return [String] + def body + read_content :body + end - content_attr_reader :thread + # Set the message body + # + # @param [#to_s] body the message body + def body=(body) + set_content_for :body, body + end - def parent_thread # :nodoc: + # Get the message xhtml node + # This will create the node if it doesn't exist + # + # @return [XML::Node] + def xhtml_node + unless h = find_first('ns:html', :ns => HTML_NS) + self << (h = XMPPNode.new('html', self.document)) + h.namespace = HTML_NS + end + + unless b = h.find_first('ns:body', :ns => HTML_BODY_NS) + h << (b = XMPPNode.new('body', self.document)) + b.namespace = HTML_BODY_NS + end + + b + end + + # Get the message xhtml + # + # @return [String] + def xhtml + self.xhtml_node.content.strip + end + + # Set the message xhtml + # This will use Nokogiri to ensure the xhtml is valid + # + # @param [#to_s] valid xhtml + def xhtml=(xhtml_body) + self.xhtml_node.content = Nokogiri::XML(xhtml_body).to_xhtml + end + + # Get the message subject + # + # @return [String] + def subject + read_content :subject + end + + # Set the message subject + # + # @param [#to_s] body the message subject + def subject=(subject) + set_content_for :subject, subject + end + + # Get the message thread + # + # @return [String] + def thread + read_content :thread + end + + # Get the parent thread + # + # @return [String, nil] + def parent_thread n = find_first('thread') n[:parent] if n end - def thread=(thread) # :nodoc: + # Set the thread + # + # @overload thread=(hash) + # Set a thread with a parent + # @param [Hash<parent-id => thread-id>] thread + # @overload thread=(thread) + # Set a thread id + # @param [#to_s] thread the new thread id + def thread=(thread) parent, thread = thread.to_a.flatten if thread.is_a?(Hash) set_content_for :thread, thread find_first('thread')[:parent] = parent end end -end #Stanza +end end \ No newline at end of file