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