tp-blather 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/.autotest +13 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +249 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +413 -0
  11. data/Rakefile +20 -0
  12. data/TODO.md +2 -0
  13. data/blather.gemspec +51 -0
  14. data/examples/certs/README +20 -0
  15. data/examples/certs/ca-bundle.crt +3987 -0
  16. data/examples/echo.rb +19 -0
  17. data/examples/execute.rb +17 -0
  18. data/examples/ping_pong.rb +38 -0
  19. data/examples/print_hierarchy.rb +77 -0
  20. data/examples/rosterprint.rb +15 -0
  21. data/examples/stream_only.rb +28 -0
  22. data/examples/trusted_echo.rb +21 -0
  23. data/examples/xmpp4r/echo.rb +36 -0
  24. data/lib/blather.rb +112 -0
  25. data/lib/blather/cert_store.rb +53 -0
  26. data/lib/blather/client.rb +95 -0
  27. data/lib/blather/client/client.rb +345 -0
  28. data/lib/blather/client/dsl.rb +320 -0
  29. data/lib/blather/client/dsl/pubsub.rb +174 -0
  30. data/lib/blather/core_ext/eventmachine.rb +125 -0
  31. data/lib/blather/core_ext/ipaddr.rb +20 -0
  32. data/lib/blather/errors.rb +69 -0
  33. data/lib/blather/errors/sasl_error.rb +44 -0
  34. data/lib/blather/errors/stanza_error.rb +110 -0
  35. data/lib/blather/errors/stream_error.rb +84 -0
  36. data/lib/blather/file_transfer.rb +107 -0
  37. data/lib/blather/file_transfer/ibb.rb +68 -0
  38. data/lib/blather/file_transfer/s5b.rb +114 -0
  39. data/lib/blather/jid.rb +141 -0
  40. data/lib/blather/roster.rb +118 -0
  41. data/lib/blather/roster_item.rb +146 -0
  42. data/lib/blather/stanza.rb +167 -0
  43. data/lib/blather/stanza/disco.rb +32 -0
  44. data/lib/blather/stanza/disco/capabilities.rb +161 -0
  45. data/lib/blather/stanza/disco/disco_info.rb +205 -0
  46. data/lib/blather/stanza/disco/disco_items.rb +134 -0
  47. data/lib/blather/stanza/iq.rb +144 -0
  48. data/lib/blather/stanza/iq/command.rb +339 -0
  49. data/lib/blather/stanza/iq/ibb.rb +86 -0
  50. data/lib/blather/stanza/iq/ping.rb +50 -0
  51. data/lib/blather/stanza/iq/query.rb +53 -0
  52. data/lib/blather/stanza/iq/roster.rb +185 -0
  53. data/lib/blather/stanza/iq/s5b.rb +208 -0
  54. data/lib/blather/stanza/iq/si.rb +415 -0
  55. data/lib/blather/stanza/iq/vcard.rb +149 -0
  56. data/lib/blather/stanza/message.rb +428 -0
  57. data/lib/blather/stanza/message/muc_user.rb +119 -0
  58. data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
  59. data/lib/blather/stanza/presence.rb +172 -0
  60. data/lib/blather/stanza/presence/c.rb +100 -0
  61. data/lib/blather/stanza/presence/muc.rb +35 -0
  62. data/lib/blather/stanza/presence/muc_user.rb +147 -0
  63. data/lib/blather/stanza/presence/status.rb +218 -0
  64. data/lib/blather/stanza/presence/subscription.rb +100 -0
  65. data/lib/blather/stanza/pubsub.rb +119 -0
  66. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  67. data/lib/blather/stanza/pubsub/create.rb +65 -0
  68. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  69. data/lib/blather/stanza/pubsub/event.rb +139 -0
  70. data/lib/blather/stanza/pubsub/items.rb +103 -0
  71. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  72. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  73. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  74. data/lib/blather/stanza/pubsub/subscription.rb +135 -0
  75. data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
  76. data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
  77. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  78. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  79. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  80. data/lib/blather/stanza/x.rb +416 -0
  81. data/lib/blather/stream.rb +266 -0
  82. data/lib/blather/stream/client.rb +32 -0
  83. data/lib/blather/stream/component.rb +39 -0
  84. data/lib/blather/stream/features.rb +70 -0
  85. data/lib/blather/stream/features/register.rb +38 -0
  86. data/lib/blather/stream/features/resource.rb +63 -0
  87. data/lib/blather/stream/features/sasl.rb +190 -0
  88. data/lib/blather/stream/features/session.rb +45 -0
  89. data/lib/blather/stream/features/tls.rb +29 -0
  90. data/lib/blather/stream/parser.rb +102 -0
  91. data/lib/blather/version.rb +3 -0
  92. data/lib/blather/xmpp_node.rb +94 -0
  93. data/spec/blather/client/client_spec.rb +687 -0
  94. data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
  95. data/spec/blather/client/dsl_spec.rb +266 -0
  96. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  97. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  98. data/spec/blather/errors/stream_error_spec.rb +108 -0
  99. data/spec/blather/errors_spec.rb +33 -0
  100. data/spec/blather/file_transfer_spec.rb +135 -0
  101. data/spec/blather/jid_spec.rb +87 -0
  102. data/spec/blather/roster_item_spec.rb +134 -0
  103. data/spec/blather/roster_spec.rb +107 -0
  104. data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
  105. data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
  106. data/spec/blather/stanza/iq/command_spec.rb +206 -0
  107. data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
  108. data/spec/blather/stanza/iq/ping_spec.rb +45 -0
  109. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  110. data/spec/blather/stanza/iq/roster_spec.rb +139 -0
  111. data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
  112. data/spec/blather/stanza/iq/si_spec.rb +98 -0
  113. data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
  114. data/spec/blather/stanza/iq_spec.rb +61 -0
  115. data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
  116. data/spec/blather/stanza/message_spec.rb +282 -0
  117. data/spec/blather/stanza/presence/c_spec.rb +56 -0
  118. data/spec/blather/stanza/presence/muc_spec.rb +37 -0
  119. data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
  120. data/spec/blather/stanza/presence/status_spec.rb +144 -0
  121. data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
  122. data/spec/blather/stanza/presence_spec.rb +125 -0
  123. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  124. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  125. data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
  126. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  127. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  128. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  129. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  130. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  131. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  132. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
  133. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  134. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  135. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  136. data/spec/blather/stanza/pubsub_spec.rb +68 -0
  137. data/spec/blather/stanza/x_spec.rb +231 -0
  138. data/spec/blather/stanza_spec.rb +134 -0
  139. data/spec/blather/stream/client_spec.rb +1090 -0
  140. data/spec/blather/stream/component_spec.rb +108 -0
  141. data/spec/blather/stream/parser_spec.rb +152 -0
  142. data/spec/blather/stream/ssl_spec.rb +32 -0
  143. data/spec/blather/xmpp_node_spec.rb +47 -0
  144. data/spec/blather_spec.rb +34 -0
  145. data/spec/fixtures/pubsub.rb +311 -0
  146. data/spec/spec_helper.rb +17 -0
  147. data/yard/templates/default/class/html/handlers.erb +18 -0
  148. data/yard/templates/default/class/setup.rb +10 -0
  149. data/yard/templates/default/class/text/handlers.erb +1 -0
  150. metadata +459 -0
@@ -0,0 +1,428 @@
1
+ module Blather
2
+ class Stanza
3
+
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`.
74
+ #
75
+ # Blather provides a helper for each possible type:
76
+ #
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`.
85
+ #
86
+ # msg = Message.new
87
+ # msg.type # => :chat
88
+ # msg.chat? # => true
89
+ # msg.type = :normal
90
+ # msg.normal? # => true
91
+ # msg.chat? # => false
92
+ #
93
+ # msg.type = :invalid # => RuntimeError
94
+ #
95
+ #
96
+ # ## "Body" Element
97
+ #
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.
101
+ #
102
+ # Blather provides an attribute-like syntax for Message `body` elements.
103
+ #
104
+ # msg = Message.new 'user@domain.tld', 'message body'
105
+ # msg.body # => 'message body'
106
+ #
107
+ # msg.body = 'other message'
108
+ # msg.body # => 'other message'
109
+ #
110
+ # ## "Subject" Element
111
+ #
112
+ # The `subject` element contains human-readable XML character data that
113
+ # specifies the topic of the message.
114
+ #
115
+ # Blather provides an attribute-like syntax for Message `subject` elements.
116
+ #
117
+ # msg = Message.new 'user@domain.tld', 'message body'
118
+ # msg.subject = 'message subject'
119
+ # msg.subject # => 'message subject'
120
+ #
121
+ # ## "Thread" Element
122
+ #
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.
134
+ #
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].
140
+ #
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.
145
+ #
146
+ # Blather provides an attribute-like syntax for Message `thread` elements.
147
+ #
148
+ # msg = Message.new
149
+ # msg.thread = '12345'
150
+ # msg.thread # => '12345'
151
+ #
152
+ # Parent threads can be set using a hash:
153
+ #
154
+ # msg.thread = {'parent-id' => 'thread-id'}
155
+ # msg.thread # => 'thread-id'
156
+ # msg.parent_thread # => 'parent-id'
157
+ #
158
+ # @handler :message
159
+ class Message < Stanza
160
+ # @private
161
+ VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal].freeze
162
+
163
+ # @private
164
+ VALID_CHAT_STATES = [:active, :composing, :gone, :inactive, :paused].freeze
165
+ # @private
166
+ CHAT_STATE_NS = 'http://jabber.org/protocol/chatstates'.freeze
167
+
168
+ # @private
169
+ HTML_NS = 'http://jabber.org/protocol/xhtml-im'.freeze
170
+ # @private
171
+ HTML_BODY_NS = 'http://www.w3.org/1999/xhtml'.freeze
172
+
173
+ register :message
174
+
175
+ # @private
176
+ def self.import(node)
177
+ klass = nil
178
+ node.children.detect do |e|
179
+ ns = e.namespace ? e.namespace.href : nil
180
+ klass = class_from_registration(e.element_name, ns)
181
+ end
182
+
183
+ if klass == Blather::Stanza::Presence::MUCUser
184
+ klass = Blather::Stanza::Message::MUCUser
185
+ end
186
+
187
+ if klass && klass != self && ![Blather::Stanza::X, Blather::Stanza::Iq].include?(klass)
188
+ klass.import(node)
189
+ else
190
+ new(node[:type]).inherit(node)
191
+ end
192
+ end
193
+
194
+ # Create a new Message stanza
195
+ #
196
+ # @param [#to_s] to the JID to send the message to
197
+ # @param [#to_s] body the body of the message
198
+ # @param [Symbol] type the message type. Must be one of VALID_TYPES
199
+ def self.new(to = nil, body = nil, type = :chat)
200
+ node = super :message
201
+ node.to = to
202
+ node.type = type
203
+ node.body = body
204
+ node.chat_state = :active if [:chat, :groupchat].include?(type)
205
+ node
206
+ end
207
+
208
+ # Overrides the parent method to ensure the current chat state is removed
209
+ #
210
+ # @see Blather::Stanza::Iq#inherit
211
+ def inherit(node)
212
+ xpath('ns:*', :ns => CHAT_STATE_NS).remove
213
+ super
214
+ end
215
+
216
+ # Check if the Message is of type :chat
217
+ #
218
+ # @return [true, false]
219
+ def chat?
220
+ self.type == :chat
221
+ end
222
+
223
+ # Check if the Message is of type :error
224
+ #
225
+ # @return [true, false]
226
+ def error?
227
+ self.type == :error
228
+ end
229
+
230
+ # Check if the Message is of type :groupchat
231
+ #
232
+ # @return [true, false]
233
+ def groupchat?
234
+ self.type == :groupchat
235
+ end
236
+
237
+ # Check if the Message is of type :headline
238
+ #
239
+ # @return [true, false]
240
+ def headline?
241
+ self.type == :headline
242
+ end
243
+
244
+ # Check if the Message is of type :normal
245
+ #
246
+ # @return [true, false]
247
+ def normal?
248
+ self.type == :normal
249
+ end
250
+
251
+ # Ensures type is :get, :set, :result or :error
252
+ #
253
+ # @param [#to_sym] type the Message type. Must be one of VALID_TYPES
254
+ def type=(type)
255
+ if type && !VALID_TYPES.include?(type.to_sym)
256
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
257
+ end
258
+ super
259
+ end
260
+
261
+ # Get the message body
262
+ #
263
+ # @return [String]
264
+ def body
265
+ read_content :body
266
+ end
267
+
268
+ # Set the message body
269
+ #
270
+ # @param [#to_s] body the message body
271
+ def body=(body)
272
+ set_content_for :body, body
273
+ end
274
+
275
+ # Get the message xhtml node
276
+ # This will create the node if it doesn't exist
277
+ #
278
+ # @return [XML::Node]
279
+ def xhtml_node
280
+ unless h = find_first('ns:html', :ns => HTML_NS) || find_first('ns:html', :ns => HTML_BODY_NS)
281
+ self << (h = XMPPNode.new('html', self.document))
282
+ h.namespace = HTML_NS
283
+ end
284
+
285
+ unless b = h.find_first('ns:body', :ns => HTML_BODY_NS)
286
+ b = XMPPNode.new('body', self.document)
287
+ b.namespace = HTML_BODY_NS
288
+ h << b
289
+ end
290
+
291
+ b
292
+ end
293
+
294
+ # Get the message xhtml
295
+ #
296
+ # @return [String]
297
+ def xhtml
298
+ self.xhtml_node.inner_html.strip
299
+ end
300
+
301
+ # Set the message xhtml
302
+ # This will use Nokogiri to ensure the xhtml is valid
303
+ #
304
+ # @param [#to_s] valid xhtml
305
+ def xhtml=(xhtml_body)
306
+ self.xhtml_node.inner_html = Nokogiri::XML::DocumentFragment.parse(xhtml_body)
307
+ end
308
+
309
+ # Get the message subject
310
+ #
311
+ # @return [String]
312
+ def subject
313
+ read_content :subject
314
+ end
315
+
316
+ # Set the message subject
317
+ #
318
+ # @param [#to_s] body the message subject
319
+ def subject=(subject)
320
+ set_content_for :subject, subject
321
+ end
322
+
323
+ # Get the message thread
324
+ #
325
+ # @return [String]
326
+ def thread
327
+ read_content :thread
328
+ end
329
+
330
+ # Get the parent thread
331
+ #
332
+ # @return [String, nil]
333
+ def parent_thread
334
+ n = find_first('thread')
335
+ n[:parent] if n
336
+ end
337
+
338
+ # Set the thread
339
+ #
340
+ # @overload thread=(hash)
341
+ # Set a thread with a parent
342
+ # @param [Hash<parent-id => thread-id>] thread
343
+ # @overload thread=(thread)
344
+ # Set a thread id
345
+ # @param [#to_s] thread the new thread id
346
+ def thread=(thread)
347
+ parent, thread = thread.to_a.flatten if thread.is_a?(Hash)
348
+ set_content_for :thread, thread
349
+ find_first('thread')[:parent] = parent
350
+ end
351
+
352
+ # Returns the message's x:data form child
353
+ def form
354
+ X.find_or_create self
355
+ end
356
+
357
+ # Get the message chat state
358
+ #
359
+ # @return [Symbol]
360
+ def chat_state
361
+ if (elem = find_first('ns:*', :ns => CHAT_STATE_NS)) && VALID_CHAT_STATES.include?(name = elem.name.to_sym)
362
+ name
363
+ end
364
+ end
365
+
366
+ # Set the message chat state
367
+ #
368
+ # @param [#to_s] chat_state the message chat state. Must be one of VALID_CHAT_STATES
369
+ def chat_state=(chat_state)
370
+ if chat_state && !VALID_CHAT_STATES.include?(chat_state.to_sym)
371
+ raise ArgumentError, "Invalid Chat State (#{chat_state}), use: #{VALID_CHAT_STATES*' '}"
372
+ end
373
+
374
+ xpath('ns:*', :ns => CHAT_STATE_NS).remove
375
+
376
+ if chat_state
377
+ state = XMPPNode.new(chat_state, self.document)
378
+ state.namespace = CHAT_STATE_NS
379
+ self << state
380
+ end
381
+ end
382
+
383
+ def delay
384
+ if d = find_first('ns:delay', :ns => "urn:xmpp:delay")
385
+ Delay.new d
386
+ end
387
+ end
388
+
389
+ def delayed?
390
+ !!delay
391
+ end
392
+
393
+ class Delay < XMPPNode
394
+ def self.new(stamp = nil, from = nil, description = nil)
395
+ new_node = super :delay
396
+
397
+ case stamp
398
+ when Nokogiri::XML::Node
399
+ new_node.inherit stamp
400
+ when Hash
401
+ new_node.stamp = stamp[:stamp]
402
+ new_node.from = stamp[:from]
403
+ new_node.description = stamp[:description]
404
+ else
405
+ new_node.stamp = stamp
406
+ new_node.from = from
407
+ new_node.description = description
408
+ end
409
+ new_node
410
+ end
411
+
412
+ def from
413
+ read_attr :from
414
+ end
415
+
416
+ def stamp
417
+ s = read_attr :stamp
418
+ s && Time.parse(s)
419
+ end
420
+
421
+ def description
422
+ content.strip
423
+ end
424
+ end
425
+ end
426
+
427
+ end
428
+ end
@@ -0,0 +1,119 @@
1
+ require 'blather/stanza/muc/muc_user_base'
2
+
3
+ module Blather
4
+ class Stanza
5
+ class Message
6
+
7
+ class MUCUser < Message
8
+ include Blather::Stanza::MUC::MUCUserBase
9
+
10
+ register :muc_user_message, :x, "http://jabber.org/protocol/muc#user"
11
+
12
+ def self.new(to = nil, body = nil, type = :normal)
13
+ super
14
+ end
15
+
16
+ def invite?
17
+ !!find_invite_node
18
+ end
19
+
20
+ def invite_decline?
21
+ !!find_decline_node
22
+ end
23
+
24
+ def invite
25
+ if invite = find_invite_node
26
+ Invite.new invite
27
+ else
28
+ muc_user << (invite = Invite.new nil, nil, nil, self.document)
29
+ invite
30
+ end
31
+ end
32
+
33
+ def find_invite_node
34
+ muc_user.find_first 'ns:invite', :ns => self.class.registered_ns
35
+ end
36
+
37
+ def decline
38
+ if decline = find_decline_node
39
+ Decline.new decline
40
+ else
41
+ muc_user << (decline = Decline.new nil, nil, nil, self.document)
42
+ decline
43
+ end
44
+ end
45
+
46
+ def find_decline_node
47
+ muc_user.find_first 'ns:decline', :ns => self.class.registered_ns
48
+ end
49
+
50
+ class InviteBase < XMPPNode
51
+ def self.new(element_name, to = nil, from = nil, reason = nil, document = nil)
52
+ new_node = super element_name, document
53
+
54
+ case to
55
+ when self
56
+ to.document ||= document
57
+ return to
58
+ when Nokogiri::XML::Node
59
+ new_node.inherit to
60
+ when Hash
61
+ new_node.to = to[:to]
62
+ new_node.from = to[:from]
63
+ new_node.reason = to[:reason]
64
+ else
65
+ new_node.to = to
66
+ new_node.from = from
67
+ new_node.reason = reason
68
+ end
69
+ new_node
70
+ end
71
+
72
+ def to
73
+ read_attr :to
74
+ end
75
+
76
+ def to=(val)
77
+ write_attr :to, val
78
+ end
79
+
80
+ def from
81
+ read_attr :from
82
+ end
83
+
84
+ def from=(val)
85
+ write_attr :from, val
86
+ end
87
+
88
+ def reason
89
+ reason_node.content.strip
90
+ end
91
+
92
+ def reason=(val)
93
+ reason_node.content = val
94
+ end
95
+
96
+ def reason_node
97
+ unless reason = find_first('ns:reason', :ns => MUCUser.registered_ns)
98
+ self << (reason = XMPPNode.new('reason', self.document))
99
+ end
100
+ reason
101
+ end
102
+ end
103
+
104
+ class Invite < InviteBase
105
+ def self.new(*args)
106
+ new_node = super :invite, *args
107
+ end
108
+ end
109
+
110
+ class Decline < InviteBase
111
+ def self.new(*args)
112
+ new_node = super :decline, *args
113
+ end
114
+ end
115
+ end # MUC
116
+
117
+ end # Presence
118
+ end # Stanza
119
+ end # Blather