troystribling-agent_xmpp 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/README.rdoc +397 -2
  2. data/Rakefile +33 -21
  3. data/VERSION +1 -1
  4. data/agent_xmpp.gemspec +78 -33
  5. data/lib/agent_xmpp.rb +11 -7
  6. data/lib/agent_xmpp/app.rb +0 -5
  7. data/lib/agent_xmpp/app/boot.rb +47 -33
  8. data/lib/agent_xmpp/app/controller.rb +135 -19
  9. data/lib/agent_xmpp/client.rb +6 -1
  10. data/lib/agent_xmpp/client/client.rb +22 -155
  11. data/lib/agent_xmpp/client/connection.rb +41 -250
  12. data/lib/agent_xmpp/client/message_delegate.rb +706 -0
  13. data/lib/agent_xmpp/client/message_pipe.rb +176 -0
  14. data/lib/agent_xmpp/client/publish_model.rb +65 -0
  15. data/lib/agent_xmpp/{app/format.rb → client/response.rb} +19 -24
  16. data/lib/agent_xmpp/client/roster_model.rb +139 -0
  17. data/lib/agent_xmpp/client/services_model.rb +97 -0
  18. data/lib/agent_xmpp/config.rb +45 -0
  19. data/lib/agent_xmpp/main.rb +126 -0
  20. data/lib/agent_xmpp/patches.rb +6 -2
  21. data/lib/agent_xmpp/patches/{standard_library_patches/array.rb → array.rb} +14 -7
  22. data/lib/agent_xmpp/patches/{standard_library_patches/float.rb → float.rb} +0 -0
  23. data/lib/agent_xmpp/patches/{standard_library_patches/hash.rb → hash.rb} +3 -3
  24. data/lib/agent_xmpp/patches/{standard_library_patches/object.rb → object.rb} +3 -3
  25. data/lib/agent_xmpp/patches/rexml.rb +69 -0
  26. data/lib/agent_xmpp/patches/{standard_library_patches/string.rb → string.rb} +5 -0
  27. data/lib/agent_xmpp/xmpp.rb +17 -0
  28. data/lib/agent_xmpp/xmpp/element.rb +159 -0
  29. data/lib/agent_xmpp/xmpp/error_response.rb +179 -0
  30. data/lib/agent_xmpp/xmpp/iq.rb +90 -0
  31. data/lib/agent_xmpp/xmpp/iq_command.rb +47 -0
  32. data/lib/agent_xmpp/xmpp/iq_disco.rb +205 -0
  33. data/lib/agent_xmpp/xmpp/iq_pubsub.rb +268 -0
  34. data/lib/agent_xmpp/xmpp/iq_roster.rb +183 -0
  35. data/lib/agent_xmpp/xmpp/iq_version.rb +89 -0
  36. data/lib/agent_xmpp/xmpp/jid.rb +150 -0
  37. data/lib/agent_xmpp/xmpp/message.rb +82 -0
  38. data/lib/agent_xmpp/xmpp/presence.rb +127 -0
  39. data/lib/agent_xmpp/xmpp/sasl.rb +241 -0
  40. data/lib/agent_xmpp/xmpp/stanza.rb +107 -0
  41. data/lib/agent_xmpp/xmpp/x_data.rb +269 -0
  42. data/test/app/app.rb +134 -0
  43. data/test/cases/test_application_message_processing.rb +65 -0
  44. data/test/cases/test_errors.rb +24 -0
  45. data/test/cases/test_presence_management.rb +139 -0
  46. data/test/cases/test_roster_management.rb +214 -0
  47. data/test/cases/test_service_discovery.rb +168 -0
  48. data/test/cases/test_session_management.rb +120 -0
  49. data/test/cases/test_version_discovery.rb +67 -0
  50. data/test/helpers/matchers.rb +23 -0
  51. data/test/helpers/mocks.rb +82 -0
  52. data/test/helpers/test_case_extensions.rb +45 -0
  53. data/test/helpers/test_client.rb +44 -0
  54. data/test/helpers/test_delegate.rb +61 -0
  55. data/test/helpers/test_helper.rb +91 -0
  56. data/test/messages/application_messages.rb +206 -0
  57. data/test/messages/error_messages.rb +35 -0
  58. data/test/messages/presence_messages.rb +66 -0
  59. data/test/messages/roster_messages.rb +126 -0
  60. data/test/messages/service_discovery_messages.rb +201 -0
  61. data/test/messages/session_messages.rb +158 -0
  62. data/test/messages/version_discovery_messages.rb +69 -0
  63. data/test/peer/peer.rb +36 -0
  64. data/test/xml_messages.txt +197 -0
  65. metadata +82 -42
  66. data/bin/agent_xmpp +0 -56
  67. data/lib/agent_xmpp/app/chat_message_body_controller.rb +0 -16
  68. data/lib/agent_xmpp/app/map.rb +0 -45
  69. data/lib/agent_xmpp/app/routes.rb +0 -77
  70. data/lib/agent_xmpp/app/view.rb +0 -49
  71. data/lib/agent_xmpp/client/parser.rb +0 -71
  72. data/lib/agent_xmpp/patches/standard_library_patches.rb +0 -5
  73. data/lib/agent_xmpp/patches/xmpp4r_patches.rb +0 -3
  74. data/lib/agent_xmpp/patches/xmpp4r_patches/command.rb +0 -26
  75. data/lib/agent_xmpp/patches/xmpp4r_patches/iq.rb +0 -26
  76. data/lib/agent_xmpp/patches/xmpp4r_patches/x_data.rb +0 -116
  77. data/lib/agent_xmpp/utils.rb +0 -2
  78. data/lib/agent_xmpp/utils/logger.rb +0 -24
  79. data/lib/agent_xmpp/utils/roster.rb +0 -23
  80. data/lib/agent_xmpp/version.rb +0 -6
  81. data/test/agent_xmpp_test.rb +0 -7
  82. data/test/test_helper.rb +0 -10
@@ -0,0 +1,176 @@
1
+ ##############################################################################################################
2
+ module AgentXmpp
3
+
4
+ #####-------------------------------------------------------------------------------------------------------
5
+ class MessagePipe
6
+
7
+ #---------------------------------------------------------------------------------------------------------
8
+ attr_reader :connection_status, :delegates, :id_callbacks, :connection, :stream_features,
9
+ :stream_mechanisms
10
+ #---------------------------------------------------------------------------------------------------------
11
+ alias_method :send_to_method, :send
12
+ #---------------------------------------------------------------------------------------------------------
13
+
14
+ #.........................................................................................................
15
+ def initialize(connection)
16
+ @connection = connection
17
+ @connection_status = :offline;
18
+ @delegates = [MessageDelegate]
19
+ @id_callbacks = {}
20
+ end
21
+
22
+ #.........................................................................................................
23
+ def add_delegate(delegate)
24
+ @delegates << delegate unless @delegates.include?(delegate)
25
+ end
26
+
27
+ #.........................................................................................................
28
+ def remove_delegate(delegate)
29
+ @delegates.delete(delegate)
30
+ end
31
+
32
+ #.........................................................................................................
33
+ def delegates_respond_to?(method)
34
+ delegates.inject(0){|r,d| d.respond_to?(method) ? r + 1 : r} > 0
35
+ end
36
+
37
+ #.........................................................................................................
38
+ def broadcast_to_delegates(method, *args)
39
+ delegates.inject([]){|r,d| d.respond_to?(method) ? r.push(d.send(method, *args)) : r}.smash
40
+ end
41
+
42
+ #.........................................................................................................
43
+ def responder_list
44
+ @id_callbacks
45
+ end
46
+
47
+ #.........................................................................................................
48
+ def send(data, &blk)
49
+ raise AgentXmppError, 'not connected' unless connected?
50
+ if block_given? and data.kind_of?(Xmpp::Stanza)
51
+ if data.id.nil?
52
+ data.id = Xmpp::IdGenerator.generate_id
53
+ end
54
+ @id_callbacks[data.id] = blk
55
+ end
56
+ AgentXmpp.logger.info "SEND: #{data.to_s}"
57
+ @connection.send_data(data.to_s)
58
+ end
59
+
60
+ #.........................................................................................................
61
+ def send_resp(resp)
62
+ [resp].flatten.map {|r| r.kind_of?(AgentXmpp::Response) ? send(r.message, &r.responds_with) : r}
63
+ end
64
+
65
+ #.........................................................................................................
66
+ def connected?
67
+ connection and !connection.error?
68
+ end
69
+
70
+ #---------------------------------------------------------------------------------------------------------
71
+ # connection callbacks
72
+ #.........................................................................................................
73
+ def receive(stanza)
74
+ AgentXmpp.logger.info "RECV: #{stanza.to_s}"
75
+ result = if stanza.kind_of?(Xmpp::Stanza) and stanza.id and blk = id_callbacks[stanza.id]
76
+ id_callbacks.delete(stanza.id)
77
+ blk.call(stanza)
78
+ else
79
+ process_stanza(stanza)
80
+ end
81
+ send_resp(result)
82
+ end
83
+
84
+ #.........................................................................................................
85
+ def connection_completed
86
+ Boot.call_if_implemented(:call_after_connected, self)
87
+ broadcast_to_delegates(:did_connect, self)
88
+ init_connection.collect{|m| send(m)}
89
+ end
90
+
91
+ #.........................................................................................................
92
+ def unbind
93
+ @connection_status = :off_line
94
+ broadcast_to_delegates(:did_disconnect, self)
95
+ end
96
+
97
+ private
98
+
99
+ #.........................................................................................................
100
+ def process_stanza(stanza)
101
+ case stanza.name
102
+ when 'features'
103
+ set_stream_features_and_mechanisms(stanza)
104
+ if connection_status.eql?(:offline)
105
+ broadcast_to_delegates(:did_receive_preauthenticate_features, self)
106
+ elsif connection_status.eql?(:authenticated)
107
+ broadcast_to_delegates(:did_receive_postauthenticate_features, self)
108
+ end
109
+ when 'stream'
110
+ when 'success'
111
+ if connection_status.eql?(:offline)
112
+ @connection.reset_parser
113
+ @connection_status = :authenticated
114
+ broadcast_to_delegates(:did_authenticate, self)
115
+ init_connection(false)
116
+ end
117
+ when 'failure'
118
+ if connection_status.eql?(:offline)
119
+ @connection.reset_parser
120
+ broadcast_to_delegates(:did_not_authenticate, self)
121
+ end
122
+ else
123
+ demux_stanza(stanza)
124
+ end
125
+ end
126
+
127
+ #.........................................................................................................
128
+ def demux_stanza(stanza)
129
+ meth = 'did_receive_' + if stanza.class.eql?(AgentXmpp::Xmpp::Iq)
130
+ iqclass = if stanza.query
131
+ stanza.query.class
132
+ elsif stanza.command
133
+ stanza.command.class
134
+ else
135
+ nil
136
+ end
137
+ if iqclass
138
+ /.*::Iq(.*)/.match(iqclass.to_s).to_a.last
139
+ else
140
+ 'fail'
141
+ end
142
+ else
143
+ /.*::(.*)/.match(stanza.class.to_s).to_a.last
144
+ end.downcase
145
+ meth += '_' + stanza.type.to_s if stanza.type
146
+ if delegates_respond_to?(meth.to_sym)
147
+ broadcast_to_delegates(meth.to_sym, self, stanza)
148
+ else
149
+ broadcast_to_delegates(:did_receive_unsupported_message, self, stanza)
150
+ end
151
+ end
152
+
153
+ #.........................................................................................................
154
+ def set_stream_features_and_mechanisms(stanza)
155
+ @stream_features, @stream_mechanisms = {}, []
156
+ stanza.elements.each do |e|
157
+ if e.name == 'mechanisms' and e.namespace == 'urn:ietf:params:xml:ns:xmpp-sasl'
158
+ e.each_element('mechanism') {|mech| @stream_mechanisms.push(mech.text)}
159
+ else
160
+ @stream_features[e.name] = e.namespace
161
+ end
162
+ end
163
+ end
164
+
165
+ #.........................................................................................................
166
+ def init_connection(starting = true)
167
+ msg = []
168
+ msg.push(Send("<?xml version='1.0' ?>")) if starting
169
+ msg.push(Send("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='#{AgentXmpp.jid.domain}'>"))
170
+ end
171
+
172
+ #### MessagePipe
173
+ end
174
+
175
+ #### AgentXmpp
176
+ end
@@ -0,0 +1,65 @@
1
+ ##############################################################################################################
2
+ module AgentXmpp
3
+
4
+ #####-------------------------------------------------------------------------------------------------------
5
+ class PublishModel
6
+
7
+ #.........................................................................................................
8
+ def initialize(items)
9
+ @items = items || []
10
+ end
11
+
12
+ #.........................................................................................................
13
+ def find_all
14
+ @items.collect{|i| PublishNode.new(i)}
15
+ end
16
+
17
+ #.........................................................................................................
18
+ def find_by_node(node)
19
+ item = @items.select{|i| node.split('/').last.eql?(i['node'])}.first
20
+ item.nil? ? nil : PublishNode.new(item)
21
+ end
22
+
23
+ #.........................................................................................................
24
+ def delete_by_node(node)
25
+ @items.delete_if{|i| node.split('/').last.eql?(i['node'])}
26
+ end
27
+
28
+ #### PublishModel
29
+ end
30
+
31
+ #####-------------------------------------------------------------------------------------------------------
32
+ class PublishNode
33
+
34
+ #.........................................................................................................
35
+ attr_reader :node, :status, :title, :access_model, :publish_model, :send_last_published_item, :max_items,
36
+ :max_payload_size, :deliver_notifications, :deliver_payloads, :persist_items,
37
+ :subscribe, :presence_based_delivery, :notify_config, :notify_delete, :notify_retract,
38
+ :notify_sub
39
+
40
+ #.........................................................................................................
41
+ def initialize(pub)
42
+ @node = pub['node']
43
+ @status = pub['status']
44
+ @title = pub['title']
45
+ @access_model = pub['access_model']
46
+ @publish_model = pub['publish_model']
47
+ @send_last_published_item = pub['send_last_published_item']
48
+ @max_items = pub['max_items']
49
+ @max_payload_size = pub['max_payload_size']
50
+ @deliver_notifications = pub['deliver_notifications']
51
+ @deliver_payloads = pub['deliver_payloads']
52
+ @persist_items = pub['persist_items']
53
+ @subscribe = pub['subscribe']
54
+ @presence_based_delivery = pub['presence_based_delivery']
55
+ @notify_config = pub['notify_config']
56
+ @notify_delete = pub['notify_delete']
57
+ @notify_retract = pub['notify_retract']
58
+ @notify_sub = pub['notify_sub']
59
+ end
60
+
61
+ #### PublishNode
62
+ end
63
+
64
+ #### AgentXmpp
65
+ end
@@ -1,40 +1,35 @@
1
+ ##############################################################################################################
2
+ def Send(msg, &blk)
3
+ AgentXmpp::Response.new(msg, &blk)
4
+ end
5
+
1
6
  ##############################################################################################################
2
7
  module AgentXmpp
3
-
8
+
4
9
  #####-------------------------------------------------------------------------------------------------------
5
- class Format
6
-
7
- #---------------------------------------------------------------------------------------------------------
8
- attr_accessor :xmlns
9
- #---------------------------------------------------------------------------------------------------------
10
+ class Response
10
11
 
11
12
  #.........................................................................................................
12
- def initialize(xmlns)
13
- @xmlns = xmlns
14
- end
13
+ attr_reader :text, :message, :responds_with
15
14
 
16
15
  #.........................................................................................................
17
- def x_data(&blk)
18
- call_block_for_xmlns('jabber:x:data', &blk)
16
+ def initialize(msg, &blk)
17
+ @message = msg
18
+ @text = msg.to_s
19
+ @responds_with = blk
19
20
  end
20
21
 
21
22
  #.........................................................................................................
22
- def json(&blk)
23
- call_block_for_xmlns('jabber:x:json', &blk)
23
+ def to_s
24
+ text
24
25
  end
25
-
26
- private
27
-
26
+
28
27
  #.........................................................................................................
29
- def call_block_for_xmlns(check_xmlns, &blk)
30
- if xmlns.eql?(check_xmlns)
31
- if blk
32
- blk.call
33
- end
34
- end
28
+ def method_missing(meth, *args, &blk)
29
+ text.send(meth, *args, &blk)
35
30
  end
36
-
37
- #### Format
31
+
32
+ #### Message
38
33
  end
39
34
 
40
35
  #### AgentXmpp
@@ -0,0 +1,139 @@
1
+ ##############################################################################################################
2
+ module AgentXmpp
3
+
4
+ #####-------------------------------------------------------------------------------------------------------
5
+ class RosterModel
6
+
7
+ #.........................................................................................................
8
+ def initialize(jid, roster)
9
+ @items = {}
10
+ roster.each{|r| @items[r['jid']] = {:status => :inactive, :resources => {},
11
+ :groups => r['groups'].nil? ? [] : r['groups'].uniq, :jid => r['jid']}} if roster
12
+ @items[jid.bare.to_s] = {:status => :both, :resources => {}, :groups => [],}
13
+ @items[jid.domain] = {:status => :host, :resources => {jid.domain => {}}, :groups => []}
14
+ end
15
+
16
+ #.........................................................................................................
17
+ def has_jid?(jid)
18
+ @items.has_key?(jid.bare.to_s)
19
+ end
20
+
21
+ #.........................................................................................................
22
+ def find_all
23
+ @items.values.collect{|r| RosterItemModel.new(r)}
24
+ end
25
+
26
+ #.........................................................................................................
27
+ def find_by_jid(jid)
28
+ @items[jid.bare.to_s].nil? ? nil : RosterItemModel.new(@items[jid.bare.to_s])
29
+ end
30
+
31
+ #.........................................................................................................
32
+ def find_all_by_status(status)
33
+ @items.select{|j,r| r[:status].eql?(status)}.map{|j,r| RosterItemModel.new(r)}
34
+ end
35
+
36
+ #.........................................................................................................
37
+ def find_all_by_group(group)
38
+ @items.select{|j,r| r[:group].include?(group)}.map{|j,r| RosterItemModel.new(r)}
39
+ end
40
+
41
+ #.........................................................................................................
42
+ def destroy_by_jid(jid)
43
+ @items.delete(jid.bare.to_s)
44
+ end
45
+
46
+ #.........................................................................................................
47
+ def has_resource?(jid)
48
+ not @items[jid.bare.to_s][:resources][jid.to_s].nil?
49
+ end
50
+
51
+ #.........................................................................................................
52
+ def resources(jid)
53
+ @items[jid.bare.to_s][:resources]
54
+ end
55
+
56
+ #.........................................................................................................
57
+ def available_resources(jid)
58
+ if @items[jid.bare.to_s] and @items[jid.bare.to_s][:resources]
59
+ @items[jid.bare.to_s][:resources].inject([]) {|r,(j,h)| h[:presence].status.nil? ? r << j : r}
60
+ else; []; end
61
+ end
62
+
63
+ #.........................................................................................................
64
+ def resource(jid)
65
+ if @items[jid.bare.to_s] and @items[jid.bare.to_s][:resources][jid.to_s]
66
+ @items[jid.bare.to_s][:resources][jid.to_s][:presence]
67
+ else; nil; end
68
+ end
69
+
70
+ #.........................................................................................................
71
+ def version(jid)
72
+ if @items[jid.bare.to_s] and @items[jid.bare.to_s][:resources][jid.to_s]
73
+ @items[jid.bare.to_s][:resources][jid.to_s][:version]
74
+ else; nil; end
75
+ end
76
+
77
+ #.........................................................................................................
78
+ def has_version?(jid)
79
+ if @items[jid.bare.to_s] and @items[jid.bare.to_s][:resources][jid.to_s]
80
+ not @items[jid.bare.to_s][:resources][jid.to_s][:version].nil?
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ #.........................................................................................................
87
+ def update_status(jid, status)
88
+ @items[jid.bare.to_s][:status] = status
89
+ end
90
+
91
+ #.........................................................................................................
92
+ def update_roster_item(roster_item)
93
+ @items[roster_item.jid.to_s][:roster_item] = roster_item
94
+ end
95
+
96
+ #.........................................................................................................
97
+ def update_resource(presence)
98
+ from_jid = presence.from.to_s
99
+ from_bare_jid = presence.from.bare.to_s
100
+ @items[from_bare_jid][:resources][from_jid] ||= {}
101
+ @items[from_bare_jid][:resources][from_jid][:presence] = presence
102
+ end
103
+
104
+ #.........................................................................................................
105
+ def update_resource_version(version)
106
+ from_jid = version.from.to_s
107
+ from_bare_jid = version.from.bare.to_s
108
+ if @items[from_bare_jid][:resources][from_jid]
109
+ @items[from_bare_jid][:resources][from_jid][:version] = version.query
110
+ end
111
+ end
112
+
113
+ #.........................................................................................................
114
+ def method_missing(meth, *args, &blk)
115
+ @items.send(meth, *args, &blk)
116
+ end
117
+
118
+ #### RosterModel
119
+ end
120
+
121
+ #####-------------------------------------------------------------------------------------------------------
122
+ class RosterItemModel
123
+
124
+ #.........................................................................................................
125
+ attr_reader :status, :groups, :jid, :priority
126
+
127
+ #.........................................................................................................
128
+ def initialize(item)
129
+ @status = item[:status]
130
+ @groups = item[:groups]
131
+ @jid = item[:jid]
132
+ @priority = item[:priority]
133
+ end
134
+
135
+ #### RosterItemModel
136
+ end
137
+
138
+ #### AgentXmpp
139
+ end
@@ -0,0 +1,97 @@
1
+ ##############################################################################################################
2
+ module AgentXmpp
3
+
4
+ #####-------------------------------------------------------------------------------------------------------
5
+ class ServicesModel
6
+
7
+ #.........................................................................................................
8
+ def initialize
9
+ @service = Hash.new{|hash, key| hash[key] = {}}
10
+ end
11
+
12
+ #.........................................................................................................
13
+ def find_all_by_category(cat)
14
+ @service.select{|(k,v)| not v[:discoinfo].identities.select{|i| i.category.eql?(cat)}.empty?}
15
+ end
16
+
17
+ #.........................................................................................................
18
+ def find_all_by_category_and_type(cat)
19
+ @service.select{|(k,v)| not v[:discoinfo].identities.select{|i| i.category.eql?(cat) and i.type.eql?(type)}.empty?}
20
+ end
21
+
22
+ #.........................................................................................................
23
+ def create(jid)
24
+ @service[jid.to_s] = {}
25
+ end
26
+
27
+ #.........................................................................................................
28
+ def has_jid?(jid)
29
+ @service.has_key?(jid.to_s)
30
+ end
31
+
32
+ #.........................................................................................................
33
+ def has_feature?(jid, feature)
34
+ not features(jid.to_s).select{|f| f.var.eql?(feature)}.empty?
35
+ end
36
+
37
+ #.........................................................................................................
38
+ def has_item?(jid, ijid)
39
+ not items(jid.to_s).select{|i| i.jid.eql?(i.jid)}.empty?
40
+ end
41
+
42
+ #.........................................................................................................
43
+ def identities(jid, node=nil)
44
+ if has_jid?(jid.to_s)
45
+ node.nil ? @service[from_jid][:discoinfo].identities : @service[from_jid][:node][node][:discoinfo].identities
46
+ else; []; end
47
+ end
48
+
49
+ #.........................................................................................................
50
+ def features(jid, node=nil)
51
+ if has_jid?(jid.to_s)
52
+ node.nil ? @service[from_jid][:discoinfo].features : @service[from_jid][:node][node][:discoinfo].features
53
+ else; []; end
54
+ end
55
+
56
+ #.........................................................................................................
57
+ def items(jid, node=nil)
58
+ if has_jid?(jid.to_s)
59
+ node.nil ? @service[from_jid][:discoitems].items : @service[from_jid][:node][node][:discoitems].items
60
+ else; []; end
61
+ end
62
+
63
+ #.........................................................................................................
64
+ def update_with_discoinfo(disco)
65
+ save_item_disco(:discoinfo, disco)
66
+ end
67
+
68
+ #.........................................................................................................
69
+ def update_with_discoitems(disco)
70
+ save_item_disco(:discoitems, disco)
71
+ end
72
+
73
+ #.........................................................................................................
74
+ def method_missing(meth, *args, &blk)
75
+ @service.send(meth, *args, &blk)
76
+ end
77
+
78
+ private
79
+
80
+ #.........................................................................................................
81
+ def save_item_disco(item, disco)
82
+ from_jid = disco.from.to_s
83
+ node = disco.query.node
84
+ unless node
85
+ @service[from_jid][:node] = {} if @service[from_jid][:node].nil?
86
+ @service[from_jid][:node][node] = {} if @service[from_jid][:node][node].nil?
87
+ @service[from_jid][:node][node][item] = disco.query
88
+ else
89
+ @service[from_jid][item] = disco.query
90
+ end
91
+ end
92
+
93
+ #### ServicesModel
94
+ end
95
+
96
+ #### AgentXmpp
97
+ end