cancer 0.1.0.a1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.autotest +3 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE.txt +22 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/cancer.gemspec +156 -0
  7. data/documentation/STREAM_INITIATION.markdown +18 -0
  8. data/examples/example.rb +80 -0
  9. data/examples/example_2.rb +20 -0
  10. data/examples/example_em.rb +11 -0
  11. data/examples/example_em_helper.rb +23 -0
  12. data/examples/example_im_roster.rb +26 -0
  13. data/examples/example_xep_0004.rb +124 -0
  14. data/examples/example_xep_0047.rb +35 -0
  15. data/examples/example_xep_0050.rb +78 -0
  16. data/examples/example_xep_0065.rb +66 -0
  17. data/examples/example_xep_0096.dup.rb +40 -0
  18. data/examples/example_xep_0096_xep_0047.rb +42 -0
  19. data/examples/example_xep_0096_xep_0065.rb +40 -0
  20. data/lib/cancer.rb +122 -0
  21. data/lib/cancer/adapter.rb +33 -0
  22. data/lib/cancer/adapters/em.rb +88 -0
  23. data/lib/cancer/adapters/socket.rb +122 -0
  24. data/lib/cancer/builder.rb +28 -0
  25. data/lib/cancer/controller.rb +32 -0
  26. data/lib/cancer/dependencies.rb +187 -0
  27. data/lib/cancer/events/abstract_event.rb +10 -0
  28. data/lib/cancer/events/base_matchers.rb +63 -0
  29. data/lib/cancer/events/binary_matchers.rb +30 -0
  30. data/lib/cancer/events/eventable.rb +92 -0
  31. data/lib/cancer/events/exception_events.rb +28 -0
  32. data/lib/cancer/events/handler.rb +33 -0
  33. data/lib/cancer/events/manager.rb +130 -0
  34. data/lib/cancer/events/named_events.rb +25 -0
  35. data/lib/cancer/events/proc_matcher.rb +16 -0
  36. data/lib/cancer/events/xml_events.rb +44 -0
  37. data/lib/cancer/exceptions.rb +53 -0
  38. data/lib/cancer/jid.rb +215 -0
  39. data/lib/cancer/lock.rb +32 -0
  40. data/lib/cancer/spec.rb +35 -0
  41. data/lib/cancer/spec/error.rb +18 -0
  42. data/lib/cancer/spec/extentions.rb +79 -0
  43. data/lib/cancer/spec/matcher.rb +30 -0
  44. data/lib/cancer/spec/mock_adapter.rb +139 -0
  45. data/lib/cancer/spec/mock_stream.rb +15 -0
  46. data/lib/cancer/spec/transcript.rb +107 -0
  47. data/lib/cancer/stream.rb +182 -0
  48. data/lib/cancer/stream/builder.rb +20 -0
  49. data/lib/cancer/stream/controller.rb +36 -0
  50. data/lib/cancer/stream/event_helper.rb +12 -0
  51. data/lib/cancer/stream/xep.rb +52 -0
  52. data/lib/cancer/stream_parser.rb +144 -0
  53. data/lib/cancer/support.rb +27 -0
  54. data/lib/cancer/support/hash.rb +32 -0
  55. data/lib/cancer/support/string.rb +22 -0
  56. data/lib/cancer/synchronized_stanza.rb +79 -0
  57. data/lib/cancer/thread_pool.rb +118 -0
  58. data/lib/cancer/xep.rb +43 -0
  59. data/lib/cancer/xeps/core.rb +109 -0
  60. data/lib/cancer/xeps/core/bind.rb +33 -0
  61. data/lib/cancer/xeps/core/sasl.rb +113 -0
  62. data/lib/cancer/xeps/core/session.rb +22 -0
  63. data/lib/cancer/xeps/core/stream.rb +18 -0
  64. data/lib/cancer/xeps/core/terminator.rb +21 -0
  65. data/lib/cancer/xeps/core/tls.rb +34 -0
  66. data/lib/cancer/xeps/im.rb +323 -0
  67. data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
  68. data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
  69. data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
  70. data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
  71. data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
  72. data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
  73. data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
  74. data/lib/cancer/xeps/xep_0095_si.rb +211 -0
  75. data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
  76. data/lib/cancer/xeps/xep_0114_component.rb +73 -0
  77. data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
  78. data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
  79. data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
  80. data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
  81. data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
  82. data/spec/spec.opts +2 -0
  83. data/spec/spec_helper.rb +14 -0
  84. data/spec/stream/stanza_errors_spec.rb +47 -0
  85. data/spec/stream/stream_errors_spec.rb +38 -0
  86. data/spec/stream/stream_initialization_spec.rb +160 -0
  87. data/spec/xep_0050/local_spec.rb +165 -0
  88. data/spec/xep_0050/remote_spec.rb +44 -0
  89. 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