cancer 0.1.0.a1

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 (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