agent_xmpp 0.1.0

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 (73) hide show
  1. data/.document +5 -0
  2. data/.gitignore +11 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +417 -0
  5. data/Rakefile +75 -0
  6. data/VERSION +1 -0
  7. data/agent_xmpp.gemspec +144 -0
  8. data/lib/agent_xmpp.rb +22 -0
  9. data/lib/agent_xmpp/admin.rb +113 -0
  10. data/lib/agent_xmpp/client.rb +7 -0
  11. data/lib/agent_xmpp/client/boot.rb +83 -0
  12. data/lib/agent_xmpp/client/client.rb +64 -0
  13. data/lib/agent_xmpp/client/connection.rb +108 -0
  14. data/lib/agent_xmpp/client/controller.rb +394 -0
  15. data/lib/agent_xmpp/client/message_delegate.rb +720 -0
  16. data/lib/agent_xmpp/client/message_pipe.rb +193 -0
  17. data/lib/agent_xmpp/client/response.rb +102 -0
  18. data/lib/agent_xmpp/config.rb +48 -0
  19. data/lib/agent_xmpp/main.rb +175 -0
  20. data/lib/agent_xmpp/models.rb +7 -0
  21. data/lib/agent_xmpp/models/contact.rb +85 -0
  22. data/lib/agent_xmpp/models/message.rb +152 -0
  23. data/lib/agent_xmpp/models/publication.rb +53 -0
  24. data/lib/agent_xmpp/models/roster.rb +107 -0
  25. data/lib/agent_xmpp/models/service.rb +91 -0
  26. data/lib/agent_xmpp/models/subscription.rb +61 -0
  27. data/lib/agent_xmpp/models/table_definitions.rb +107 -0
  28. data/lib/agent_xmpp/patches.rb +7 -0
  29. data/lib/agent_xmpp/patches/array.rb +32 -0
  30. data/lib/agent_xmpp/patches/float.rb +10 -0
  31. data/lib/agent_xmpp/patches/hash.rb +13 -0
  32. data/lib/agent_xmpp/patches/object.rb +15 -0
  33. data/lib/agent_xmpp/patches/rexml.rb +69 -0
  34. data/lib/agent_xmpp/patches/string.rb +15 -0
  35. data/lib/agent_xmpp/xmpp.rb +18 -0
  36. data/lib/agent_xmpp/xmpp/element.rb +158 -0
  37. data/lib/agent_xmpp/xmpp/entry.rb +36 -0
  38. data/lib/agent_xmpp/xmpp/error_response.rb +189 -0
  39. data/lib/agent_xmpp/xmpp/iq.rb +90 -0
  40. data/lib/agent_xmpp/xmpp/iq_command.rb +54 -0
  41. data/lib/agent_xmpp/xmpp/iq_disco.rb +206 -0
  42. data/lib/agent_xmpp/xmpp/iq_pubsub.rb +270 -0
  43. data/lib/agent_xmpp/xmpp/iq_roster.rb +183 -0
  44. data/lib/agent_xmpp/xmpp/iq_version.rb +89 -0
  45. data/lib/agent_xmpp/xmpp/jid.rb +150 -0
  46. data/lib/agent_xmpp/xmpp/message.rb +82 -0
  47. data/lib/agent_xmpp/xmpp/presence.rb +127 -0
  48. data/lib/agent_xmpp/xmpp/sasl.rb +241 -0
  49. data/lib/agent_xmpp/xmpp/stanza.rb +107 -0
  50. data/lib/agent_xmpp/xmpp/x_data.rb +357 -0
  51. data/test/app/app.rb +339 -0
  52. data/test/cases/test_application_message_processing.rb +65 -0
  53. data/test/cases/test_errors.rb +24 -0
  54. data/test/cases/test_presence_management.rb +139 -0
  55. data/test/cases/test_roster_management.rb +214 -0
  56. data/test/cases/test_service_discovery.rb +168 -0
  57. data/test/cases/test_session_management.rb +120 -0
  58. data/test/cases/test_version_discovery.rb +67 -0
  59. data/test/helpers/matchers.rb +23 -0
  60. data/test/helpers/mocks.rb +82 -0
  61. data/test/helpers/test_case_extensions.rb +45 -0
  62. data/test/helpers/test_client.rb +44 -0
  63. data/test/helpers/test_delegate.rb +60 -0
  64. data/test/helpers/test_helper.rb +91 -0
  65. data/test/messages/application_messages.rb +206 -0
  66. data/test/messages/error_messages.rb +35 -0
  67. data/test/messages/presence_messages.rb +66 -0
  68. data/test/messages/roster_messages.rb +126 -0
  69. data/test/messages/service_discovery_messages.rb +201 -0
  70. data/test/messages/session_messages.rb +158 -0
  71. data/test/messages/version_discovery_messages.rb +69 -0
  72. data/test/peer/peer.rb +21 -0
  73. metadata +187 -0
@@ -0,0 +1,241 @@
1
+ # Original from XMPP4R - XMPP Library for Ruby Website::http://home.gna.org/xmpp4r/
2
+ ##############################################################################################################
3
+ module AgentXmpp
4
+
5
+ #####-------------------------------------------------------------------------------------------------------
6
+ module Xmpp
7
+
8
+ #####-------------------------------------------------------------------------------------------------------
9
+ module SASL
10
+
11
+ #.....................................................................................................
12
+ NS_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
13
+
14
+ #####-------------------------------------------------------------------------------------------------------
15
+ class << self
16
+
17
+ #.....................................................................................................
18
+ def new(mechanism)
19
+ case mechanism
20
+ when 'DIGEST-MD5'
21
+ DigestMD5.new
22
+ when 'PLAIN'
23
+ Plain.new
24
+ when 'ANONYMOUS'
25
+ Anonymous.new
26
+ else
27
+ raise AgentXmppError "Unknown SASL mechanism: #{mechanism}"
28
+ end
29
+ end
30
+
31
+ #.........................................................................................................
32
+ def authenticate(stream_mechanisms)
33
+ if stream_mechanisms.include?('PLAIN')
34
+ Send(new('PLAIN').auth(AgentXmpp.jid, AgentXmpp.password))
35
+ else
36
+ raise AgentXmppError, "PLAIN authentication required"
37
+ end
38
+ end
39
+
40
+ #### self
41
+ end
42
+
43
+ #####-------------------------------------------------------------------------------------------------------
44
+ class Base
45
+
46
+ #.....................................................................................................
47
+ def initialize
48
+ end
49
+
50
+ private
51
+
52
+ #.....................................................................................................
53
+ def generate_auth(mechanism, text=nil)
54
+ auth = REXML::Element.new 'auth'
55
+ auth.add_namespace NS_SASL
56
+ auth.attributes['mechanism'] = mechanism
57
+ auth.text = text
58
+ auth
59
+ end
60
+
61
+ #.....................................................................................................
62
+ def generate_nonce
63
+ Digest::MD5.hexdigest(Time.new.to_f.to_s)
64
+ end
65
+
66
+ #### Base
67
+ end
68
+
69
+ #####-------------------------------------------------------------------------------------------------------
70
+ class Plain < Base
71
+
72
+ #.....................................................................................................
73
+ def auth(jid, password)
74
+ auth_text = "#{jid.strip}\x00#{jid.node}\x00#{password}"
75
+ generate_auth('PLAIN', Base64::encode64(auth_text).gsub(/\s/, ''))
76
+ end
77
+
78
+ ### Plain
79
+ end
80
+
81
+ #####-------------------------------------------------------------------------------------------------------
82
+ class Anonymous < Base
83
+
84
+ #.....................................................................................................
85
+ def auth(password)
86
+ auth_text = "#{@stream.jid.node}"
87
+ error = nil
88
+ @stream.send(generate_auth('ANONYMOUS', Base64::encode64(auth_text).gsub(/\s/, ''))) do |reply|
89
+ error = reply.first_element(nil).name if reply.name != 'success'
90
+ true
91
+ end
92
+ raise error if error
93
+ end
94
+
95
+ #### Anonymous
96
+ end
97
+
98
+ #####-------------------------------------------------------------------------------------------------------
99
+ class DigestMD5 < Base
100
+
101
+ #.....................................................................................................
102
+ def initialize(stream)
103
+ super
104
+ challenge = {}
105
+ error = nil
106
+ @stream.send(generate_auth('DIGEST-MD5')) { |reply|
107
+ if reply.name == 'challenge' and reply.namespace == NS_SASL
108
+ challenge = decode_challenge(reply.text)
109
+ else
110
+ error = reply.first_element(nil).name
111
+ end
112
+ true
113
+ }
114
+ raise error if error
115
+ @nonce = challenge['nonce']
116
+ @realm = challenge['realm']
117
+ end
118
+
119
+ #.....................................................................................................
120
+ def decode_challenge(challenge)
121
+ text = Base64::decode64(challenge)
122
+ res = {}
123
+
124
+ state = :key
125
+ key = ''
126
+ value = ''
127
+
128
+ text.scan(/./) do |ch|
129
+ if state == :key
130
+ if ch == '='
131
+ state = :value
132
+ else
133
+ key += ch
134
+ end
135
+
136
+ elsif state == :value
137
+ if ch == ','
138
+ res[key] = value
139
+ key = ''
140
+ value = ''
141
+ state = :key
142
+ elsif ch == '"' and value == ''
143
+ state = :quote
144
+ else
145
+ value += ch
146
+ end
147
+
148
+ elsif state == :quote
149
+ if ch == '"'
150
+ state = :value
151
+ else
152
+ value += ch
153
+ end
154
+ end
155
+ end
156
+ res[key] = value unless key == ''
157
+
158
+ res
159
+ end
160
+
161
+ #.....................................................................................................
162
+ def auth(password)
163
+ response = {}
164
+ response['nonce'] = @nonce
165
+ response['charset'] = 'utf-8'
166
+ response['username'] = @stream.jid.node
167
+ response['realm'] = @realm || @stream.jid.domain
168
+ response['cnonce'] = generate_nonce
169
+ response['nc'] = '00000001'
170
+ response['qop'] = 'auth'
171
+ response['digest-uri'] = "xmpp/#{@stream.jid.domain}"
172
+ response['response'] = response_value(@stream.jid.node, @stream.jid.domain, response['digest-uri'], password, @nonce, response['cnonce'], response['qop'], response['authzid'])
173
+ response.each do |key,value|
174
+ unless %w(nc qop response charset).include? key
175
+ response[key] = "\"#{value}\""
176
+ end
177
+ end
178
+
179
+ response_text = response.collect { |k,v| "#{k}=#{v}" }.join(',')
180
+ r = REXML::Element.new('response')
181
+ r.add_namespace NS_SASL
182
+ r.text = Base64::encode64(response_text).gsub(/\s/, '')
183
+
184
+ success_already = false
185
+ error = nil
186
+ @stream.send(r) do |reply|
187
+ if reply.name == 'success'
188
+ success_already = true
189
+ elsif reply.name != 'challenge'
190
+ error = reply.first_element(nil).name
191
+ end
192
+ true
193
+ end
194
+
195
+ return if success_already
196
+ raise error if error
197
+
198
+ r.text = nil
199
+ @stream.send(r) do |reply|
200
+ if reply.name != 'success'
201
+ error = reply.first_element(nil).name
202
+ end
203
+ true
204
+ end
205
+ raise error if error
206
+
207
+ end
208
+
209
+ private
210
+
211
+ #.....................................................................................................
212
+ def h(s); Digest::MD5.digest(s); end
213
+
214
+ #.....................................................................................................
215
+ def hh(s); Digest::MD5.hexdigest(s); end
216
+
217
+ #.....................................................................................................
218
+ def response_value(username, realm, digest_uri, passwd, nonce, cnonce, qop, authzid)
219
+ a1_h = h("#{username}:#{realm}:#{passwd}")
220
+ a1 = "#{a1_h}:#{nonce}:#{cnonce}"
221
+ if authzid
222
+ a1 += ":#{authzid}"
223
+ end
224
+ if qop == 'auth-int' || qop == 'auth-conf'
225
+ a2 = "AUTHENTICATE:#{digest_uri}:00000000000000000000000000000000"
226
+ else
227
+ a2 = "AUTHENTICATE:#{digest_uri}"
228
+ end
229
+ hh("#{hh(a1)}:#{nonce}:00000001:#{cnonce}:#{qop}:#{hh(a2)}")
230
+ end
231
+ end
232
+
233
+ #### DigestMD5
234
+ end
235
+
236
+ #### XMPP
237
+ end
238
+
239
+ #### AgentXmpp
240
+ end
241
+
@@ -0,0 +1,107 @@
1
+ # Original from XMPP4R - XMPP Library for Ruby Website::http://home.gna.org/xmpp4r/
2
+ ##############################################################################################################
3
+ module AgentXmpp
4
+
5
+ #####-------------------------------------------------------------------------------------------------------
6
+ module Xmpp
7
+
8
+ #####-------------------------------------------------------------------------------------------------------
9
+ class IdGenerator
10
+
11
+ #.......................................................................................................
12
+ @last_id = 0
13
+
14
+ #.......................................................................................................
15
+ class << self
16
+
17
+ #.......................................................................................................
18
+ def generate_id
19
+ @last_id += 1
20
+ timefrac = Time.new.to_f.to_s.split(/\./, 2).last[-3..-1]
21
+ "#{@last_id}#{timefrac}"
22
+ end
23
+
24
+ #### self
25
+ end
26
+
27
+ #### IdGenerator
28
+ end
29
+
30
+ #####-------------------------------------------------------------------------------------------------------
31
+ class Stanza < Element
32
+
33
+ #.......................................................................................................
34
+ def Stanza.answer(stanza, import=true)
35
+ x = stanza.class.new
36
+ if import
37
+ x.import(stanza)
38
+ end
39
+ x.from = stanza.to
40
+ x.to = stanza.from
41
+ x.id = stanza.id
42
+ x
43
+ end
44
+
45
+ #.......................................................................................................
46
+ def error
47
+ first_element('error')
48
+ end
49
+
50
+ #.......................................................................................................
51
+ def answer(import=true)
52
+ Stanza.answer(self, import)
53
+ end
54
+
55
+ #.......................................................................................................
56
+ def normalize
57
+ end
58
+
59
+ #.......................................................................................................
60
+ def to
61
+ (a = attribute('to')).nil? ? a : Jid.new(a.value)
62
+ end
63
+
64
+ #.......................................................................................................
65
+ def to=(v)
66
+ add_attribute('to', v ? v.to_s : nil)
67
+ end
68
+
69
+ #.......................................................................................................
70
+ def from
71
+ (a = attribute('from')).nil? ? a : Jid.new(a.value)
72
+ end
73
+
74
+ #.......................................................................................................
75
+ def from=(v)
76
+ add_attribute('from', v ? v.to_s : nil)
77
+ end
78
+
79
+ #.......................................................................................................
80
+ def id
81
+ (a = attribute('id')).nil? ? a : a.value
82
+ end
83
+
84
+ #.......................................................................................................
85
+ def id=(v)
86
+ add_attribute('id', v.to_s)
87
+ end
88
+
89
+ #.......................................................................................................
90
+ def type
91
+ stanza_type = attributes['type']
92
+ stanza_type.nil? ? nil : stanza_type.to_sym
93
+ end
94
+
95
+ #.......................................................................................................
96
+ def type=(t)
97
+ attributes['type'] = t.to_s
98
+ end
99
+
100
+ #### Stanza
101
+ end
102
+
103
+ #### XMPP
104
+ end
105
+
106
+ #### AgentXmpp
107
+ end
@@ -0,0 +1,357 @@
1
+ # Original from XMPP4R - XMPP Library for Ruby Website::http://home.gna.org/xmpp4r/
2
+ ##############################################################################################################
3
+ module AgentXmpp
4
+
5
+ #####-------------------------------------------------------------------------------------------------------
6
+ module Xmpp
7
+
8
+ #####-------------------------------------------------------------------------------------------------------
9
+ class X < Element
10
+ name_xmlns 'x'
11
+ end
12
+
13
+ #####-------------------------------------------------------------------------------------------------------
14
+ module XParent
15
+
16
+ #.......................................................................................................
17
+ def x(wanted_xmlns=nil)
18
+ if wanted_xmlns.kind_of? Class and wanted_xmlns.ancestors.include? Element
19
+ wanted_xmlns = wanted_xmlns.new.namespace
20
+ end
21
+ elements.to_a('x').select{|x| wanted_xmlns.nil? or wanted_xmlns == x.namespace}.first
22
+ end
23
+
24
+ #### XParent
25
+ end
26
+
27
+ #####-------------------------------------------------------------------------------------------------------
28
+ class XData < X
29
+
30
+ #.....................................................................................................
31
+ name_xmlns 'x', 'jabber:x:data'
32
+
33
+ #.....................................................................................................
34
+ def initialize(type=nil)
35
+ super()
36
+ self.type = type
37
+ end
38
+
39
+ #.....................................................................................................
40
+ def fields
41
+ elements.to_a('field')
42
+ end
43
+
44
+ #.....................................................................................................
45
+ def items
46
+ elements.to_a('item')
47
+ end
48
+
49
+ #.....................................................................................................
50
+ def type
51
+ attributes['type'].to_sym
52
+ end
53
+
54
+ #.....................................................................................................
55
+ def type=(t)
56
+ attributes['type'] = t.to_s
57
+ end
58
+
59
+ #.....................................................................................................
60
+ def title
61
+ first_element('title')
62
+ end
63
+
64
+ #.....................................................................................................
65
+ def add_title(title)
66
+ delete_elements('title')
67
+ add_element(XDataTitle.new(title))
68
+ end
69
+
70
+ #.....................................................................................................
71
+ def instructions
72
+ elements.inject('instructions', []) {|f, xe| f << xe}
73
+ end
74
+
75
+ #.....................................................................................................
76
+ def add_instructions(i)
77
+ add(XDataInstructions.new(i))
78
+ end
79
+
80
+ #.....................................................................................................
81
+ def add_fixed(val=' ')
82
+ add_field_with_value(nil, val, 'fixed')
83
+ end
84
+
85
+ #.....................................................................................................
86
+ def add_hidden(var, val)
87
+ add_field_with_value(var, val, 'hidden')
88
+ end
89
+
90
+ #.....................................................................................................
91
+ def add_text_single(var, label=nil)
92
+ add_field(var, label, 'text-single')
93
+ end
94
+
95
+ #.....................................................................................................
96
+ def add_text_multi(var, label=nil)
97
+ add_field(var, label, 'text-multi')
98
+ end
99
+
100
+ #.....................................................................................................
101
+ def add_text_private(var, label=nil)
102
+ add_field(var, label, 'text-private')
103
+ end
104
+
105
+ #.....................................................................................................
106
+ def add_jid_single(var, label=nil)
107
+ add_field(var, label, 'jid-single')
108
+ end
109
+
110
+ #.....................................................................................................
111
+ def add_boolean(var, label=nil)
112
+ add_field(var, label, 'boolean')
113
+ end
114
+
115
+ #.....................................................................................................
116
+ def add_list_single(var, opts, label=nil, default=nil)
117
+ field = XDataField.new(var, 'list-single')
118
+ field.label = label if label
119
+ field.options = opts
120
+ field.values = [default] if default
121
+ self << field
122
+ end
123
+
124
+ #.....................................................................................................
125
+ def add_field_with_value(var, value, type=nil)
126
+ field = XDataField.new(var, type)
127
+ field.values = value
128
+ self << field
129
+ end
130
+
131
+ #.....................................................................................................
132
+ def to_native
133
+ f, i = fields, items
134
+ if f.length.eql?(1) and i.length.eql?(0)
135
+ to_scalar(f.first.values)
136
+ elsif f.length > 1 and i.length.eql?(0)
137
+ to_hash(f)
138
+ elsif i.length > 0
139
+ to_array_of_hashes(i)
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
145
+ #.....................................................................................................
146
+ def to_params
147
+ to_hash(fields)
148
+ end
149
+
150
+ #.....................................................................................................
151
+ def to_x_data
152
+ self
153
+ end
154
+
155
+ #.....................................................................................................
156
+ # private
157
+ #.....................................................................................................
158
+ def to_scalar(vals)
159
+ vals.length.eql?(1) ? vals.first : vals
160
+ end
161
+
162
+ #.....................................................................................................
163
+ def to_hash(flds)
164
+ flds.inject({}) do |h,f|
165
+ if handle_field_type?(f)
166
+ h[f.var] = to_scalar(get_field_values(f))
167
+ end; h
168
+ end
169
+ end
170
+
171
+ #.....................................................................................................
172
+ def to_array_of_hashes(itms)
173
+ itms.map{|i| to_hash(i.fields)}
174
+ end
175
+
176
+ #.....................................................................................................
177
+ def add_field(var, label, type=nil)
178
+ field = XDataField.new(var, type)
179
+ field.label = label if label
180
+ self << field
181
+ end
182
+
183
+ #.....................................................................................................
184
+ def get_field_values(field)
185
+ vals = field.values
186
+ case field.type
187
+ when :boolean then vals.length.eql?(0) ? ['0'] : vals
188
+ when :"text-multi" then [vals.compact.join("/n")]
189
+ else vals
190
+ end
191
+ end
192
+
193
+ #.....................................................................................................
194
+ def handle_field_type?(field)
195
+ case field.type
196
+ when :fixed then false
197
+ else field
198
+ end
199
+
200
+ end
201
+
202
+ #.....................................................................................................
203
+ private :to_scalar, :to_hash, :to_array_of_hashes, :add_field, :get_field_values,
204
+ :handle_field_type?
205
+
206
+ end
207
+
208
+ #####-------------------------------------------------------------------------------------------------------
209
+ class XDataTitle < Element
210
+
211
+ #.....................................................................................................
212
+ name_xmlns 'title', 'jabber:x:data'
213
+
214
+ #.....................................................................................................
215
+ def initialize(title=nil)
216
+ super()
217
+ add_text(title)
218
+ end
219
+
220
+ #.....................................................................................................
221
+ def to_s
222
+ text.to_s
223
+ end
224
+
225
+ #.....................................................................................................
226
+ def title
227
+ text
228
+ end
229
+
230
+ end
231
+
232
+ #####-------------------------------------------------------------------------------------------------------
233
+ class XDataInstructions < Element
234
+
235
+ #.....................................................................................................
236
+ name_xmlns 'instructions', 'jabber:x:data'
237
+
238
+ #.....................................................................................................
239
+ def initialize(instructions=nil)
240
+ super()
241
+ add_text(instructions)
242
+ end
243
+
244
+ #.....................................................................................................
245
+ def to_s
246
+ text.to_s
247
+ end
248
+
249
+ #.....................................................................................................
250
+ def instructions
251
+ text
252
+ end
253
+ end
254
+
255
+ #####-------------------------------------------------------------------------------------------------------
256
+ class XDataField < Element
257
+
258
+ #.....................................................................................................
259
+ name_xmlns 'field', 'jabber:x:data'
260
+ xmpp_attribute :label, :var
261
+ xmpp_attribute :type, :sym => true
262
+
263
+ #.....................................................................................................
264
+ def initialize(var=nil, type=nil)
265
+ super()
266
+ self.var = var if var
267
+ self.type = type if type
268
+ end
269
+
270
+ #.....................................................................................................
271
+ def required?
272
+ res = false
273
+ each_element('required') { res = true }
274
+ res
275
+ end
276
+
277
+ #.....................................................................................................
278
+ def required=(r)
279
+ delete_elements('required')
280
+ add REXML::Element.new('required') if r
281
+ end
282
+
283
+ #.....................................................................................................
284
+ def values
285
+ elements.inject('value', []){|r,v| r << v.text}
286
+ end
287
+
288
+ #.....................................................................................................
289
+ def values=(ary)
290
+ delete_elements('value')
291
+ [ary].flatten.each {|v| add(REXML::Element.new('value')).text = v}
292
+ end
293
+
294
+ #.....................................................................................................
295
+ def options
296
+ elements.inject('option',{}) do |r, e|
297
+ value = nil
298
+ value = (ve = first_element('value')).nil? ? nil : ve.text
299
+ r[value] = e.attributes['label'] if value; r
300
+ end
301
+ end
302
+
303
+ #.....................................................................................................
304
+ def options=(opts)
305
+ opts = opts.inject({}){|h,v| h.update(v => v.to_s.gsub(/_/,' '))} if opts.kind_of?(Array)
306
+ delete_elements('option')
307
+ opts.each do |value,label|
308
+ o = add(REXML::Element.new('option'))
309
+ o.attributes['label'] = label
310
+ o.add(REXML::Element.new('value')).text = value.to_s
311
+ end
312
+ end
313
+ end
314
+
315
+ #####-------------------------------------------------------------------------------------------------------
316
+ class XDataReported < Element
317
+
318
+ #.....................................................................................................
319
+ name_xmlns 'reported', 'jabber:x:data'
320
+
321
+ #.....................................................................................................
322
+ def fields
323
+ elements.to_a('field')
324
+ end
325
+
326
+ #.....................................................................................................
327
+ def add_field(var)
328
+ self << XDataField.new(var)
329
+ end
330
+ end
331
+
332
+ #####-------------------------------------------------------------------------------------------------------
333
+ class XDataItem < Element
334
+
335
+ #.....................................................................................................
336
+ name_xmlns 'item', 'jabber:x:data'
337
+
338
+ #.....................................................................................................
339
+ def fields
340
+ elements.to_a('field')
341
+ end
342
+
343
+ #.....................................................................................................
344
+ def add_field_with_value(var, value, type=nil)
345
+ field = XDataField.new(var, type)
346
+ field.values = value
347
+ self << field
348
+ end
349
+
350
+ #### XDataItem
351
+ end
352
+
353
+ #### XMPP
354
+ end
355
+
356
+ #### AgentXmpp
357
+ end