blather 0.4.15 → 0.4.16
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.
- data/.autotest +13 -0
- data/.gitignore +18 -0
- data/CHANGELOG +150 -0
- data/Gemfile +4 -0
- data/Rakefile +32 -0
- data/TODO.md +2 -0
- data/blather.gemspec +49 -0
- data/lib/blather.rb +3 -0
- data/lib/blather/client/client.rb +139 -1
- data/lib/blather/client/dsl.rb +16 -0
- data/lib/blather/core_ext/active_support.rb +1 -1
- data/lib/blather/core_ext/ipaddr.rb +19 -0
- data/lib/blather/file_transfer/s5b.rb +8 -0
- data/lib/blather/roster_item.rb +5 -2
- data/lib/blather/stanza/disco.rb +7 -0
- data/lib/blather/stanza/disco/disco_info.rb +13 -19
- data/lib/blather/stanza/disco/disco_items.rb +5 -11
- data/lib/blather/stanza/presence.rb +15 -5
- data/lib/blather/stanza/presence/c.rb +103 -0
- data/lib/blather/stanza/x.rb +2 -4
- data/lib/blather/version.rb +3 -0
- data/lib/blather/xmpp_node.rb +8 -0
- data/spec/blather/client/client_spec.rb +87 -1
- data/spec/blather/client/dsl/pubsub_spec.rb +2 -2
- data/spec/blather/client/dsl_spec.rb +85 -5
- data/spec/blather/core_ext/nokogiri_spec.rb +1 -1
- data/spec/blather/errors/sasl_error_spec.rb +1 -1
- data/spec/blather/errors/stanza_error_spec.rb +2 -2
- data/spec/blather/errors/stream_error_spec.rb +2 -2
- data/spec/blather/errors_spec.rb +1 -1
- data/spec/blather/file_transfer_spec.rb +2 -2
- data/spec/blather/jid_spec.rb +1 -1
- data/spec/blather/roster_item_spec.rb +1 -1
- data/spec/blather/roster_spec.rb +1 -1
- data/spec/blather/stanza/discos/disco_info_spec.rb +1 -11
- data/spec/blather/stanza/discos/disco_items_spec.rb +1 -6
- data/spec/blather/stanza/iq/command_spec.rb +3 -3
- data/spec/blather/stanza/iq/ibb_spec.rb +2 -2
- data/spec/blather/stanza/iq/query_spec.rb +1 -1
- data/spec/blather/stanza/iq/roster_spec.rb +1 -1
- data/spec/blather/stanza/iq/s5b_spec.rb +2 -2
- data/spec/blather/stanza/iq/si_spec.rb +2 -2
- data/spec/blather/stanza/iq/vcard_spec.rb +3 -3
- data/spec/blather/stanza/iq_spec.rb +1 -1
- data/spec/blather/stanza/message_spec.rb +2 -2
- data/spec/blather/stanza/presence/c_spec.rb +46 -0
- data/spec/blather/stanza/presence/status_spec.rb +1 -1
- data/spec/blather/stanza/presence/subscription_spec.rb +1 -1
- data/spec/blather/stanza/presence_spec.rb +1 -1
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +2 -2
- data/spec/blather/stanza/pubsub/create_spec.rb +3 -3
- data/spec/blather/stanza/pubsub/event_spec.rb +2 -2
- data/spec/blather/stanza/pubsub/items_spec.rb +2 -2
- data/spec/blather/stanza/pubsub/publish_spec.rb +2 -2
- data/spec/blather/stanza/pubsub/retract_spec.rb +2 -2
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +2 -2
- data/spec/blather/stanza/pubsub/subscription_spec.rb +3 -3
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +2 -2
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +2 -2
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +2 -2
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +3 -3
- data/spec/blather/stanza/pubsub_owner_spec.rb +2 -2
- data/spec/blather/stanza/pubsub_spec.rb +2 -8
- data/spec/blather/stanza/x_spec.rb +1 -6
- data/spec/blather/stanza_spec.rb +1 -1
- data/spec/blather/stream/client_spec.rb +3 -3
- data/spec/blather/stream/component_spec.rb +1 -1
- data/spec/blather/stream/parser_spec.rb +1 -1
- data/spec/blather/xmpp_node_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -12
- data/yard/templates/default/class/html/handlers.erb +18 -0
- data/yard/templates/default/class/setup.rb +10 -0
- data/yard/templates/default/class/text/handlers.erb +1 -0
- metadata +123 -13
- data/lib/test.rb +0 -55
data/lib/blather/client/dsl.rb
CHANGED
@@ -252,6 +252,22 @@ module Blather
|
|
252
252
|
client.write stanza
|
253
253
|
end
|
254
254
|
|
255
|
+
def set_caps(node, identities, features)
|
256
|
+
client.caps.node = node
|
257
|
+
client.caps.identities = identities
|
258
|
+
client.caps.features = features
|
259
|
+
end
|
260
|
+
|
261
|
+
def send_caps
|
262
|
+
client.register_handler :disco_info, :type => :get, :node => client.caps.node do |s|
|
263
|
+
r = client.caps.dup
|
264
|
+
r.to = s.from
|
265
|
+
r.id = s.id
|
266
|
+
client.write r
|
267
|
+
end
|
268
|
+
client.write client.caps.c
|
269
|
+
end
|
270
|
+
|
255
271
|
# Generate a method for every stanza handler that exists.
|
256
272
|
Blather::Stanza.handler_list.each do |handler_name|
|
257
273
|
module_eval <<-METHOD, __FILE__, __LINE__
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class IPAddr
|
2
|
+
PrivateRanges = [
|
3
|
+
IPAddr.new("10.0.0.0/8"),
|
4
|
+
IPAddr.new("172.16.0.0/12"),
|
5
|
+
IPAddr.new("192.168.0.0/16")
|
6
|
+
]
|
7
|
+
|
8
|
+
def private?
|
9
|
+
return false unless self.ipv4?
|
10
|
+
PrivateRanges.each do |ipr|
|
11
|
+
return true if ipr.include?(self)
|
12
|
+
end
|
13
|
+
return false
|
14
|
+
end
|
15
|
+
|
16
|
+
def public?
|
17
|
+
!private?
|
18
|
+
end
|
19
|
+
end
|
@@ -7,10 +7,17 @@ module Blather
|
|
7
7
|
# Set this to false if you don't want to fallback to In-Band Bytestreams
|
8
8
|
attr_accessor :allow_ibb_fallback
|
9
9
|
|
10
|
+
# Set this to true if the buddies of your bot will be in the same local network
|
11
|
+
#
|
12
|
+
# Usually IM clients advertise all network addresses which they can determine.
|
13
|
+
# Skipping the local ones can save time if your bot is not in the same local network as it's buddies
|
14
|
+
attr_accessor :allow_private_ips
|
15
|
+
|
10
16
|
def initialize(stream, iq)
|
11
17
|
@stream = stream
|
12
18
|
@iq = iq
|
13
19
|
@allow_ibb_fallback = true
|
20
|
+
@allow_private_ips = false
|
14
21
|
end
|
15
22
|
|
16
23
|
# Accept an incoming file-transfer
|
@@ -19,6 +26,7 @@ module Blather
|
|
19
26
|
# @param [Array] params the params to be passed into the handler
|
20
27
|
def accept(handler, *params)
|
21
28
|
@streamhosts = @iq.streamhosts
|
29
|
+
@streamhosts.delete_if {|s| begin IPAddr.new(s.host).private? rescue false end } unless @allow_private_ips
|
22
30
|
@socket_address = Digest::SHA1.hexdigest("#{@iq.sid}#{@iq.from}#{@iq.to}")
|
23
31
|
|
24
32
|
@handler = handler
|
data/lib/blather/roster_item.rb
CHANGED
@@ -122,8 +122,11 @@ module Blather
|
|
122
122
|
self.jid.to_s <=> o.jid.to_s
|
123
123
|
end
|
124
124
|
|
125
|
-
|
126
|
-
|
125
|
+
# Compare two RosterItem objects by name, type and category
|
126
|
+
# @param [RosterItem] o the Identity object to compare against
|
127
|
+
# @return [true, false]
|
128
|
+
def eql?(o, *fields)
|
129
|
+
o.is_a?(self.class) &&
|
127
130
|
o.jid == self.jid &&
|
128
131
|
o.groups == self.groups
|
129
132
|
end
|
data/lib/blather/stanza/disco.rb
CHANGED
@@ -19,6 +19,13 @@ class Stanza
|
|
19
19
|
def node=(node)
|
20
20
|
query[:node] = node
|
21
21
|
end
|
22
|
+
|
23
|
+
# Compare two Disco objects by name, type and category
|
24
|
+
# @param [Disco] o the Identity object to compare against
|
25
|
+
# @return [true, false]
|
26
|
+
def eql?(o, *fields)
|
27
|
+
super o, *(fields + [:node])
|
28
|
+
end
|
22
29
|
end
|
23
30
|
|
24
31
|
end # Stanza
|
@@ -59,6 +59,13 @@ class Stanza
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
# Compare two DiscoInfo objects by name, type and category
|
63
|
+
# @param [DiscoInfo] o the Identity object to compare against
|
64
|
+
# @return [true, false]
|
65
|
+
def eql?(o, *fields)
|
66
|
+
super o, *(fields + [:identities, :features])
|
67
|
+
end
|
68
|
+
|
62
69
|
class Identity < XMPPNode
|
63
70
|
# Create a new DiscoInfo Identity
|
64
71
|
# @overload new(node)
|
@@ -146,17 +153,9 @@ class Stanza
|
|
146
153
|
# Compare two Identity objects by name, type and category
|
147
154
|
# @param [DiscoInfo::Identity] o the Identity object to compare against
|
148
155
|
# @return [true, false]
|
149
|
-
def eql?(o)
|
150
|
-
|
151
|
-
raise "Cannot compare #{self.class} with #{o.class}"
|
152
|
-
end
|
153
|
-
|
154
|
-
o.name == self.name &&
|
155
|
-
o.type == self.type &&
|
156
|
-
o.category == self.category &&
|
157
|
-
o.xml_lang == self.xml_lang
|
156
|
+
def eql?(o, *fields)
|
157
|
+
super o, *(fields + [:name, :type, :category, :xml_lang])
|
158
158
|
end
|
159
|
-
alias_method :==, :eql?
|
160
159
|
end # Identity
|
161
160
|
|
162
161
|
class Feature < XMPPNode
|
@@ -191,17 +190,12 @@ class Stanza
|
|
191
190
|
write_attr :var, var
|
192
191
|
end
|
193
192
|
|
194
|
-
# Compare two Feature objects by
|
195
|
-
# @param [DiscoInfo::Feature] o the
|
193
|
+
# Compare two DiscoInfo::Feature objects by name, type and category
|
194
|
+
# @param [DiscoInfo::Feature] o the Identity object to compare against
|
196
195
|
# @return [true, false]
|
197
|
-
def eql?(o)
|
198
|
-
|
199
|
-
raise "Cannot compare #{self.class} with #{o.class}"
|
200
|
-
end
|
201
|
-
|
202
|
-
o.var == self.var
|
196
|
+
def eql?(o, *fields)
|
197
|
+
super o, *(fields + [:var])
|
203
198
|
end
|
204
|
-
alias_method :==, :eql?
|
205
199
|
end
|
206
200
|
end # Feature
|
207
201
|
|
@@ -121,19 +121,13 @@ class Stanza
|
|
121
121
|
write_attr :name, name
|
122
122
|
end
|
123
123
|
|
124
|
-
# Check for equality based on jid, node, and name
|
125
|
-
#
|
126
|
-
# @param [Blather::Stanza::DiscoItems::Item] o the other Item
|
127
|
-
def eql?(o)
|
128
|
-
unless o.is_a?(self.class)
|
129
|
-
raise "Cannot compare #{self.class} with #{o.class}"
|
130
|
-
end
|
131
124
|
|
132
|
-
|
133
|
-
|
134
|
-
|
125
|
+
# Compare two DiscoItems::Item objects by name, type and category
|
126
|
+
# @param [DiscoItems::Item] o the Identity object to compare against
|
127
|
+
# @return [true, false]
|
128
|
+
def eql?(o, *fields)
|
129
|
+
super o, *(fields + [:jid, :node, :name])
|
135
130
|
end
|
136
|
-
alias_method :==, :eql?
|
137
131
|
end
|
138
132
|
end
|
139
133
|
|
@@ -85,12 +85,22 @@ class Stanza
|
|
85
85
|
# on the type attribute.
|
86
86
|
# If neither is found it instantiates a Presence object
|
87
87
|
def self.import(node) # :nodoc:
|
88
|
-
klass =
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
klass = nil
|
89
|
+
node.children.detect do |e|
|
90
|
+
ns = e.namespace ? e.namespace.href : nil
|
91
|
+
klass = class_from_registration(e.element_name, ns)
|
92
|
+
end
|
93
|
+
|
94
|
+
if klass && klass != self
|
95
|
+
klass.import(node)
|
96
|
+
else
|
97
|
+
klass = case node['type']
|
98
|
+
when nil, 'unavailable' then Status
|
99
|
+
when /subscribe/ then Subscription
|
100
|
+
else self
|
101
|
+
end
|
102
|
+
klass.new.inherit(node)
|
92
103
|
end
|
93
|
-
klass.new.inherit(node)
|
94
104
|
end
|
95
105
|
|
96
106
|
# Ensure element_name is "presence" for all subclasses
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stanza
|
3
|
+
class Presence
|
4
|
+
|
5
|
+
# # Entity Capabilities Stanza
|
6
|
+
#
|
7
|
+
# [XEP-0115 - Entity Capabilities](http://http://xmpp.org/extensions/xep-0115.html)
|
8
|
+
#
|
9
|
+
# Blather handles c nodes through this class. It provides a set of helper methods
|
10
|
+
# to quickly deal with capabilites presence stanzas.
|
11
|
+
#
|
12
|
+
# @handler :c
|
13
|
+
class C < Presence
|
14
|
+
register :c, :c, 'http://jabber.org/protocol/caps'
|
15
|
+
|
16
|
+
VALID_HASH_TYPES = %w[md2 md5 sha-1 sha-224 sha-256 sha-384 sha-512].freeze
|
17
|
+
|
18
|
+
def self.new(node = nil, ver = nil, hash = 'sha-1')
|
19
|
+
new_node = super()
|
20
|
+
new_node.c
|
21
|
+
new_node.hash = hash
|
22
|
+
new_node.node = node
|
23
|
+
new_node.ver = ver
|
24
|
+
new_node
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.import(node)
|
28
|
+
self.new.inherit(node)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private
|
32
|
+
def inherit(node)
|
33
|
+
inherit_attrs node.attributes
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get the name of the node
|
38
|
+
#
|
39
|
+
# @return [String, nil]
|
40
|
+
def node
|
41
|
+
c[:node]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set the name of the node
|
45
|
+
#
|
46
|
+
# @param [String, nil] node the new node name
|
47
|
+
def node=(node)
|
48
|
+
c[:node] = node
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the name of the hash
|
52
|
+
#
|
53
|
+
# @return [Symbol, nil]
|
54
|
+
def hash
|
55
|
+
c[:hash].to_sym
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set the name of the hash
|
59
|
+
#
|
60
|
+
# @param [String, nil] hash the new hash name
|
61
|
+
def hash=(hash)
|
62
|
+
if hash && !VALID_HASH_TYPES.include?(hash.to_s)
|
63
|
+
raise ArgumentError, "Invalid Hash Type (#{hash}), use: #{VALID_HASH_TYPES*' '}"
|
64
|
+
end
|
65
|
+
c[:hash] = hash
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the ver
|
69
|
+
#
|
70
|
+
# @return [String, nil]
|
71
|
+
def ver
|
72
|
+
c[:ver]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Set the ver
|
76
|
+
#
|
77
|
+
# @param [String, nil] ver the new ver
|
78
|
+
def ver=(ver)
|
79
|
+
c[:ver] = ver
|
80
|
+
end
|
81
|
+
|
82
|
+
# C node accessor
|
83
|
+
# If a c node exists it will be returned.
|
84
|
+
# Otherwise a new node will be created and returned
|
85
|
+
#
|
86
|
+
# @return [Blather::XMPPNode]
|
87
|
+
def c
|
88
|
+
c = if self.class.registered_ns
|
89
|
+
find_first('ns:c', :ns => self.class.registered_ns)
|
90
|
+
else
|
91
|
+
find_first('c')
|
92
|
+
end
|
93
|
+
|
94
|
+
unless c
|
95
|
+
(self << (c = XMPPNode.new('c', self.document)))
|
96
|
+
c.namespace = self.class.registered_ns
|
97
|
+
end
|
98
|
+
c
|
99
|
+
end
|
100
|
+
end # C
|
101
|
+
end #Presence
|
102
|
+
end #Stanza
|
103
|
+
end
|
data/lib/blather/stanza/x.rb
CHANGED
@@ -331,11 +331,9 @@ class Stanza
|
|
331
331
|
# Compare two Field objects by type, var and label
|
332
332
|
# @param [X::Field] o the Field object to compare against
|
333
333
|
# @return [true, false]
|
334
|
-
def eql?(o)
|
335
|
-
|
336
|
-
![:type, :var, :label, :desc, :required?, :value].detect { |m| o.send(m) != self.send(m) }
|
334
|
+
def eql?(o, *fields)
|
335
|
+
super o, *(fields + [:type, :var, :label, :desc, :required?, :value])
|
337
336
|
end
|
338
|
-
alias_method :==, :eql?
|
339
337
|
|
340
338
|
class Option < XMPPNode
|
341
339
|
register :option, 'jabber:x:data'
|
data/lib/blather/xmpp_node.rb
CHANGED
@@ -213,6 +213,14 @@ module Blather
|
|
213
213
|
def inspect
|
214
214
|
self.to_xml
|
215
215
|
end
|
216
|
+
|
217
|
+
def eql?(o, *fields)
|
218
|
+
o.is_a?(self.class) && fields.all? { |f| self.__send__(f) == o.__send__(f) }
|
219
|
+
end
|
220
|
+
|
221
|
+
def ==(o)
|
222
|
+
eql?(o)
|
223
|
+
end
|
216
224
|
end # XMPPNode
|
217
225
|
|
218
226
|
end # Blather
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'blather/client/client'
|
3
3
|
|
4
4
|
describe Blather::Client do
|
@@ -26,6 +26,11 @@ describe Blather::Client do
|
|
26
26
|
@client.status.must_equal :away
|
27
27
|
end
|
28
28
|
|
29
|
+
it 'should have a caps handler' do
|
30
|
+
@client.must_respond_to :caps
|
31
|
+
@client.caps.must_be_kind_of Blather::Client::Caps
|
32
|
+
end
|
33
|
+
|
29
34
|
it 'can be setup' do
|
30
35
|
@client.must_respond_to :setup
|
31
36
|
@client.setup('me@me.com', 'pass').must_equal @client
|
@@ -571,3 +576,84 @@ describe 'Blather::Client guards' do
|
|
571
576
|
lambda { @client.register_handler(:iq, 0) {} }.must_raise RuntimeError
|
572
577
|
end
|
573
578
|
end
|
579
|
+
|
580
|
+
describe 'Blather::Client::Caps' do
|
581
|
+
before do
|
582
|
+
@client = Blather::Client.new
|
583
|
+
@stream = mock()
|
584
|
+
@stream.stubs(:send)
|
585
|
+
@client.post_init @stream, Blather::JID.new('n@d/r')
|
586
|
+
@caps = @client.caps
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'must be of type result' do
|
590
|
+
@caps.must_respond_to :type
|
591
|
+
@caps.type.must_equal :result
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'can have a client node set' do
|
595
|
+
@caps.must_respond_to :node=
|
596
|
+
@caps.node = "somenode"
|
597
|
+
end
|
598
|
+
|
599
|
+
it 'provides a client node reader' do
|
600
|
+
@caps.must_respond_to :node
|
601
|
+
@caps.node = "somenode"
|
602
|
+
@caps.node.must_equal "somenode##{@caps.ver}"
|
603
|
+
end
|
604
|
+
|
605
|
+
it 'can have identities set' do
|
606
|
+
@caps.must_respond_to :identities=
|
607
|
+
@caps.identities = [{:name => "name", :type => "type", :category => "cat"}]
|
608
|
+
end
|
609
|
+
|
610
|
+
it 'provides an identities reader' do
|
611
|
+
@caps.must_respond_to :identities
|
612
|
+
@caps.identities = [{:name => "name", :type => "type", :category => "cat"}]
|
613
|
+
@caps.identities.must_equal [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => "name", :type => "type", :category => "cat"})]
|
614
|
+
end
|
615
|
+
|
616
|
+
it 'can have features set' do
|
617
|
+
@caps.must_respond_to :features=
|
618
|
+
@caps.features.size.must_equal 0
|
619
|
+
@caps.features = ["feature1"]
|
620
|
+
@caps.features.size.must_equal 1
|
621
|
+
@caps.features += [Blather::Stanza::Iq::DiscoInfo::Feature.new("feature2")]
|
622
|
+
@caps.features.size.must_equal 2
|
623
|
+
@caps.features = nil
|
624
|
+
@caps.features.size.must_equal 0
|
625
|
+
end
|
626
|
+
|
627
|
+
it 'provides a features reader' do
|
628
|
+
@caps.must_respond_to :features
|
629
|
+
@caps.features = %w{feature1 feature2}
|
630
|
+
@caps.features.must_equal [Blather::Stanza::Iq::DiscoInfo::Feature.new("feature1"), Blather::Stanza::Iq::DiscoInfo::Feature.new("feature2")]
|
631
|
+
end
|
632
|
+
|
633
|
+
it 'provides a client ver reader' do
|
634
|
+
@caps.must_respond_to :ver
|
635
|
+
@caps.node = 'http://code.google.com/p/exodus'
|
636
|
+
@caps.identities = [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => 'Exodus 0.9.1', :type => 'pc', :category => 'client'})]
|
637
|
+
@caps.features = %w{
|
638
|
+
http://jabber.org/protocol/caps
|
639
|
+
http://jabber.org/protocol/disco#info
|
640
|
+
http://jabber.org/protocol/disco#items
|
641
|
+
http://jabber.org/protocol/muc
|
642
|
+
}
|
643
|
+
@caps.ver.must_equal 'QgayPKawpkPSDYmwT/WM94uAlu0='
|
644
|
+
@caps.node.must_equal "http://code.google.com/p/exodus#QgayPKawpkPSDYmwT/WM94uAlu0="
|
645
|
+
end
|
646
|
+
|
647
|
+
it 'can construct caps presence correctly' do
|
648
|
+
@caps.must_respond_to :c
|
649
|
+
@caps.node = 'http://code.google.com/p/exodus'
|
650
|
+
@caps.identities = [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => 'Exodus 0.9.1', :type => 'pc', :category => 'client'})]
|
651
|
+
@caps.features = %w{
|
652
|
+
http://jabber.org/protocol/caps
|
653
|
+
http://jabber.org/protocol/disco#info
|
654
|
+
http://jabber.org/protocol/disco#items
|
655
|
+
http://jabber.org/protocol/muc
|
656
|
+
}
|
657
|
+
@caps.c.inspect.must_equal "<presence>\n <c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"http://code.google.com/p/exodus\" ver=\"QgayPKawpkPSDYmwT/WM94uAlu0=\"/>\n</presence>"
|
658
|
+
end
|
659
|
+
end
|