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,43 @@
1
+
2
+ module Cancer
3
+ module XEP
4
+
5
+ def self.xeps
6
+ @xeps ||= {}
7
+ end
8
+
9
+ def self.register(name, mod_name)
10
+ self.xeps[name.to_s] = mod_name
11
+ end
12
+
13
+ def self.[](name)
14
+ self.xeps[name.to_s]
15
+ end
16
+
17
+ def self.const_for(name)
18
+ mod_name = self[name]
19
+ raise Cancer::XEP::LoadError.new(name) unless mod_name
20
+
21
+ mod_name.sub!(/^[:]{2}/, '')
22
+ eval("::#{mod_name}")
23
+
24
+ rescue NameError => e
25
+ if e.message.include?(mod_name)
26
+ raise Cancer::XEP::LoadError.new(name, mod_name)
27
+ else
28
+ raise e
29
+ end
30
+ end
31
+
32
+ def self.included(base)
33
+ base.send(:include, Cancer::Dependencies)
34
+ end
35
+
36
+ class LoadError < Cancer::CancerError
37
+ def initialize(xep_name, mod_name=nil)
38
+ super("failed to load #{xep_name}"+(mod_name ? " (#{mod_name})" : ''))
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,109 @@
1
+
2
+ module Cancer
3
+ # Core XMPP protocol
4
+ # http://tools.ietf.org/html/draft-ietf-xmpp-3920bis-02
5
+ module Core
6
+ include Cancer::XEP
7
+
8
+ autoload :Stream, 'cancer/xeps/core/stream'
9
+ autoload :TLS, 'cancer/xeps/core/tls'
10
+ autoload :SASL, 'cancer/xeps/core/sasl'
11
+ autoload :Bind, 'cancer/xeps/core/bind'
12
+ autoload :Session, 'cancer/xeps/core/session'
13
+ autoload :Terminator, 'cancer/xeps/core/terminator'
14
+
15
+ def self.enhance_stream(stream)
16
+ stream.extend_stream do
17
+ include Cancer::Core::StreamHelper
18
+ end
19
+ stream.extend_builder do
20
+ include Cancer::Core::BuilderHelper
21
+ end
22
+
23
+ stream.install_controller(Cancer::Core::Stream)
24
+ stream.install_controller(Cancer::Core::TLS)
25
+ stream.install_controller(Cancer::Core::SASL)
26
+ stream.install_controller(Cancer::Core::Bind)
27
+ stream.install_controller(Cancer::Core::Session)
28
+ stream.install_controller(Cancer::Core::Terminator)
29
+ end
30
+
31
+ module StreamHelper
32
+
33
+ def restart_stream
34
+ send_stream_header
35
+ throw :halt
36
+ end
37
+
38
+ def send_stream_header
39
+ send %(<stream:stream xmlns="#{CLIENT_NS}" xmlns:stream="#{STREAM_NS}" to="#{options[:jid].server_jid}" from="#{options[:jid].bare_jid}" version="1.0">)
40
+ end
41
+
42
+ def start_tls
43
+ @adapter.start_tls
44
+ end
45
+
46
+ def send_iq(to, type=:get, id=nil, from=nil, &proc)
47
+ iq = build do |x|
48
+ x.iq(to, type, id, from) do
49
+ proc.call(x) if proc
50
+ end
51
+ end
52
+
53
+ if type == :result or type == :error
54
+ send iq
55
+ else
56
+ send_and_receive(iq) { |x| x.named?('iq', CLIENT_NS) and (x[:id] == iq[:id]) }
57
+ end
58
+ end
59
+
60
+ def send_iq_response(original_iq, type = :result, &proc)
61
+ original_iq = (Cancer::Events::XMLEvent === original_iq ? original_iq.xml : original_iq)
62
+ to = original_iq['from']
63
+ id = original_iq['iq']
64
+ send_iq(to, type, id, &proc)
65
+ end
66
+
67
+ def send_message(to=nil, type=nil, id=nil, &proc)
68
+ send do |x|
69
+ x.message(to, type) do
70
+ proc.call(x) if proc
71
+ end
72
+ end
73
+ end
74
+
75
+ def next_stanza_id
76
+ @stanza_id ||= 0
77
+ @stanza_id += 1
78
+ "cancer-#{@stanza_id}"
79
+ end
80
+ end
81
+
82
+ module BuilderHelper
83
+ def iq(to=nil, type=nil, id=nil, from=nil, &proc)
84
+ attributes = {}
85
+ attributes[:to] = to.to_s if to
86
+ attributes[:from] = from.to_s if from
87
+ attributes[:type] = type.to_s if type
88
+ attributes[:id] = id || @stream.next_stanza_id
89
+ iq_(attributes, &proc)
90
+ end
91
+
92
+ def iq_response(original_iq, type = :result, &proc)
93
+ original_iq = (Cancer::Events::XMLEvent === original_iq ? original_iq.xml : original_iq)
94
+ to = original_iq['from']
95
+ id = original_iq['iq']
96
+ iq(to, type, id, &proc)
97
+ end
98
+
99
+ def message(to=nil, type=nil, id=nil, &proc)
100
+ attributes = {}
101
+ attributes[:to] = to.to_s if to
102
+ attributes[:type] = type.to_s if type
103
+ attributes[:id] = id || @stream.next_stanza_id
104
+ message_(attributes, &proc)
105
+ end
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module Cancer
3
+ module Core
4
+ class Bind < Cancer::Controller
5
+
6
+ NS = "urn:ietf:params:xml:ns:xmpp-bind"
7
+
8
+ priority 300
9
+
10
+ on { |e| e.xpath('/s:features/b:bind', 'b' => NS, 's' => STREAM_NS) }
11
+ def bind_resource(e)
12
+ resource = options[:jid].resource
13
+ result = send_iq(nil, :set) do |x|
14
+ x.bind(:xmlns => NS) do
15
+ x.resource { x.text resource } if resource
16
+ end
17
+ end
18
+ jid_element = result.first('b:bind/b:jid', 'b' => NS)
19
+ if jid_element.nil? or result[:type] == 'error'
20
+ raise "Bind failed: #{result}"
21
+ end
22
+
23
+ options[:jid].merge! Cancer::JID.parse(jid_element.text).to_hash
24
+ end
25
+
26
+ on { |e| e.name(:initialized) }
27
+ def unregister_ourself
28
+ unregister!
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,113 @@
1
+
2
+ require 'rubygems'
3
+ require 'sasl/base'
4
+ require 'sasl'
5
+ autoload :Base64, 'base64'
6
+
7
+ module Cancer
8
+ module Core
9
+ class SASL < Cancer::Controller
10
+
11
+ NS = "urn:ietf:params:xml:ns:xmpp-sasl"
12
+
13
+ priority 200
14
+
15
+ on { |e| e.xpath('/t:features/s:mechanisms/s:mechanism', 's' => NS, 't' => STREAM_NS) }
16
+ def find_sasl_mechanisms(e)
17
+ mechanisms = e.xml.all('/stream:features/s:mechanisms/s:mechanism', 's' => NS)
18
+ @mechanisms = mechanisms.to_a.collect { |e| e.text.upcase }
19
+ return if @mechanisms.empty?
20
+
21
+ try_sasl_mechanism
22
+ end
23
+
24
+ on { |e| e.xpath('/n:success', 'n' => NS) }
25
+ def on_success(e)
26
+ restart_stream
27
+ end
28
+
29
+ on { |e| e.xpath('/n:challenge', 'n' => NS) }
30
+ def on_challenge(e)
31
+ message, content = *@sasl.receive('challenge', Base64.decode64(e.xml.text))
32
+
33
+ unless message == 'response'
34
+ try_sasl_mechanism
35
+ end
36
+
37
+ send do |x|
38
+ x.response :xmlns => NS do |x|
39
+ x.text Base64.encode64(content) if content
40
+ end
41
+ end
42
+
43
+ throw :halt
44
+ end
45
+
46
+ on { |e| e.name(:initialized) }
47
+ def unregister_ourself
48
+ unregister!
49
+ end
50
+
51
+ def try_sasl_mechanism
52
+ if @mechanisms.empty?
53
+ raise "SASL autentication failed!"
54
+ end
55
+
56
+ @sasl = ::SASL.new(@mechanisms, Preferences.new(options))
57
+ @mechanisms.delete(@sasl.mechanism)
58
+
59
+ message, content = *@sasl.start
60
+ unless message == 'auth'
61
+ try_sasl_mechanism
62
+ end
63
+
64
+ send do |x|
65
+ x.auth :mechanism => @sasl.mechanism, :xmlns => NS do |x|
66
+ x.text Base64.encode64(content) if content
67
+ end
68
+ end
69
+
70
+ throw :halt
71
+ end
72
+
73
+ class Preferences < ::SASL::Preferences
74
+ def initialize(options={})
75
+ @options = options
76
+ end
77
+
78
+ def authzid
79
+ nil
80
+ end
81
+
82
+ def realm
83
+ @options[:jid].domain
84
+ end
85
+
86
+ def digest_uri
87
+ "xmpp/#{@options[:jid].domain}"
88
+ end
89
+
90
+ def username
91
+ @options[:jid].bare_jid.to_s
92
+ end
93
+
94
+ def has_password?
95
+ !!@options[:password]
96
+ end
97
+
98
+ def allow_plaintext?
99
+ true
100
+ end
101
+
102
+ def password
103
+ @options[:password]
104
+ end
105
+
106
+ def want_anonymous?
107
+ false
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module Cancer
3
+ module Core
4
+ class Session < Cancer::Controller
5
+
6
+ NS = "urn:ietf:params:xml:ns:xmpp-session"
7
+
8
+ priority 400
9
+
10
+ on { |e| e.xpath('/t:features/s:session', 's' => NS, 't' => STREAM_NS) }
11
+ def initiate_session(e)
12
+ send_iq(nil, :set) { |x| x.session(:xmlns => NS) }
13
+ end
14
+
15
+ on { |e| e.name(:initialized) }
16
+ def unregister_ourself
17
+ unregister!
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module Cancer
3
+ module Core
4
+ class Stream < Cancer::Controller
5
+
6
+ on { |e| e.xpath('/n:stream', 'n' => STREAM_NS) }
7
+ def set_session_id(e)
8
+ options[:session_id] = e.xml['id']
9
+ end
10
+
11
+ on { |e| e.name(:initialized) }
12
+ def unregister_ourself
13
+ unregister!
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module Cancer
3
+ module Core
4
+ class Terminator < Cancer::Controller
5
+
6
+ priority 500
7
+
8
+ on { |e| e.xpath('/t:features', 't' => STREAM_NS) }
9
+ def initialization_completed(e)
10
+ fire! Cancer::Events::NamedEvent, :initialized
11
+ initialization_completed!
12
+ end
13
+
14
+ on { |e| e.name(:initialized) }
15
+ def unregister_ourself
16
+ unregister!
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module Cancer
3
+ module Core
4
+ class TLS < Cancer::Controller
5
+
6
+ NS = "urn:ietf:params:xml:ns:xmpp-tls"
7
+
8
+ priority 100
9
+
10
+ on { |e| e.xpath('/s:features/t:starttls', 't' => NS, 's' => STREAM_NS) }
11
+ def try_start_tls(e)
12
+ send { |x| x.starttls :xmlns => NS }
13
+ throw :halt
14
+ end
15
+
16
+ on { |e| e.xpath('/t:proceed', 't' => NS) }
17
+ def start_tls_proceed(e)
18
+ start_tls
19
+ restart_stream
20
+ end
21
+
22
+ on { |e| e.xpath('/t:failure', 't' => NS) }
23
+ def start_tls_failed(e)
24
+ raise "TLS connection failed: #{e.xml}"
25
+ end
26
+
27
+ on { |e| e.name(:initialized) }
28
+ def unregister_ourself
29
+ unregister!
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,323 @@
1
+
2
+ module Cancer
3
+ # XMPP IM protocol
4
+ module IM
5
+ include Cancer::XEP
6
+
7
+ ROSTER_NS = 'jabber:iq:roster'
8
+
9
+ def self.enhance_stream(stream)
10
+ stream.extend_stream do
11
+ include Cancer::IM::StreamHelper
12
+ end
13
+ stream.install_event_helper Cancer::IM::EventDSL
14
+ stream.install_controller Cancer::IM::Roster::Controller
15
+
16
+ stream.presence_enhancer_for :priority do |x|
17
+ x.priority_(self.presence_priority) if self.presence_priority
18
+ end
19
+ stream.presence_enhancer_for :show do |x|
20
+ x.show_(self.presence_show) if self.presence_show
21
+ end
22
+ stream.presence_enhancer_for :status do |x|
23
+ x.status_(self.presence_status) if self.presence_status
24
+ end
25
+ end
26
+
27
+ module StreamHelper
28
+
29
+ def roster
30
+ @roster ||= Roster.new(self)
31
+ end
32
+
33
+ def peers
34
+ @peers ||= {}
35
+ end
36
+
37
+ def presence_enhancers
38
+ @presence_enhancers ||= []
39
+ end
40
+
41
+ def presence_enhancer_for(name, &proc)
42
+ method_name = "enhance_presence_with_#{name}"
43
+ if self.respond_to?(method_name)
44
+ raise "#{self} already has a presence enhancer for #{name} (#{method_name})"
45
+ end
46
+
47
+ metaklass = (class << self ; self; end)
48
+ metaklass.send(:define_method, method_name, &proc)
49
+ metaklass.send(:private, method_name)
50
+ self.presence_enhancers.push(method_name)
51
+
52
+ name
53
+ end
54
+
55
+ def remove_precense_enhancer_for(name)
56
+ method_name = "enhance_presence_with_#{name}"
57
+
58
+ if self.respond_to?(method_name)
59
+ metaklass = (class << self ; self; end)
60
+ metaklass.send(:remove_method, method_name)
61
+ self.precense_enhancers.delete(method_name)
62
+ name
63
+ end
64
+ end
65
+
66
+ def update_presence!
67
+ send do |x|
68
+ x.presence do
69
+
70
+ self.presence_enhancers.each do |method_name|
71
+ self.__send__(method_name, x)
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+
78
+ attr_accessor :presence_priority
79
+ attr_accessor :presence_show
80
+ attr_accessor :presence_status
81
+
82
+ end
83
+
84
+ class Roster
85
+ include Enumerable
86
+
87
+ def initialize(stream)
88
+ @stream = stream
89
+ pull!
90
+ end
91
+
92
+ def [](jid)
93
+ @items[jid.to_s]
94
+ end
95
+
96
+ def each(&proc)
97
+ @items.values.each(&proc)
98
+ end
99
+
100
+ def pull!
101
+ @items = {}
102
+
103
+ result = @stream.send_iq(nil) do |x|
104
+ x.query :xmlns => ROSTER_NS
105
+ end
106
+
107
+ namespaces = { 'c' => CLIENT_NS, 'r' => ROSTER_NS }
108
+ result.all('/c:iq/r:query/r:item', namespaces) do |i|
109
+ item = Roster::Item.new(i)
110
+ @items[item.jid] = item
111
+ end
112
+
113
+ @items
114
+ end
115
+
116
+ def push!(iq)
117
+ namespaces = { 'c' => CLIENT_NS, 'r' => ROSTER_NS }
118
+ iq.all('/c:iq/r:query/r:item', namespaces) do |i|
119
+ if i[:subscription].to_s == 'remove'
120
+ @items.delete(i[:jid].to_s)
121
+ else
122
+ item = Roster::Item.new(i)
123
+ @items[item.jid] = item
124
+ end
125
+ end
126
+ end
127
+
128
+ def save(jid, options={})
129
+ subscription = options.delete(:subscription)
130
+ needs_roster = (!options.empty?) or (@items[jid.to_s].nil?)
131
+
132
+ if needs_roster
133
+ groups = options.delete(:groups) || []
134
+ @stream.send_iq(nil, :set) do |x|
135
+ x.query :xmlns => ROSTER_NS do
136
+ x.item(options.merge(:jid => jid)) do
137
+ groups.each { |group| x.group(group) }
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ unless subscription.nil?
144
+ subscription = case subscription
145
+ when TrueClass then :subscribe
146
+ when FalseClass then :unsubscribe
147
+ else subscription
148
+ end
149
+ @stream.send { |x| x.presence(:to => jid, :type => subscription.to_s) }
150
+ end
151
+ end
152
+
153
+ def create(jid, options={})
154
+ raise "Roster item #{jid} already exists!" if @items[jid.to_s]
155
+ save(jid, options)
156
+ end
157
+
158
+ def update(jid, options={})
159
+ raise "Roster item #{jid} doesn't exists yet!" unless @items[jid.to_s]
160
+ save(jid, options)
161
+ end
162
+
163
+ def delete(jid_or_item)
164
+ jid = (Roster::Item === jid_or_item ? jid_or_item.jid.to_s : jid_or_item.to_s)
165
+
166
+ raise "Roster item #{jid} doesn't exists yet!" unless @items[jid]
167
+
168
+ @stream.send_iq(nil, :set) do |x|
169
+ x.query :xmlns => ROSTER_NS do
170
+ x.item(:jid => jid, :subscription => 'remove')
171
+ end
172
+ end
173
+ end
174
+
175
+ class Item
176
+ attr_reader :jid, :name, :subscription, :groups, :peers
177
+ def initialize(options={})
178
+ @jid = (options[:jid].to_s rescue nil)
179
+ @name = (options[:name].to_s rescue nil)
180
+ @subscription = (options[:subscription].to_sym rescue :none)
181
+ @pending_subscription = ('subscribe' == options[:ask])
182
+ @peers = []
183
+ @groups = case options
184
+ when Nokogiri::XML::Node
185
+ options.all('r:group', 'r' => ROSTER_NS).collect { |g| g.text.to_s }
186
+ else options[:groups]
187
+ end
188
+ end
189
+
190
+ def pending_subscription?
191
+ @pending_subscription || false
192
+ end
193
+ end
194
+
195
+ class Controller < Cancer::Controller
196
+
197
+ on(:priority => 1000) { |e| e.name(:initialized) }
198
+ def broadcast
199
+ update_presence!
200
+ end
201
+
202
+ NAMESPACES = { 'c' => CLIENT_NS, 'r' => ROSTER_NS }
203
+ on { |e| e.xpath('/c:iq[@type="set"]/r:query/r:item', NAMESPACES) }
204
+ def roster_push(e)
205
+ roster.push!(e.xml)
206
+
207
+ send_iq(e.sender, :result, e.id)
208
+
209
+ throw :halt
210
+ end
211
+
212
+ on(:priority => 5) { |e| e.xpath('/c:presence', 'c' => CLIENT_NS) }
213
+ def received_precense(e)
214
+ peer = self.peers[e.sender] || Peer.load(e.xml)
215
+
216
+ case (e.xml[:type] || :available).to_sym
217
+
218
+ when :available
219
+ peer = Peer.load(e.xml)
220
+ self.peers[e.sender] = peer
221
+ self.roster[e.sender.bare_jid].peers.push(peer) if self.roster[e.sender.bare_jid]
222
+ fire! PrecenceEvent, self, peer, :available, e.xml
223
+ when :unavailable
224
+ self.peers.delete(e.sender)
225
+ self.roster[e.sender.bare_jid].peers.delete(peer) if self.roster[e.sender.bare_jid]
226
+ fire! PrecenceEvent, self, peer, :unavailable, e.xml
227
+
228
+ when :subscribe
229
+ fire! PrecenceEvent, self, peer, :subscribe, e.xml
230
+ when :subscribed
231
+ fire! PrecenceEvent, self, peer, :subscribed, e.xml
232
+
233
+ when :unsubscribe
234
+ fire! PrecenceEvent, self, peer, :unsubscribe, e.xml
235
+ when :unsubscribed
236
+ fire! PrecenceEvent, self, peer, :unsubscribed, e.xml
237
+
238
+ when :probe
239
+ when :error
240
+
241
+ end
242
+ end
243
+
244
+ end
245
+ end
246
+
247
+ class Peer
248
+ attr_accessor :jid, :show, :status
249
+
250
+ def self.load(presence)
251
+ new.load(presence)
252
+ end
253
+
254
+ def load(presence)
255
+ @jid = presence[:from].to_s.to_jid
256
+ @show = (presence.first('./c:show/text()').to_sym rescue nil)
257
+ @status = (presence.first('./c:status/text()').to_s rescue nil)
258
+ self
259
+ end
260
+ end
261
+
262
+ class PrecenceEvent < Cancer::Events::AbstractEvent
263
+ attr_reader :peer, :action, :xml
264
+ def initialize(controller, peer, action, xml)
265
+ @controller, @peer, @action, @xml = controller, peer, action, xml
266
+ end
267
+ end
268
+
269
+ class PrecenceEventMatcher < Cancer::Events::Matcher
270
+
271
+ def initialize(options={})
272
+ @action = options[:action]
273
+ end
274
+
275
+ def match?(event)
276
+ Cancer::IM::PrecenceEvent === event and match_action?(event)
277
+ end
278
+
279
+ def match_action?(event, pattern=@action)
280
+ case pattern
281
+ when Symbol then event.action.to_sym == pattern
282
+ when String then event.action.to_s == pattern
283
+ when Array then pattern.any? { |p| match_profile?(event, p) }
284
+ when NilClass then true
285
+ end
286
+ end
287
+
288
+ end
289
+
290
+ module EventDSL
291
+
292
+ def presence(options={})
293
+ Cancer::IM::PrecenceEventMatcher.new(options)
294
+ end
295
+
296
+ def peer_available(options={})
297
+ presence(options.merge(:action => :available))
298
+ end
299
+
300
+ def peer_unavailable(options={})
301
+ presence(options.merge(:action => :unavailable))
302
+ end
303
+
304
+ def subscription_request(options={})
305
+ presence(options.merge(:action => :subscribe))
306
+ end
307
+
308
+ def subscription_granted(options={})
309
+ presence(options.merge(:action => :subscribed))
310
+ end
311
+
312
+ def unsubscription_request(options={})
313
+ presence(options.merge(:action => :unsubscribe))
314
+ end
315
+
316
+ def subscription_revoked(options={})
317
+ presence(options.merge(:action => :unsubscribed))
318
+ end
319
+
320
+ end
321
+
322
+ end
323
+ end