cancer 0.1.0.a1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +3 -0
- data/.gitignore +5 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/cancer.gemspec +156 -0
- data/documentation/STREAM_INITIATION.markdown +18 -0
- data/examples/example.rb +80 -0
- data/examples/example_2.rb +20 -0
- data/examples/example_em.rb +11 -0
- data/examples/example_em_helper.rb +23 -0
- data/examples/example_im_roster.rb +26 -0
- data/examples/example_xep_0004.rb +124 -0
- data/examples/example_xep_0047.rb +35 -0
- data/examples/example_xep_0050.rb +78 -0
- data/examples/example_xep_0065.rb +66 -0
- data/examples/example_xep_0096.dup.rb +40 -0
- data/examples/example_xep_0096_xep_0047.rb +42 -0
- data/examples/example_xep_0096_xep_0065.rb +40 -0
- data/lib/cancer.rb +122 -0
- data/lib/cancer/adapter.rb +33 -0
- data/lib/cancer/adapters/em.rb +88 -0
- data/lib/cancer/adapters/socket.rb +122 -0
- data/lib/cancer/builder.rb +28 -0
- data/lib/cancer/controller.rb +32 -0
- data/lib/cancer/dependencies.rb +187 -0
- data/lib/cancer/events/abstract_event.rb +10 -0
- data/lib/cancer/events/base_matchers.rb +63 -0
- data/lib/cancer/events/binary_matchers.rb +30 -0
- data/lib/cancer/events/eventable.rb +92 -0
- data/lib/cancer/events/exception_events.rb +28 -0
- data/lib/cancer/events/handler.rb +33 -0
- data/lib/cancer/events/manager.rb +130 -0
- data/lib/cancer/events/named_events.rb +25 -0
- data/lib/cancer/events/proc_matcher.rb +16 -0
- data/lib/cancer/events/xml_events.rb +44 -0
- data/lib/cancer/exceptions.rb +53 -0
- data/lib/cancer/jid.rb +215 -0
- data/lib/cancer/lock.rb +32 -0
- data/lib/cancer/spec.rb +35 -0
- data/lib/cancer/spec/error.rb +18 -0
- data/lib/cancer/spec/extentions.rb +79 -0
- data/lib/cancer/spec/matcher.rb +30 -0
- data/lib/cancer/spec/mock_adapter.rb +139 -0
- data/lib/cancer/spec/mock_stream.rb +15 -0
- data/lib/cancer/spec/transcript.rb +107 -0
- data/lib/cancer/stream.rb +182 -0
- data/lib/cancer/stream/builder.rb +20 -0
- data/lib/cancer/stream/controller.rb +36 -0
- data/lib/cancer/stream/event_helper.rb +12 -0
- data/lib/cancer/stream/xep.rb +52 -0
- data/lib/cancer/stream_parser.rb +144 -0
- data/lib/cancer/support.rb +27 -0
- data/lib/cancer/support/hash.rb +32 -0
- data/lib/cancer/support/string.rb +22 -0
- data/lib/cancer/synchronized_stanza.rb +79 -0
- data/lib/cancer/thread_pool.rb +118 -0
- data/lib/cancer/xep.rb +43 -0
- data/lib/cancer/xeps/core.rb +109 -0
- data/lib/cancer/xeps/core/bind.rb +33 -0
- data/lib/cancer/xeps/core/sasl.rb +113 -0
- data/lib/cancer/xeps/core/session.rb +22 -0
- data/lib/cancer/xeps/core/stream.rb +18 -0
- data/lib/cancer/xeps/core/terminator.rb +21 -0
- data/lib/cancer/xeps/core/tls.rb +34 -0
- data/lib/cancer/xeps/im.rb +323 -0
- data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
- data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
- data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
- data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
- data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
- data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
- data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
- data/lib/cancer/xeps/xep_0095_si.rb +211 -0
- data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
- data/lib/cancer/xeps/xep_0114_component.rb +73 -0
- data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
- data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
- data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
- data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
- data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/stream/stanza_errors_spec.rb +47 -0
- data/spec/stream/stream_errors_spec.rb +38 -0
- data/spec/stream/stream_initialization_spec.rb +160 -0
- data/spec/xep_0050/local_spec.rb +165 -0
- data/spec/xep_0050/remote_spec.rb +44 -0
- metadata +200 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
module Cancer
|
3
|
+
# Jabber Component Protocol
|
4
|
+
# http://xmpp.org/extensions/xep-0114.html
|
5
|
+
module XEP_0114
|
6
|
+
include Cancer::XEP
|
7
|
+
|
8
|
+
dependency 'core'
|
9
|
+
dependency 'xep-0138', :weak => true
|
10
|
+
|
11
|
+
def self.enhance_stream(stream)
|
12
|
+
stream.extend_stream do
|
13
|
+
include Cancer::XEP_0114::Helpers
|
14
|
+
|
15
|
+
alias_method_chain :send_stream_header, :xep_0114
|
16
|
+
alias_method_chain :process_send_data, :xep_0114
|
17
|
+
alias_method_chain :process_received_data, :xep_0114
|
18
|
+
alias_method_chain :resolve_options, :xep_0114
|
19
|
+
end
|
20
|
+
|
21
|
+
stream.install_controller(Cancer::XEP_0114::Controller)
|
22
|
+
end
|
23
|
+
|
24
|
+
module Helpers
|
25
|
+
def send_stream_header_with_xep_0114
|
26
|
+
send %(<stream:stream xmlns="jabber:component:accept" xmlns:stream="#{STREAM_NS}" to="#{options[:jid].bare_jid}" version="1.0">)
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_send_data_with_xep_0114(data)
|
30
|
+
data = data.to_s
|
31
|
+
data.gsub!("jabber:client", "jabber:component:accept")
|
32
|
+
process_send_data_without_xep_0114(data)
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_received_data_with_xep_0114(data)
|
36
|
+
data = process_received_data_without_xep_0114(data)
|
37
|
+
data.gsub!("jabber:component:accept", "jabber:client") if data
|
38
|
+
data
|
39
|
+
end
|
40
|
+
|
41
|
+
def resolve_options_with_xep_0114(options)
|
42
|
+
options[:default_port] ||= 5347
|
43
|
+
options[:username] ||= options[:jid].bare_jid.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Controller < Cancer::Controller
|
48
|
+
|
49
|
+
priority 300
|
50
|
+
|
51
|
+
on { |e| e.xpath('/n:stream', 'n' => STREAM_NS) }
|
52
|
+
def send_handshake(e)
|
53
|
+
send do |x|
|
54
|
+
x.handshake { x.text Digest::SHA1.hexdigest(options[:session_id]+options[:password]) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# we use jabber:client here because we replace jabber:component:accept with jabber:client
|
59
|
+
on { |e| e.xpath('/c:handshake', 'c' => "jabber:client") }
|
60
|
+
def handshake_was_accespted
|
61
|
+
fire! Cancer::Events::NamedEvent, :initialized
|
62
|
+
initialization_completed!
|
63
|
+
end
|
64
|
+
|
65
|
+
on { |e| e.name(:initialized) }
|
66
|
+
def unregister_ourself
|
67
|
+
unregister!
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Cancer
|
6
|
+
# Entity Capabilities
|
7
|
+
# http://xmpp.org/extensions/xep-0115.html
|
8
|
+
module XEP_0115
|
9
|
+
include Cancer::XEP
|
10
|
+
|
11
|
+
dependency 'core'
|
12
|
+
dependency 'im'
|
13
|
+
dependency 'xep-0030'
|
14
|
+
|
15
|
+
NS = 'http://jabber.org/protocol/caps'
|
16
|
+
NAMESPACES = { 'c' => CLIENT_NS, 'caps' => NS, 'disco' => Cancer::XEP_0030::INFO_NS }
|
17
|
+
|
18
|
+
def self.enhance_stream(stream)
|
19
|
+
stream.extend_stream do
|
20
|
+
include Cancer::XEP_0115::StreamHelpers
|
21
|
+
end
|
22
|
+
stream.install_controller Cancer::XEP_0115::Controller
|
23
|
+
stream.disco.feature NS
|
24
|
+
|
25
|
+
stream.presence_enhancer_for :status do |x|
|
26
|
+
controller = self.controllers_by_name['Cancer::XEP_0115::Controller']
|
27
|
+
controller.cache_disco_data(self.disco)
|
28
|
+
x.c(:xmlns => NS, :hash => 'sha-1', :node => CLIENT,
|
29
|
+
:ver => controller.verification_code(self.disco))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module StreamHelpers
|
34
|
+
def send_capabilities(to=nil, item=self.disco)
|
35
|
+
controller = self.controllers_by_name['Cancer::XEP_0115::Controller']
|
36
|
+
send do |x|
|
37
|
+
|
38
|
+
controller.cache_disco_data(item)
|
39
|
+
|
40
|
+
x.presence(to ? {:to => to} : {}) do
|
41
|
+
x.c(:xmlns => NS, :hash => 'sha-1', :node => CLIENT, :ver => controller.verification_code(item))
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Controller < Cancer::Controller
|
49
|
+
|
50
|
+
def capabilities
|
51
|
+
@capabilities ||= {}
|
52
|
+
end
|
53
|
+
|
54
|
+
on(:priority => 5) { |e| e.xpath('/c:iq[@type="get"]/disco:query', NAMESPACES) }
|
55
|
+
def catch_capabilities_query(e)
|
56
|
+
query_node = e.xml.first('/c:iq/disco:query', NAMESPACES)
|
57
|
+
|
58
|
+
if (node = query_node[:node]) and (capabilities = self.capabilities[node.to_s])
|
59
|
+
|
60
|
+
send_iq(e.sender, :result) do |x|
|
61
|
+
x.query(:xmlns => Cancer::XEP_0030::INFO_NS, :node => node) do
|
62
|
+
|
63
|
+
capabilities.identities.each do |identity|
|
64
|
+
x.identity(identity)
|
65
|
+
end
|
66
|
+
|
67
|
+
capabilities.features.each do |feature|
|
68
|
+
x.feature(:var => feature)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
throw :halt
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
on { |e| e.xpath('/c:presence/caps:c', NAMESPACES) }
|
79
|
+
def received_caps(e)
|
80
|
+
caps = e.xml.first('/c:presence/caps:c', NAMESPACES)
|
81
|
+
self.peers[e.sender].client = caps[:node].to_s
|
82
|
+
self.peers[e.sender].capabilities_id = caps[:ver].to_s
|
83
|
+
|
84
|
+
capabilities_node = "#{caps[:node]}##{caps[:ver]}"
|
85
|
+
unless self.capabilities[capabilities_node]
|
86
|
+
item = disco_info_for(e.sender, capabilities_node)
|
87
|
+
item ||= disco_info_for(e.sender, capabilities_node)
|
88
|
+
if item and verify_disco_item(item, caps[:ver].to_s, caps[:hash].to_s)
|
89
|
+
self.capabilities[capabilities_node] = item
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
self.peers[e.sender].disco = self.capabilities[capabilities_node]
|
94
|
+
end
|
95
|
+
|
96
|
+
def cache_disco_data(item=self.disco, hash='sha-1')
|
97
|
+
verification_string = verification_code(item, hash)
|
98
|
+
capabilities_node = "#{CLIENT}##{verification_string}"
|
99
|
+
|
100
|
+
self.capabilities[capabilities_node] = item
|
101
|
+
|
102
|
+
item
|
103
|
+
end
|
104
|
+
|
105
|
+
def verification_code(item, hash='sha-1')
|
106
|
+
|
107
|
+
identities = item.identities.dup
|
108
|
+
features = item.features.dup
|
109
|
+
|
110
|
+
fields = [:category, :type, :lang, :name]
|
111
|
+
identities.sort! do |a,b|
|
112
|
+
cmp_result = nil
|
113
|
+
fields.each do |field|
|
114
|
+
value_a = a[field]
|
115
|
+
value_b = b[field]
|
116
|
+
|
117
|
+
if value_a != value_b
|
118
|
+
cmp_result = value_a.cmp_ioctet(value_b)
|
119
|
+
break
|
120
|
+
end
|
121
|
+
end
|
122
|
+
cmp_result
|
123
|
+
end
|
124
|
+
|
125
|
+
identities.collect! do |identity|
|
126
|
+
"#{identity[:category]}/#{identity[:type]}/#{identity[:lang]}/#{identity[:name]}<"
|
127
|
+
end
|
128
|
+
|
129
|
+
if identities.uniq.size < identities.size
|
130
|
+
raise InvalidVerificationString, "this is invalid disco info (duplicate identity)"
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
features.sort! do |a,b|
|
135
|
+
a.cmp_ioctet(b)
|
136
|
+
end
|
137
|
+
|
138
|
+
features.collect! { |f| f + "<" }
|
139
|
+
|
140
|
+
if features.uniq.size < features.size
|
141
|
+
raise InvalidVerificationString, "this is invalid disco info (duplicate feature)"
|
142
|
+
end
|
143
|
+
|
144
|
+
verification_string = (identities + features).join('')
|
145
|
+
|
146
|
+
digest_klass = case hash
|
147
|
+
# when 'md2' then nil
|
148
|
+
when 'md5' then Digest::MD5
|
149
|
+
when 'sha-1' then Digest::SHA1
|
150
|
+
# when 'sha-224' then nil
|
151
|
+
when 'sha-256' then Digest::SHA256
|
152
|
+
when 'sha-384' then Digest::SHA384
|
153
|
+
when 'sha-512' then Digest::SHA512
|
154
|
+
else raise UnknownHashMethod, "the hash method #{hash} is not supported"
|
155
|
+
end
|
156
|
+
|
157
|
+
verification_string = digest_klass.digest(verification_string)
|
158
|
+
|
159
|
+
return Base64.encode64(verification_string).gsub(/\s|\n/, '')
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
def verify_disco_item(item, received_ver, hash)
|
164
|
+
generated_ver = verification_code(item, hash)
|
165
|
+
return generated_ver == received_ver
|
166
|
+
rescue InvalidVerificationString
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
class InvalidVerificationString < RuntimeError ; end
|
173
|
+
class UnknownHashMethod < RuntimeError ; end
|
174
|
+
|
175
|
+
class ::Cancer::IM::Peer
|
176
|
+
attr_accessor :client, :capabilities_id, :disco
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
module Cancer
|
5
|
+
# Stream Compression
|
6
|
+
# http://xmpp.org/extensions/xep-0138.html
|
7
|
+
module XEP_0138
|
8
|
+
include Cancer::XEP
|
9
|
+
|
10
|
+
dependency 'core'
|
11
|
+
|
12
|
+
def self.enhance_stream(stream)
|
13
|
+
stream.extend_stream do
|
14
|
+
include Cancer::XEP_0138::Helpers
|
15
|
+
|
16
|
+
alias_method_chain :process_send_data, :xep_0138
|
17
|
+
alias_method_chain :process_received_data, :xep_0138
|
18
|
+
alias_method_chain :close, :xep_0138
|
19
|
+
end
|
20
|
+
|
21
|
+
stream.install_controller(Controller)
|
22
|
+
stream.register_compressor('zlib', Zlib)
|
23
|
+
end
|
24
|
+
|
25
|
+
module Helpers
|
26
|
+
def process_send_data_with_xep_0138(data)
|
27
|
+
data = data.to_s
|
28
|
+
data = @compressor.deflate(data) if @compressor
|
29
|
+
process_send_data_without_xep_0138(data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_received_data_with_xep_0138(data)
|
33
|
+
data = process_received_data_without_xep_0138(data)
|
34
|
+
data = @compressor.inflate(data) if @compressor and data
|
35
|
+
data
|
36
|
+
end
|
37
|
+
|
38
|
+
def close_with_xep_0138
|
39
|
+
close_without_xep_0138
|
40
|
+
if @compressor and @compressor.respond_to?(:close)
|
41
|
+
@compressor.close
|
42
|
+
end
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def install_compressor(compressor)
|
47
|
+
@compressor = compressor
|
48
|
+
end
|
49
|
+
|
50
|
+
def available_compressors
|
51
|
+
compressors.keys
|
52
|
+
end
|
53
|
+
|
54
|
+
def register_compressor(name, klass)
|
55
|
+
compressors[name] = klass
|
56
|
+
end
|
57
|
+
|
58
|
+
def compressor_class(name)
|
59
|
+
compressors[name]
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def compressors
|
65
|
+
@compressors ||= {}
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
class Controller < Cancer::Controller
|
71
|
+
|
72
|
+
FEATURES_NS = "http://jabber.org/features/compress"
|
73
|
+
PROTOCOL_NS = "http://jabber.org/protocol/compress"
|
74
|
+
|
75
|
+
priority 250
|
76
|
+
|
77
|
+
on { |e| e.xpath('/s:features/c:compression/c:method', 'c' => FEATURES_NS, 's' => STREAM_NS) }
|
78
|
+
def try_compression(e)
|
79
|
+
methods = e.xml.all('/stream:features/c:compression/c:method', 'c' => FEATURES_NS)
|
80
|
+
methods = methods.to_a.collect { |m| m.text }
|
81
|
+
@methods = (methods & available_compressors)
|
82
|
+
return if @methods.empty?
|
83
|
+
|
84
|
+
try_compressor
|
85
|
+
end
|
86
|
+
|
87
|
+
on { |e| e.xpath('/n:compressed', 'n' => PROTOCOL_NS) }
|
88
|
+
def restart_stream_on_compressed(e)
|
89
|
+
install_compressor(compressor_class(@current_method).new(options))
|
90
|
+
restart_stream
|
91
|
+
end
|
92
|
+
|
93
|
+
on { |e| e.xpath('/n:failure', 'n' => PROTOCOL_NS) }
|
94
|
+
def retry_other_compression_method_on_failure(e)
|
95
|
+
try_compressor
|
96
|
+
end
|
97
|
+
|
98
|
+
def try_compressor
|
99
|
+
@current_method = @methods.shift
|
100
|
+
throw :skip unless @current_method
|
101
|
+
|
102
|
+
send do |x|
|
103
|
+
x.compress(:xmlns => PROTOCOL_NS) do
|
104
|
+
x.method_ { x.text @current_method }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
throw :halt
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
class Zlib
|
114
|
+
def initialize(options={})
|
115
|
+
@inflate_io = ::Zlib::Inflate.new
|
116
|
+
@deflate_io = ::Zlib::Deflate.new(options[:compression_level] || ::Zlib::BEST_COMPRESSION)
|
117
|
+
end
|
118
|
+
|
119
|
+
def inflate(data)
|
120
|
+
@inflate_io.inflate(data)
|
121
|
+
end
|
122
|
+
|
123
|
+
def deflate(data)
|
124
|
+
@deflate_io.deflate(data, ::Zlib::FULL_FLUSH)
|
125
|
+
end
|
126
|
+
|
127
|
+
def close
|
128
|
+
@inflate_io.close
|
129
|
+
@deflate_io.close
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
|
2
|
+
module Cancer
|
3
|
+
# Roster Item Exchange
|
4
|
+
# http://xmpp.org/extensions/xep-0144.html
|
5
|
+
module XEP_0144
|
6
|
+
include Cancer::XEP
|
7
|
+
|
8
|
+
NS = 'http://jabber.org/protocol/rosterx'
|
9
|
+
NAMESPACES = { 'c' => CLIENT_NS, 'rx' => NS }
|
10
|
+
|
11
|
+
dependency 'core'
|
12
|
+
dependency 'im'
|
13
|
+
dependency 'xep-0030'
|
14
|
+
dependency 'xep-0115'
|
15
|
+
|
16
|
+
def self.enhance_stream(stream)
|
17
|
+
stream.extend_stream do
|
18
|
+
include Cancer::XEP_0144::StreamHelpers
|
19
|
+
end
|
20
|
+
stream.install_controller Cancer::XEP_0144::Controller
|
21
|
+
stream.install_event_helper Cancer::XEP_0144::EventDSL
|
22
|
+
stream.disco.feature(NS)
|
23
|
+
end
|
24
|
+
|
25
|
+
module StreamHelpers
|
26
|
+
|
27
|
+
def exchange_roster(jid, message=nil, &proc)
|
28
|
+
if contact = roster[jid.to_jid.bare_jid]
|
29
|
+
if contact.peers.empty? or !peer_capabilities(jid).features.include?(NS)
|
30
|
+
send_roster_items_exchange_messages(jid.to_jid.bare_jid, message, &proc)
|
31
|
+
else
|
32
|
+
send_roster_items_exchange_iqs(jid, &proc)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def send_roster_items_exchange_messages(jid, message, &proc)
|
40
|
+
|
41
|
+
builder = Builder.new(&proc)
|
42
|
+
|
43
|
+
if builder.send(:build?, :add)
|
44
|
+
send_message(jid) do |x|
|
45
|
+
x.body message if message
|
46
|
+
builder.send(:build, x, :add)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if builder.send(:build?, :delete)
|
51
|
+
send_message(jid) do |x|
|
52
|
+
x.body message if message
|
53
|
+
builder.send(:build, x, :delete)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if builder.send(:build?, :modify)
|
58
|
+
send_message(jid) do |x|
|
59
|
+
x.body message if message
|
60
|
+
builder.send(:build, x, :modify)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def send_roster_items_exchange_iqs(jid, &proc)
|
67
|
+
|
68
|
+
builder = Builder.new(&proc)
|
69
|
+
|
70
|
+
if builder.send(:build?, :add)
|
71
|
+
send_iq(jid, :set) do |x|
|
72
|
+
builder.send(:build, x, :add)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if builder.send(:build?, :delete)
|
77
|
+
send_iq(jid, :set) do |x|
|
78
|
+
builder.send(:build, x, :delete)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if builder.send(:build?, :modify)
|
83
|
+
send_iq(jid, :set) do |x|
|
84
|
+
builder.send(:build, x, :modify)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
class Builder
|
93
|
+
|
94
|
+
def initialize(builder)
|
95
|
+
@actions = {
|
96
|
+
:add => [],
|
97
|
+
:delete => [],
|
98
|
+
:modify => []
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def add(jid, options={})
|
103
|
+
action(:add, jid, options)
|
104
|
+
end
|
105
|
+
|
106
|
+
def delete(jid, options={})
|
107
|
+
action(:jid, jid, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def modify(jid, options={})
|
111
|
+
action(:modify, jid, options)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def build?(action)
|
117
|
+
@actions[actions.to_sym] and !@actions[actions.to_sym].empty?
|
118
|
+
end
|
119
|
+
|
120
|
+
def build(builder, action)
|
121
|
+
builder.x_(:xmlns => NS) do
|
122
|
+
@actions[actions.to_sym].each do |item|
|
123
|
+
build_item(builder, action, item)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def build_item(builder, action, item)
|
129
|
+
jid, options = *item
|
130
|
+
builder.item({:action => action.to_s, :jid => jid.to_s, :name => options[:name]}.clean) do
|
131
|
+
options[:groups].each do |group|
|
132
|
+
builder.group(group)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def action(action, jid, options={})
|
138
|
+
options[:groups] = [options.delete(:groups)], options.delete(:group)].flatten.compact.uniq
|
139
|
+
@actions[actions.to_sym].push([jid, options])
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
class Controller < Cancer::Controller
|
145
|
+
|
146
|
+
on { |e| e.xpath('/c:iq[@type="set"]/rx:x', NAMESPACES) }
|
147
|
+
def handle_iq(e)
|
148
|
+
handle_roster_items(e, e.xml.all('/c:iq/rx:x/rx:item', NAMESPACES))
|
149
|
+
send_iq(e.sender, :result, e.id)
|
150
|
+
end
|
151
|
+
|
152
|
+
on { |e| e.xpath('/c:message/rx:x', NAMESPACES) }
|
153
|
+
def handle_message(e)
|
154
|
+
handle_roster_items(e, e.xml.all('/c:iq/rx:x/rx:item', NAMESPACES))
|
155
|
+
end
|
156
|
+
|
157
|
+
def handle_roster_items(e, items)
|
158
|
+
items.each do |item|
|
159
|
+
name = (item[:name] ? item[:name].to_s : nil)
|
160
|
+
groups = item.all('./rx:group', NAMESPACES).collect { |g| g.text.to_s }
|
161
|
+
fire! Cancer::XEP_0144::Event, e.sender, item[:action].to_s, item[:jid].to_s, name, groups
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
class Event < Cancer::Events::AbstractEvent
|
168
|
+
attr_reader :sender, :jid, :name, :groups, :action
|
169
|
+
def initialize(sender, action, jid, name, groups)
|
170
|
+
@sender, @action, @jid, @name, @groups = sender.to_jid, action.to_sym, jid.to_jid, name, groups
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class EventMatcher < Cancer::Events::Matcher
|
175
|
+
|
176
|
+
def initialize(options={})
|
177
|
+
@sender = options.delete(:sender)
|
178
|
+
@action = options.delete(:action)
|
179
|
+
@jid = options.delete(:jid)
|
180
|
+
@name = options.delete(:name)
|
181
|
+
@groups = options.delete(:groups)
|
182
|
+
end
|
183
|
+
|
184
|
+
def match?(event)
|
185
|
+
(
|
186
|
+
Cancer::XEP_0144::Event === event and
|
187
|
+
match_sender?(event) and
|
188
|
+
match_action?(event) and
|
189
|
+
match_jid?(event) and
|
190
|
+
match_name?(event) and
|
191
|
+
match_groups?(event)
|
192
|
+
)
|
193
|
+
end
|
194
|
+
|
195
|
+
def match_sender?(event, pattern=@sender)
|
196
|
+
case pattern
|
197
|
+
when String then event.sender.to_s == pattern
|
198
|
+
when Regexp then event.sender.to_s =~ pattern
|
199
|
+
when Array then pattern.any? { |p| match_sender?(event, p) }
|
200
|
+
when NilClass then true
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def match_action?(event, pattern=@action)
|
205
|
+
case pattern
|
206
|
+
when Symbol then event.action == pattern
|
207
|
+
when Array then pattern.any? { |p| match_action?(event, p) }
|
208
|
+
when NilClass then true
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def match_jid?(event, pattern=@jid)
|
213
|
+
case pattern
|
214
|
+
when String then event.jid.to_s == pattern
|
215
|
+
when Regexp then event.jid.to_s =~ pattern
|
216
|
+
when Array then pattern.any? { |p| match_jid?(event, p) }
|
217
|
+
when NilClass then true
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def match_name?(event, pattern=@name)
|
222
|
+
case pattern
|
223
|
+
when String then event.name == pattern
|
224
|
+
when Regexp then event.name =~ pattern
|
225
|
+
when Array then pattern.any? { |p| match_name?(event, p) }
|
226
|
+
when NilClass then true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def match_groups?(event, pattern=@groups)
|
231
|
+
case pattern
|
232
|
+
when String then event.groups.include?(pattern)
|
233
|
+
when Regexp then event.groups.any? { |group| group =~ pattern }
|
234
|
+
when Array then pattern.any? { |p| match_groups?(event, p) }
|
235
|
+
when NilClass then true
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
module EventDSL
|
242
|
+
|
243
|
+
def roster_exchange(options={})
|
244
|
+
Cancer::XEP_0144::EventMatcher.new(options)
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
end
|