blather 0.4.7 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/README.md +162 -0
  2. data/examples/{print_heirarchy.rb → print_hierarchy.rb} +5 -5
  3. data/examples/stream_only.rb +27 -0
  4. data/lib/blather.rb +4 -0
  5. data/lib/blather/client/client.rb +91 -73
  6. data/lib/blather/client/dsl.rb +156 -32
  7. data/lib/blather/client/dsl/pubsub.rb +86 -54
  8. data/lib/blather/core_ext/active_support.rb +9 -9
  9. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +2 -2
  10. data/lib/blather/core_ext/nokogiri.rb +12 -7
  11. data/lib/blather/errors.rb +25 -14
  12. data/lib/blather/errors/sasl_error.rb +21 -3
  13. data/lib/blather/errors/stanza_error.rb +37 -21
  14. data/lib/blather/errors/stream_error.rb +27 -17
  15. data/lib/blather/jid.rb +79 -24
  16. data/lib/blather/roster.rb +39 -21
  17. data/lib/blather/roster_item.rb +43 -21
  18. data/lib/blather/stanza.rb +88 -40
  19. data/lib/blather/stanza/disco.rb +12 -2
  20. data/lib/blather/stanza/disco/disco_info.rb +112 -20
  21. data/lib/blather/stanza/disco/disco_items.rb +81 -12
  22. data/lib/blather/stanza/iq.rb +94 -38
  23. data/lib/blather/stanza/iq/query.rb +16 -22
  24. data/lib/blather/stanza/iq/roster.rb +98 -20
  25. data/lib/blather/stanza/message.rb +266 -111
  26. data/lib/blather/stanza/presence.rb +118 -42
  27. data/lib/blather/stanza/presence/status.rb +140 -60
  28. data/lib/blather/stanza/presence/subscription.rb +44 -10
  29. data/lib/blather/stanza/pubsub.rb +70 -15
  30. data/lib/blather/stanza/pubsub/affiliations.rb +36 -7
  31. data/lib/blather/stanza/pubsub/create.rb +26 -4
  32. data/lib/blather/stanza/pubsub/errors.rb +13 -4
  33. data/lib/blather/stanza/pubsub/event.rb +56 -10
  34. data/lib/blather/stanza/pubsub/items.rb +46 -6
  35. data/lib/blather/stanza/pubsub/publish.rb +52 -7
  36. data/lib/blather/stanza/pubsub/retract.rb +45 -6
  37. data/lib/blather/stanza/pubsub/subscribe.rb +30 -4
  38. data/lib/blather/stanza/pubsub/subscription.rb +74 -6
  39. data/lib/blather/stanza/pubsub/subscriptions.rb +35 -9
  40. data/lib/blather/stanza/pubsub/unsubscribe.rb +30 -4
  41. data/lib/blather/stanza/pubsub_owner.rb +17 -7
  42. data/lib/blather/stanza/pubsub_owner/delete.rb +23 -5
  43. data/lib/blather/stanza/pubsub_owner/purge.rb +23 -5
  44. data/lib/blather/stream.rb +96 -29
  45. data/lib/blather/stream/parser.rb +6 -9
  46. data/lib/blather/xmpp_node.rb +101 -153
  47. data/spec/blather/client/client_spec.rb +1 -1
  48. data/spec/blather/errors_spec.rb +5 -5
  49. data/spec/blather/stanza/message_spec.rb +56 -0
  50. data/spec/blather/stanza/presence/status_spec.rb +1 -1
  51. data/spec/blather/stanza_spec.rb +3 -3
  52. data/spec/blather/xmpp_node_spec.rb +19 -74
  53. metadata +6 -10
  54. data/README.rdoc +0 -185
  55. data/examples/drb_client.rb +0 -5
  56. data/examples/ping.rb +0 -11
  57. data/examples/pong.rb +0 -6
  58. data/examples/pubsub/cli.rb +0 -64
  59. data/examples/pubsub/ping_pong.rb +0 -18
@@ -2,27 +2,37 @@ module Blather
2
2
  class Stanza
3
3
  class Iq
4
4
 
5
+ # # Query Stanza
6
+ #
7
+ # This is a base class for any query based Iq stanzas. It provides a base set
8
+ # of methods for working with query stanzas
9
+ #
10
+ # @handler :query
5
11
  class Query < Iq
6
12
  register :query, :query
7
13
 
8
- ##
9
- # Ensure the query node is created
14
+ # Overrides the parent method to ensure a query node is created
15
+ #
16
+ # @see Blather::Stanza::Iq.new
10
17
  def self.new(type = nil)
11
18
  node = super
12
19
  node.query
13
20
  node
14
21
  end
15
22
 
16
- ##
17
- # Kill the query node before running inherit
23
+ # Overrides the parent method to ensure the current query node is destroyed
24
+ #
25
+ # @see Blather::Stanza::Iq#inherit
18
26
  def inherit(node)
19
27
  query.remove
20
28
  super
21
29
  end
22
30
 
23
- ##
24
31
  # Query node accessor
25
- # This will ensure there actually is a query node
32
+ # If a query node exists it will be returned.
33
+ # Otherwise a new node will be created and returned
34
+ #
35
+ # @return [Balather::XMPPNode]
26
36
  def query
27
37
  q = if self.class.registered_ns
28
38
  find_first('query_ns:query', :query_ns => self.class.registered_ns)
@@ -36,22 +46,6 @@ class Iq
36
46
  end
37
47
  q
38
48
  end
39
-
40
- ##
41
- # A query reply should have type set to "result"
42
- def reply
43
- elem = super
44
- elem.type = :result
45
- elem
46
- end
47
-
48
- ##
49
- # A query reply should have type set to "result"
50
- def reply!
51
- super
52
- self.type = :result
53
- self
54
- end
55
49
  end #Query
56
50
 
57
51
  end #Iq
@@ -2,20 +2,28 @@ module Blather
2
2
  class Stanza
3
3
  class Iq
4
4
 
5
+ # # Roster Stanza
6
+ #
7
+ # [RFC 3921 Section 7 - Roster Management](http://xmpp.org/rfcs/rfc3921.html#roster)
8
+ #
9
+ # @handler :roster
5
10
  class Roster < Query
6
11
  register :roster, nil, 'jabber:iq:roster'
7
12
 
8
- ##
9
- # Any new items are added to the query
13
+ # Create a new roster stanza and (optionally) load it with an item
14
+ #
15
+ # @param [<Blather::Stanza::Iq::VALID_TYPES>] type the stanza type
16
+ # @param [Blather::XMPPNode] item a roster item
10
17
  def self.new(type = nil, item = nil)
11
18
  node = super type
12
19
  node.query << item if item
13
20
  node
14
21
  end
15
22
 
16
- ##
17
23
  # Inherit the XMPPNode to create a proper Roster object.
18
24
  # Creates RosterItem objects out of each roster item as well.
25
+ #
26
+ # @param [Blather::XMPPNode] node a node to inherit
19
27
  def inherit(node)
20
28
  # remove the current set of nodes
21
29
  remove_children :item
@@ -25,18 +33,41 @@ class Iq
25
33
  self
26
34
  end
27
35
 
28
- ##
29
- # Roster items
36
+ # The list of roster items
37
+ #
38
+ # @return [Array<Blather::Stanza::Iq::Roster::RosterItem>]
30
39
  def items
31
- query.find('//ns:item', :ns => self.class.registered_ns).map { |i| RosterItem.new(i) }
40
+ query.find('//ns:item', :ns => self.class.registered_ns).map do |i|
41
+ RosterItem.new i
42
+ end
32
43
  end
33
44
 
45
+ # # RosterItem Fragment
46
+ #
47
+ # Individual roster items.
48
+ # This is a convenience class to attach methods to the node
34
49
  class RosterItem < XMPPNode
35
- ##
36
- # [jid] may be either a JID or XMPPNode.
37
- # [name] name alias of the given JID
38
- # [subscription] subscription type
39
- # [ask] ask subscription sub-state
50
+
51
+ # Create a new RosterItem
52
+ # @overload new(XML::Node)
53
+ # Create a RosterItem by inheriting a node
54
+ # @param [XML::Node] node an xml node to inherit
55
+ # @overload new(opts)
56
+ # Create a RosterItem through a hash of options
57
+ # @param [Hash] opts the options
58
+ # @option opts [Blather::JID, String, nil] :jid the JID of the item
59
+ # @option opts [String, nil] :name the alias to give the JID
60
+ # @option opts [Symbol, nil] :subscription the subscription status of
61
+ # the RosterItem must be one of
62
+ # Blather::RosterItem::VALID_SUBSCRIPTION_TYPES
63
+ # @option opts [:subscribe, nil] :ask the ask value of the RosterItem
64
+ # @overload new(jid = nil, name = nil, subscription = nil, ask = nil)
65
+ # @param [Blather::JID, String, nil] jid the JID of the item
66
+ # @param [String, nil] name the alias to give the JID
67
+ # @param [Symbol, nil] subscription the subscription status of the
68
+ # RosterItem must be one of
69
+ # Blather::RosterItem::VALID_SUBSCRIPTION_TYPES
70
+ # @param [:subscribe, nil] ask the ask value of the RosterItem
40
71
  def self.new(jid = nil, name = nil, subscription = nil, ask = nil)
41
72
  new_node = super :item
42
73
 
@@ -57,26 +88,72 @@ class Iq
57
88
  new_node
58
89
  end
59
90
 
60
- ##
61
- # Roster item's JID
91
+ # Get the JID attached to the item
92
+ #
93
+ # @return [Blather::JID, nil]
62
94
  def jid
63
95
  (j = self[:jid]) ? JID.new(j) : nil
64
96
  end
65
- attribute_writer :jid
66
97
 
67
- attribute_accessor :name
98
+ # Set the JID of the item
99
+ #
100
+ # @param [Blather::JID, String, nil] jid the new JID
101
+ def jid=(jid)
102
+ write_attr :jid, jid
103
+ end
104
+
105
+ # Get the item name
106
+ #
107
+ # @return [String, nil]
108
+ def name
109
+ read_attr :name
110
+ end
111
+
112
+ # Set the item name
113
+ #
114
+ # @param [#to_s] name the name of the item
115
+ def name=(name)
116
+ write_attr :name, name
117
+ end
118
+
119
+ # Get the subscription value of the item
120
+ #
121
+ # @return [<:both, :from, :none, :remove, :to>]
122
+ def subscription
123
+ read_attr :subscription, :to_sym
124
+ end
125
+
126
+ # Set the subscription value of the item
127
+ #
128
+ # @param [<:both, :from, :none, :remove, :to>] subscription
129
+ def subscription=(subscription)
130
+ write_attr :subscription, subscription
131
+ end
68
132
 
69
- attribute_accessor :subscription, :ask, :call => :to_sym
133
+ # Get the ask value of the item
134
+ #
135
+ # @return [<:subscribe, nil>]
136
+ def ask
137
+ read_attr :ask, :to_sym
138
+ end
139
+
140
+ # Set the ask value of the item
141
+ #
142
+ # @param [<:subscribe, nil>] ask
143
+ def ask=(ask)
144
+ write_attr :ask, ask
145
+ end
70
146
 
71
- ##
72
147
  # The groups roster item belongs to
148
+ #
149
+ # @return [Array<String>]
73
150
  def groups
74
151
  find('child::*[local-name()="group"]').map { |g| g.content }
75
152
  end
76
153
 
77
- ##
78
154
  # Set the roster item's groups
79
- # must be an array
155
+ #
156
+ # @param [Array<#to_s>] new_groups an array of group names
80
157
  def groups=(new_groups)
81
158
  remove_children :group
82
159
  if new_groups
@@ -87,9 +164,10 @@ class Iq
87
164
  end
88
165
  end
89
166
 
90
- ##
91
167
  # Convert the roster item to a proper stanza all wrapped up
92
168
  # This facilitates new subscriptions
169
+ #
170
+ # @return [Blather::Stanza::Iq::Roster]
93
171
  def to_stanza
94
172
  Roster.new(:set, self)
95
173
  end
@@ -1,138 +1,176 @@
1
1
  module Blather
2
2
  class Stanza
3
3
 
4
- # Exchanging messages is a basic use of XMPP and occurs when a user generates a message stanza
5
- # that is addressed to another entity. The sender's server is responsible for delivering the
6
- # message to the intended recipient (if the recipient is on the same local server) or for routing
7
- # the message to the recipient's server (if the recipient is on a remote server). Thus a message
8
- # stanza is used to "push" information to another entity.
9
- #
10
- # == To Attribute
11
- #
12
- # An instant messaging client specifies an intended recipient for a message by providing the JID
13
- # of an entity other than the sender in the +to+ attribute of the Message stanza. If the message
14
- # is being sent outside the context of any existing chat session or received message, the value
15
- # of the +to+ address SHOULD be of the form "user@domain" rather than of the form "user@domain/resource".
16
- #
17
- # msg = Message.new 'user@domain.tld/resource'
18
- # msg.to == 'user@domain.tld/resource'
19
- #
20
- # msg.to = 'another-user@some-domain.tld/resource'
21
- # msg.to == 'another-user@some-domain.tld/resource'
22
- #
23
- # The +to+ attribute on a Message stanza works like any regular ruby object attribute
24
- #
25
- # == Type Attribute
26
- #
27
- # Common uses of the message stanza in instant messaging applications include: single messages;
28
- # messages sent in the context of a one-to-one chat session; messages sent in the context of a
29
- # multi-user chat room; alerts, notifications, or other information to which no reply is expected;
30
- # and errors. These uses are differentiated via the +type+ attribute. If included, the +type+
31
- # attribute MUST have one of the following values:
32
- #
33
- # * +:chat+ -- The message is sent in the context of a one-to-one chat session. Typically a receiving
34
- # client will present message of type +chat+ in an interface that enables one-to-one chat between
35
- # the two parties, including an appropriate conversation history.
36
- # * +:error+ -- The message is generated by an entity that experiences an error in processing a message
37
- # received from another entity. A client that receives a message of type +error+ SHOULD present an
38
- # appropriate interface informing the sender of the nature of the error.
39
- # * +:groupchat+ -- The message is sent in the context of a multi-user chat environment (similar to that
40
- # of [IRC]). Typically a receiving client will present a message of type +groupchat+ in an interface
41
- # that enables many-to-many chat between the parties, including a roster of parties in the chatroom
42
- # and an appropriate conversation history.
43
- # * +:headline+ -- The message provides an alert, a notification, or other information to which no reply
44
- # is expected (e.g., news headlines, sports updates, near-real-time market data, and syndicated content).
45
- # Because no reply to the message is expected, typically a receiving client will present a message of
46
- # type "headline" in an interface that appropriately differentiates the message from standalone messages,
47
- # chat messages, or groupchat messages (e.g., by not providing the recipient with the ability to reply).
48
- # * +:normal+ -- The message is a standalone message that is sent outside the context of a one-to-one
49
- # conversation or groupchat, and to which it is expected that the recipient will reply. Typically a receiving
50
- # client will present a message of type +normal+ in an interface that enables the recipient to reply, but
51
- # without a conversation history. The default value of the +type+ attribute is +normal+.
4
+ # # Message Stanza
5
+ #
6
+ # [RFC 3921 Section 2.1 - Message Syntax](http://xmpp.org/rfcs/rfc3921.html#rfc.section.2.1)
7
+ #
8
+ # Exchanging messages is a basic use of XMPP and occurs when a user
9
+ # generates a message stanza that is addressed to another entity. The
10
+ # sender's server is responsible for delivering the message to the intended
11
+ # recipient (if the recipient is on the same local server) or for routing
12
+ # the message to the recipient's server (if the recipient is on a remote
13
+ # server). Thus a message stanza is used to "push" information to another
14
+ # entity.
15
+ #
16
+ # ## "To" Attribute
17
+ #
18
+ # An instant messaging client specifies an intended recipient for a message
19
+ # by providing the JID of an entity other than the sender in the `to`
20
+ # attribute of the Message stanza. If the message is being sent outside the
21
+ # context of any existing chat session or received message, the value of the
22
+ # `to` address SHOULD be of the form "user@domain" rather than of the form
23
+ # "user@domain/resource".
24
+ #
25
+ # msg = Message.new 'user@domain.tld/resource'
26
+ # msg.to == 'user@domain.tld/resource'
27
+ #
28
+ # msg.to = 'another-user@some-domain.tld/resource'
29
+ # msg.to == 'another-user@some-domain.tld/resource'
30
+ #
31
+ # The `to` attribute on a Message stanza works like any regular ruby object
32
+ # attribute
33
+ #
34
+ # ## "Type" Attribute
35
+ #
36
+ # Common uses of the message stanza in instant messaging applications
37
+ # include: single messages; messages sent in the context of a one-to-one
38
+ # chat session; messages sent in the context of a multi-user chat room;
39
+ # alerts, notifications, or other information to which no reply is expected;
40
+ # and errors. These uses are differentiated via the `type` attribute. If
41
+ # included, the `type` attribute MUST have one of the following values:
42
+ #
43
+ # * `:chat` -- The message is sent in the context of a one-to-one chat
44
+ # session. Typically a receiving client will present message of type
45
+ # `chat` in an interface that enables one-to-one chat between the two
46
+ # parties, including an appropriate conversation history.
47
+ #
48
+ # * `:error` -- The message is generated by an entity that experiences an
49
+ # error in processing a message received from another entity. A client
50
+ # that receives a message of type `error` SHOULD present an appropriate
51
+ # interface informing the sender of the nature of the error.
52
+ #
53
+ # * `:groupchat` -- The message is sent in the context of a multi-user chat
54
+ # environment (similar to that of [IRC]). Typically a receiving client
55
+ # will present a message of type `groupchat` in an interface that enables
56
+ # many-to-many chat between the parties, including a roster of parties in
57
+ # the chatroom and an appropriate conversation history.
58
+ #
59
+ # * `:headline` -- The message provides an alert, a notification, or other
60
+ # information to which no reply is expected (e.g., news headlines, sports
61
+ # updates, near-real-time market data, and syndicated content). Because no
62
+ # reply to the message is expected, typically a receiving client will
63
+ # present a message of type "headline" in an interface that appropriately
64
+ # differentiates the message from standalone messages, chat messages, or
65
+ # groupchat messages (e.g., by not providing the recipient with the
66
+ # ability to reply).
67
+ #
68
+ # * `:normal` -- The message is a standalone message that is sent outside
69
+ # the context of a one-to-one conversation or groupchat, and to which it
70
+ # is expected that the recipient will reply. Typically a receiving client
71
+ # will present a message of type `normal` in an interface that enables the
72
+ # recipient to reply, but without a conversation history. The default
73
+ # value of the `type` attribute is `normal`.
52
74
  #
53
75
  # Blather provides a helper for each possible type:
54
76
  #
55
- # Message#chat?
56
- # Message#error?
57
- # Message#groupchat?
58
- # Message#headline?
59
- # Message#normal?
77
+ # Message#chat?
78
+ # Message#error?
79
+ # Message#groupchat?
80
+ # Message#headline?
81
+ # Message#normal?
82
+ #
83
+ # Blather treats the `type` attribute like a normal ruby object attribute
84
+ # providing a getter and setter. The default `type` is `chat`.
60
85
  #
61
- # Blather treats the +type+ attribute like a normal ruby object attribute providing a getter and setter.
62
- # The default +type+ is +chat+.
86
+ # msg = Message.new
87
+ # msg.type # => :chat
88
+ # msg.chat? # => true
89
+ # msg.type = :normal
90
+ # msg.normal? # => true
91
+ # msg.chat? # => false
63
92
  #
64
- # msg = Message.new
65
- # msg.type # => :chat
66
- # msg.chat? # => true
67
- # msg.type = :normal
68
- # msg.normal? # => true
69
- # msg.chat? # => false
93
+ # msg.type = :invalid # => RuntimeError
70
94
  #
71
- # msg.type = :invalid # => RuntimeError
72
95
  #
73
- # == Body Element
96
+ # ## "Body" Element
74
97
  #
75
- # The +body+ element contains human-readable XML character data that specifies the textual contents of the message;
76
- # this child element is normally included but is optional.
98
+ # The `body` element contains human-readable XML character data that
99
+ # specifies the textual contents of the message; this child element is
100
+ # normally included but is optional.
77
101
  #
78
- # Blather provides an attribute-like syntax for Message +body+ elements.
102
+ # Blather provides an attribute-like syntax for Message `body` elements.
79
103
  #
80
- # msg = Message.new 'user@domain.tld', 'message body'
81
- # msg.body # => 'message body'
104
+ # msg = Message.new 'user@domain.tld', 'message body'
105
+ # msg.body # => 'message body'
82
106
  #
83
- # msg.body = 'other message'
84
- # msg.body # => 'other message'
107
+ # msg.body = 'other message'
108
+ # msg.body # => 'other message'
85
109
  #
86
- # == Subject Element
110
+ # ## "Subject" Element
87
111
  #
88
- # The +subject+ element contains human-readable XML character data that specifies the topic of the message.
112
+ # The `subject` element contains human-readable XML character data that
113
+ # specifies the topic of the message.
89
114
  #
90
- # Blather provides an attribute-like syntax for Message +subject+ elements.
115
+ # Blather provides an attribute-like syntax for Message `subject` elements.
91
116
  #
92
- # msg = Message.new 'user@domain.tld', 'message subject'
93
- # msg.subject # => 'message subject'
117
+ # msg = Message.new 'user@domain.tld', 'message body'
118
+ # msg.subject = 'message subject'
119
+ # msg.subject # => 'message subject'
94
120
  #
95
- # msg.subject = 'other subject'
96
- # msg.subject # => 'other subject'
121
+ # ## "Thread" Element
97
122
  #
98
- # == Thread Element
123
+ # The primary use of the XMPP `thread` element is to uniquely identify a
124
+ # conversation thread or "chat session" between two entities instantiated by
125
+ # Message stanzas of type `chat`. However, the XMPP thread element can also
126
+ # be used to uniquely identify an analogous thread between two entities
127
+ # instantiated by Message stanzas of type `headline` or `normal`, or among
128
+ # multiple entities in the context of a multi-user chat room instantiated by
129
+ # Message stanzas of type `groupchat`. It MAY also be used for Message
130
+ # stanzas not related to a human conversation, such as a game session or an
131
+ # interaction between plugins. The `thread` element is not used to identify
132
+ # individual messages, only conversations or messagingg sessions. The
133
+ # inclusion of the `thread` element is optional.
99
134
  #
100
- # The primary use of the XMPP +thread+ element is to uniquely identify a conversation thread or "chat session"
101
- # between two entities instantiated by Message stanzas of type +chat+. However, the XMPP thread element can
102
- # also be used to uniquely identify an analogous thread between two entities instantiated by Message stanzas
103
- # of type +headline+ or +normal+, or among multiple entities in the context of a multi-user chat room instantiated
104
- # by Message stanzas of type +groupchat+. It MAY also be used for Message stanzas not related to a human
105
- # conversation, such as a game session or an interaction between plugins. The +thread+ element is not used to
106
- # identify individual messages, only conversations or messagingg sessions. The inclusion of the +thread+ element
107
- # is optional.
135
+ # The value of the `thread` element is not human-readable and MUST be
136
+ # treated as opaque by entities; no semantic meaning can be derived from it,
137
+ # and only exact comparisons can be made against it. The value of the
138
+ # `thread` element MUST be a universally unique identifier (UUID) as
139
+ # described in [UUID].
108
140
  #
109
- # The value of the +thread+ element is not human-readable and MUST be treated as opaque by entities; no semantic
110
- # meaning can be derived from it, and only exact comparisons can be made against it. The value of the +thread+
111
- # element MUST be a universally unique identifier (UUID) as described in [UUID].
141
+ # The `thread` element MAY possess a 'parent' attribute that identifies
142
+ # another thread of which the current thread is an offshoot or child; the
143
+ # value of the 'parent' must conform to the syntax of the `thread` element
144
+ # itself.
112
145
  #
113
- # The +thread+ element MAY possess a 'parent' attribute that identifies another thread of which the current
114
- # thread is an offshoot or child; the value of the 'parent' must conform to the syntax of the +thread+ element itself.
146
+ # Blather provides an attribute-like syntax for Message `thread` elements.
115
147
  #
116
- # Blather provides an attribute-like syntax for Message +thread+ elements.
117
- #
118
- # msg = Message.new
119
- # msg.thread = '12345'
120
- # msg.thread # => '12345'
148
+ # msg = Message.new
149
+ # msg.thread = '12345'
150
+ # msg.thread # => '12345'
121
151
  #
122
152
  # Parent threads can be set using a hash:
123
153
  #
124
- # msg.thread = {'parent-id' => 'thread-id'}
125
- # msg.thread # => 'thread-id'
126
- # msg.parent_thread # => 'parent-id'
154
+ # msg.thread = {'parent-id' => 'thread-id'}
155
+ # msg.thread # => 'thread-id'
156
+ # msg.parent_thread # => 'parent-id'
127
157
  #
158
+ # @handler :message
128
159
  class Message < Stanza
129
- VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal] # :nodoc:
160
+ VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal].freeze
161
+
162
+ HTML_NS = 'http://jabber.org/protocol/xhtml-im'.freeze
163
+ HTML_BODY_NS = 'http://www.w3.org/1999/xhtml'.freeze
130
164
 
131
165
  register :message
132
166
 
133
- def self.import(node) # :nodoc:
167
+ # @private
168
+ def self.import(node)
134
169
  klass = nil
135
- node.children.each { |e| break if klass = class_from_registration(e.element_name, (e.namespace.href if e.namespace)) }
170
+ node.children.detect do |e|
171
+ ns = e.namespace ? e.namespace.href : nil
172
+ klass = class_from_registration(e.element_name, ns)
173
+ end
136
174
 
137
175
  if klass && klass != self
138
176
  klass.import(node)
@@ -141,6 +179,11 @@ class Stanza
141
179
  end
142
180
  end
143
181
 
182
+ # Create a new Message stanza
183
+ #
184
+ # @param [#to_s] to the JID to send the message to
185
+ # @param [#to_s] body the body of the message
186
+ # @param [Symbol] type the message type. Must be one of VALID_TYPES
144
187
  def self.new(to = nil, body = nil, type = :chat)
145
188
  node = super :message
146
189
  node.to = to
@@ -149,29 +192,141 @@ class Stanza
149
192
  node
150
193
  end
151
194
 
152
- attribute_helpers_for :type, VALID_TYPES
195
+ # Check if the Message is of type :chat
196
+ #
197
+ # @return [true, false]
198
+ def chat?
199
+ self.type == :chat
200
+ end
153
201
 
154
- def type=(type) # :nodoc:
155
- raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
202
+ # Check if the Message is of type :error
203
+ #
204
+ # @return [true, false]
205
+ def error?
206
+ self.type == :error
207
+ end
208
+
209
+ # Check if the Message is of type :groupchat
210
+ #
211
+ # @return [true, false]
212
+ def groupchat?
213
+ self.type == :groupchat
214
+ end
215
+
216
+ # Check if the Message is of type :headline
217
+ #
218
+ # @return [true, false]
219
+ def headline?
220
+ self.type == :headline
221
+ end
222
+
223
+ # Check if the Message is of type :normal
224
+ #
225
+ # @return [true, false]
226
+ def normal?
227
+ self.type == :normal
228
+ end
229
+
230
+ # Ensures type is :get, :set, :result or :error
231
+ #
232
+ # @param [#to_sym] type the Message type. Must be one of VALID_TYPES
233
+ def type=(type)
234
+ if type && !VALID_TYPES.include?(type.to_sym)
235
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
236
+ end
156
237
  super
157
238
  end
158
239
 
159
- content_attr_accessor :body
160
- content_attr_accessor :subject
240
+ # Get the message body
241
+ #
242
+ # @return [String]
243
+ def body
244
+ read_content :body
245
+ end
246
+
247
+ # Set the message body
248
+ #
249
+ # @param [#to_s] body the message body
250
+ def body=(body)
251
+ set_content_for :body, body
252
+ end
253
+
254
+ # Get the message xhtml node
255
+ # This will create the node if it doesn't exist
256
+ #
257
+ # @return [XML::Node]
258
+ def xhtml_node
259
+ unless h = find_first('ns:html', :ns => HTML_NS)
260
+ self << (h = XMPPNode.new('html', self.document))
261
+ h.namespace = HTML_NS
262
+ end
263
+
264
+ unless b = h.find_first('ns:body', :ns => HTML_BODY_NS)
265
+ h << (b = XMPPNode.new('body', self.document))
266
+ b.namespace = HTML_BODY_NS
267
+ end
268
+
269
+ b
270
+ end
271
+
272
+ # Get the message xhtml
273
+ #
274
+ # @return [String]
275
+ def xhtml
276
+ self.xhtml_node.content.strip
277
+ end
278
+
279
+ # Set the message xhtml
280
+ # This will use Nokogiri to ensure the xhtml is valid
281
+ #
282
+ # @param [#to_s] valid xhtml
283
+ def xhtml=(xhtml_body)
284
+ self.xhtml_node.content = Nokogiri::XML(xhtml_body).to_xhtml
285
+ end
161
286
 
162
- content_attr_reader :thread
287
+ # Get the message subject
288
+ #
289
+ # @return [String]
290
+ def subject
291
+ read_content :subject
292
+ end
293
+
294
+ # Set the message subject
295
+ #
296
+ # @param [#to_s] body the message subject
297
+ def subject=(subject)
298
+ set_content_for :subject, subject
299
+ end
300
+
301
+ # Get the message thread
302
+ #
303
+ # @return [String]
304
+ def thread
305
+ read_content :thread
306
+ end
163
307
 
164
- def parent_thread # :nodoc:
308
+ # Get the parent thread
309
+ #
310
+ # @return [String, nil]
311
+ def parent_thread
165
312
  n = find_first('thread')
166
313
  n[:parent] if n
167
314
  end
168
315
 
169
- def thread=(thread) # :nodoc:
316
+ # Set the thread
317
+ #
318
+ # @overload thread=(hash)
319
+ # Set a thread with a parent
320
+ # @param [Hash<parent-id => thread-id>] thread
321
+ # @overload thread=(thread)
322
+ # Set a thread id
323
+ # @param [#to_s] thread the new thread id
324
+ def thread=(thread)
170
325
  parent, thread = thread.to_a.flatten if thread.is_a?(Hash)
171
326
  set_content_for :thread, thread
172
327
  find_first('thread')[:parent] = parent
173
328
  end
174
329
  end
175
330
 
176
- end #Stanza
331
+ end
177
332
  end