shingara-blather 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +162 -0
  3. data/examples/echo.rb +18 -0
  4. data/examples/execute.rb +16 -0
  5. data/examples/ping_pong.rb +37 -0
  6. data/examples/print_hierarchy.rb +76 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/stream_only.rb +27 -0
  9. data/examples/xmpp4r/echo.rb +35 -0
  10. data/lib/blather/client/client.rb +310 -0
  11. data/lib/blather/client/dsl/pubsub.rb +170 -0
  12. data/lib/blather/client/dsl.rb +264 -0
  13. data/lib/blather/client.rb +87 -0
  14. data/lib/blather/core_ext/nokogiri.rb +40 -0
  15. data/lib/blather/errors/sasl_error.rb +43 -0
  16. data/lib/blather/errors/stanza_error.rb +107 -0
  17. data/lib/blather/errors/stream_error.rb +82 -0
  18. data/lib/blather/errors.rb +69 -0
  19. data/lib/blather/jid.rb +142 -0
  20. data/lib/blather/roster.rb +111 -0
  21. data/lib/blather/roster_item.rb +122 -0
  22. data/lib/blather/stanza/disco/disco_info.rb +176 -0
  23. data/lib/blather/stanza/disco/disco_items.rb +132 -0
  24. data/lib/blather/stanza/disco.rb +25 -0
  25. data/lib/blather/stanza/iq/query.rb +53 -0
  26. data/lib/blather/stanza/iq/roster.rb +179 -0
  27. data/lib/blather/stanza/iq.rb +138 -0
  28. data/lib/blather/stanza/message.rb +332 -0
  29. data/lib/blather/stanza/presence/status.rb +212 -0
  30. data/lib/blather/stanza/presence/subscription.rb +101 -0
  31. data/lib/blather/stanza/presence.rb +163 -0
  32. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  33. data/lib/blather/stanza/pubsub/create.rb +65 -0
  34. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  35. data/lib/blather/stanza/pubsub/event.rb +123 -0
  36. data/lib/blather/stanza/pubsub/items.rb +103 -0
  37. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  38. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  39. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  40. data/lib/blather/stanza/pubsub/subscription.rb +134 -0
  41. data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
  42. data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
  43. data/lib/blather/stanza/pubsub.rb +129 -0
  44. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  45. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  46. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  47. data/lib/blather/stanza.rb +149 -0
  48. data/lib/blather/stream/client.rb +31 -0
  49. data/lib/blather/stream/component.rb +38 -0
  50. data/lib/blather/stream/features/resource.rb +63 -0
  51. data/lib/blather/stream/features/sasl.rb +187 -0
  52. data/lib/blather/stream/features/session.rb +44 -0
  53. data/lib/blather/stream/features/tls.rb +28 -0
  54. data/lib/blather/stream/features.rb +53 -0
  55. data/lib/blather/stream/parser.rb +102 -0
  56. data/lib/blather/stream.rb +231 -0
  57. data/lib/blather/xmpp_node.rb +218 -0
  58. data/lib/blather.rb +78 -0
  59. data/spec/blather/client/client_spec.rb +559 -0
  60. data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
  61. data/spec/blather/client/dsl_spec.rb +143 -0
  62. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  63. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  64. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  65. data/spec/blather/errors/stream_error_spec.rb +108 -0
  66. data/spec/blather/errors_spec.rb +33 -0
  67. data/spec/blather/jid_spec.rb +87 -0
  68. data/spec/blather/roster_item_spec.rb +96 -0
  69. data/spec/blather/roster_spec.rb +103 -0
  70. data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
  71. data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
  72. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  73. data/spec/blather/stanza/iq/roster_spec.rb +140 -0
  74. data/spec/blather/stanza/iq_spec.rb +45 -0
  75. data/spec/blather/stanza/message_spec.rb +132 -0
  76. data/spec/blather/stanza/presence/status_spec.rb +132 -0
  77. data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
  78. data/spec/blather/stanza/presence_spec.rb +66 -0
  79. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  80. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  81. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  82. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  83. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  84. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  85. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  86. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  87. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  88. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  89. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  90. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  92. data/spec/blather/stanza/pubsub_spec.rb +67 -0
  93. data/spec/blather/stanza_spec.rb +116 -0
  94. data/spec/blather/stream/client_spec.rb +1011 -0
  95. data/spec/blather/stream/component_spec.rb +95 -0
  96. data/spec/blather/stream/parser_spec.rb +145 -0
  97. data/spec/blather/xmpp_node_spec.rb +231 -0
  98. data/spec/fixtures/pubsub.rb +311 -0
  99. data/spec/spec_helper.rb +43 -0
  100. metadata +249 -0
@@ -0,0 +1,310 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. blather])
2
+
3
+ module Blather
4
+ # # Blather Client
5
+ #
6
+ # Blather's Client class provides a set of helpers for working with common
7
+ # XMPP tasks such as setting up and starting the connection, settings
8
+ # status, registering and dispatching filters and handlers and roster
9
+ # management.
10
+ #
11
+ # Client can be used separately from the DSL if you'd like to implement your
12
+ # own DSL Here's the echo example using the client without the DSL:
13
+ #
14
+ # require 'blather/client/client'
15
+ # client = Client.setup 'echo@jabber.local', 'echo'
16
+ #
17
+ # client.register_handler(:ready) do
18
+ # puts "Connected ! send messages to #{client.jid.stripped}."
19
+ # end
20
+ #
21
+ # client.register_handler :subscription, :request? do |s|
22
+ # client.write s.approve!
23
+ # end
24
+ #
25
+ # client.register_handler :message, :chat?, :body => 'exit' do |m|
26
+ # client.write Blather::Stanza::Message.new(m.from, 'Exiting...')
27
+ # client.close
28
+ # end
29
+ #
30
+ # client.register_handler :message, :chat?, :body do |m|
31
+ # client.write Blather::Stanza::Message.new(m.from, "You sent: #{m.body}")
32
+ # end
33
+ #
34
+ class Client
35
+ attr_reader :jid,
36
+ :roster
37
+
38
+ # Create a new client and set it up
39
+ #
40
+ # @param [Blather::JID, #to_s] jid the JID to authorize with
41
+ # @param [String] password the password to authorize with
42
+ # @param [String] host if this isn't set it'll be resolved off the JID's
43
+ # domain
44
+ # @param [Fixnum, String] port the port to connect to.
45
+ #
46
+ # @return [Blather::Client]
47
+ def self.setup(jid, password, host = nil, port = nil)
48
+ self.new.setup(jid, password, host, port)
49
+ end
50
+
51
+ def initialize # @private
52
+ @state = :initializing
53
+
54
+ @status = Stanza::Presence::Status.new
55
+ @handlers = {}
56
+ @tmp_handlers = {}
57
+ @filters = {:before => [], :after => []}
58
+ @roster = Roster.new self
59
+
60
+ setup_initial_handlers
61
+ end
62
+
63
+ # Get the current status. Taken from the `state` attribute of Status
64
+ def status
65
+ @status.state
66
+ end
67
+
68
+ # Set the status. Status can be set with either a single value or an array
69
+ # containing
70
+ #
71
+ # [state, message, to].
72
+ def status=(state)
73
+ state, msg, to = state
74
+
75
+ status = Stanza::Presence::Status.new state, msg
76
+ status.to = to
77
+ @status = status unless to
78
+
79
+ write status
80
+ end
81
+
82
+ # Start the connection.
83
+ #
84
+ # The stream type used is based on the JID. If a node exists it uses
85
+ # Blather::Stream::Client otherwise Blather::Stream::Component
86
+ def run
87
+ raise 'not setup!' unless setup?
88
+ klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
89
+ klass.start self, *@setup
90
+ end
91
+ alias_method :connect, :run
92
+
93
+ # Register a filter to be run before or after the handler chain is run.
94
+ #
95
+ # @param [<:before, :after>] type the filter type
96
+ # @param [Symbol, nil] handler set the filter on a specific handler
97
+ # @param [guards] guards take a look at the guards documentation
98
+ # @yield [Blather::Stanza] stanza the incomming stanza
99
+ def register_filter(type, handler = nil, *guards, &filter)
100
+ unless [:before, :after].include?(type)
101
+ raise "Invalid filter: #{type}. Must be :before or :after"
102
+ end
103
+ @filters[type] << [guards, handler, filter]
104
+ end
105
+
106
+ # Register a temporary handler. Temporary handlers are based on the ID of
107
+ # the JID and live only until a stanza with said ID is received.
108
+ #
109
+ # @param [#to_s] id the ID of the stanza that should be handled
110
+ # @yield [Blather::Stanza] stanza the incomming stanza
111
+ def register_tmp_handler(id, &handler)
112
+ @tmp_handlers[id.to_s] = handler
113
+ end
114
+
115
+ # Register a handler
116
+ #
117
+ # @param [Symbol, nil] handler set the filter on a specific handler
118
+ # @param [guards] guards take a look at the guards documentation
119
+ # @yield [Blather::Stanza] stanza the incomming stanza
120
+ def register_handler(type, *guards, &handler)
121
+ check_handler type, guards
122
+ @handlers[type] ||= []
123
+ @handlers[type] << [guards, handler]
124
+ end
125
+
126
+ # Write data to the stream
127
+ #
128
+ # @param [#to_xml, #to_s] stanza the content to send down the wire
129
+ def write(stanza)
130
+ self.stream.send(stanza)
131
+ end
132
+
133
+ # Helper that will create a temporary handler for the stanza being sent
134
+ # before writing it to the stream.
135
+ #
136
+ # client.write_with_handler(stanza) { |s| "handle stanza here" }
137
+ #
138
+ # is equivalent to:
139
+ #
140
+ # client.register_tmp_handler(stanza.id) { |s| "handle stanza here" }
141
+ # client.write stanza
142
+ #
143
+ # @param [Blather::Stanza] stanza the stanza to send down the wire
144
+ # @yield [Blather::Stanza] stanza the reply stanza
145
+ def write_with_handler(stanza, &handler)
146
+ register_tmp_handler stanza.id, &handler
147
+ write stanza
148
+ end
149
+
150
+ # Close the connection
151
+ def close
152
+ self.stream.close_connection_after_writing
153
+ end
154
+
155
+ def post_init(stream, jid = nil) # @private
156
+ @stream = stream
157
+ @jid = JID.new(jid) if jid
158
+ self.jid.node ? client_post_init : ready!
159
+ end
160
+
161
+ def unbind # @private
162
+ call_handler_for(:disconnected, nil) || (EM.reactor_running? && EM.stop)
163
+ end
164
+
165
+ def receive_data(stanza) # @private
166
+ catch(:halt) do
167
+ run_filters :before, stanza
168
+ handle_stanza stanza
169
+ run_filters :after, stanza
170
+ end
171
+ end
172
+
173
+ def setup? # @private
174
+ @setup.is_a? Array
175
+ end
176
+
177
+ def setup(jid, password, host = nil, port = nil) # @private
178
+ @jid = JID.new(jid)
179
+ @setup = [@jid, password]
180
+ @setup << host if host
181
+ @setup << port if port
182
+ self
183
+ end
184
+
185
+ protected
186
+ def stream # @private
187
+ @stream || raise('Stream not ready!')
188
+ end
189
+
190
+ def check_handler(type, guards) # @private
191
+ Blather.logger.warn "Handler for type \"#{type}\" will never be called as it's not a registered type" unless current_handlers.include?(type)
192
+ check_guards guards
193
+ end
194
+
195
+ def current_handlers # @private
196
+ [:ready, :disconnected] + Stanza.handler_list + BlatherError.handler_list
197
+ end
198
+
199
+ def setup_initial_handlers # @private
200
+ register_handler :error do |err|
201
+ raise err
202
+ end
203
+
204
+ # register_handler :iq, :type => [:get, :set] do |iq|
205
+ # write StanzaError.new(iq, 'service-unavailable', :cancel).to_node
206
+ # end
207
+
208
+ register_handler :status do |status|
209
+ roster[status.from].status = status if roster[status.from]
210
+ nil
211
+ end
212
+
213
+ register_handler :roster do |node|
214
+ roster.process node
215
+ end
216
+ end
217
+
218
+ def ready! # @private
219
+ @state = :ready
220
+ call_handler_for :ready, nil
221
+ end
222
+
223
+ def client_post_init # @private
224
+ write_with_handler Stanza::Iq::Roster.new do |node|
225
+ roster.process node
226
+ write @status
227
+ ready!
228
+ end
229
+ end
230
+
231
+ def run_filters(type, stanza) # @private
232
+ @filters[type].each do |guards, handler, filter|
233
+ next if handler && !stanza.handler_hierarchy.include?(handler)
234
+ catch(:pass) { call_handler filter, guards, stanza }
235
+ end
236
+ end
237
+
238
+ def handle_stanza(stanza) # @private
239
+ if handler = @tmp_handlers.delete(stanza.id)
240
+ handler.call stanza
241
+ else
242
+ stanza.handler_hierarchy.each do |type|
243
+ break if call_handler_for(type, stanza)
244
+ end
245
+ end
246
+ end
247
+
248
+ def call_handler_for(type, stanza) # @private
249
+ return unless handler = @handlers[type]
250
+ handler.find do |guards, handler|
251
+ catch(:pass) { call_handler handler, guards, stanza }
252
+ end
253
+ end
254
+
255
+ def call_handler(handler, guards, stanza) # @private
256
+ if guards.first.respond_to?(:to_str)
257
+ result = stanza.find(*guards)
258
+ handler.call(stanza, result) unless result.empty?
259
+ else
260
+ handler.call(stanza) unless guarded?(guards, stanza)
261
+ end
262
+ end
263
+
264
+ # If any of the guards returns FALSE this returns true
265
+ # the logic is reversed to allow short circuiting
266
+ # (why would anyone want to loop over more values than necessary?)
267
+ #
268
+ # @private
269
+ def guarded?(guards, stanza)
270
+ guards.find do |guard|
271
+ case guard
272
+ when Symbol
273
+ !stanza.__send__(guard)
274
+ when Array
275
+ # return FALSE if any item is TRUE
276
+ !guard.detect { |condition| !guarded?([condition], stanza) }
277
+ when Hash
278
+ # return FALSE unless any inequality is found
279
+ guard.find do |method, test|
280
+ value = stanza.__send__(method)
281
+ # last_match is the only method found unique to Regexp classes
282
+ if test.class.respond_to?(:last_match)
283
+ !(test =~ value.to_s)
284
+ elsif test.is_a?(Array)
285
+ !test.include? value
286
+ else
287
+ test != value
288
+ end
289
+ end
290
+ when Proc
291
+ !guard.call(stanza)
292
+ end
293
+ end
294
+ end
295
+
296
+ def check_guards(guards) # @private
297
+ guards.each do |guard|
298
+ case guard
299
+ when Array
300
+ guard.each { |g| check_guards([g]) }
301
+ when Symbol, Proc, Hash, String
302
+ nil
303
+ else
304
+ raise "Bad guard: #{guard.inspect}"
305
+ end
306
+ end
307
+ end
308
+ end # Client
309
+
310
+ end # Blather
@@ -0,0 +1,170 @@
1
+ module Blather
2
+ module DSL
3
+
4
+ class PubSub
5
+ attr_accessor :host
6
+
7
+ # Create a new pubsub DSL
8
+ #
9
+ # @param [Blather::Client] client the client who's connection will be used
10
+ # @param [#to_s] host the PubSub host
11
+ def initialize(client, host)
12
+ @client = client
13
+ @host = host
14
+ end
15
+
16
+ # Retrieve Affiliations
17
+ #
18
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
19
+ # @yield [Hash] affiliations See {Blather::Stanza::PubSub::Affiliations#list}
20
+ def affiliations(host = nil, &callback)
21
+ request Stanza::PubSub::Affiliations.new(:get, send_to(host)), :list, callback
22
+ end
23
+
24
+ # Retrieve Subscriptions
25
+ #
26
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
27
+ # @yield [Hash] affiliations See {Blather::Stanza::PubSub::Subscriptions#list}
28
+ def subscriptions(host = nil, &callback)
29
+ request Stanza::PubSub::Subscriptions.new(:get, send_to(host)), :list, callback
30
+ end
31
+
32
+ # Discover Nodes
33
+ #
34
+ # @param [#to_s] path the node path
35
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
36
+ # @yield [Array<Blather::Stanza::DiscoItems::Item>] items
37
+ def nodes(path = nil, host = nil, &callback)
38
+ path ||= '/'
39
+ stanza = Stanza::DiscoItems.new(:get, path)
40
+ stanza.to = send_to(host)
41
+ request stanza, :items, callback
42
+ end
43
+
44
+ # Discover node information
45
+ #
46
+ # @param [#to_s] path the node path
47
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
48
+ # @yield [Blather::Stanza::DiscoInfo>] info
49
+ def node(path, host = nil, &callback)
50
+ stanza = Stanza::DiscoInfo.new(:get, path)
51
+ stanza.to = send_to(host)
52
+ request stanza, nil, callback
53
+ end
54
+
55
+ # Retrieve items for a node
56
+ #
57
+ # @param [#to_s] path the node path
58
+ # @param [Array<#to_s>] list a list of IDs to retrieve
59
+ # @param [Fixnum, #to_s] max the maximum number of items to return
60
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
61
+ # @yield [Array<Blather::Stanza::PubSub::PubSubItem>] items see {Blather::Stanza::PubSub::Items#items}
62
+ def items(path, list = [], max = nil, host = nil, &callback)
63
+ request(
64
+ Stanza::PubSub::Items.request(send_to(host), path, list, max),
65
+ :items,
66
+ callback
67
+ )
68
+ end
69
+
70
+ # Subscribe to a node
71
+ #
72
+ # @param [#to_s] node the node to subscribe to
73
+ # @param [Blather::JID, #to_s] jid is the jid that should be used.
74
+ # Defaults to the stripped current JID
75
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
76
+ # @yield [Blather::Stanza] stanza the reply stanza
77
+ def subscribe(node, jid = nil, host = nil)
78
+ jid ||= client.jid.stripped
79
+ stanza = Stanza::PubSub::Subscribe.new(:set, send_to(host), node, jid)
80
+ request(stanza) { |n| yield n if block_given? }
81
+ end
82
+
83
+ # Unsubscribe from a node
84
+ #
85
+ # @param [#to_s] node the node to unsubscribe from
86
+ # @param [Blather::JID, #to_s] jid is the jid that should be used.
87
+ # Defaults to the stripped current JID
88
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
89
+ # @yield [Blather::Stanza] stanza the reply stanza
90
+ def unsubscribe(node, jid = nil, host = nil)
91
+ jid ||= client.jid.stripped
92
+ stanza = Stanza::PubSub::Unsubscribe.new(:set, send_to(host), node, jid)
93
+ request(stanza) { |n| yield n if block_given? }
94
+ end
95
+
96
+ # Publish an item to a node
97
+ #
98
+ # @param [#to_s] node the node to publish to
99
+ # @param [#to_s] payload the payload to send see {Blather::Stanza::PubSub::Publish}
100
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
101
+ # @yield [Blather::Stanza] stanza the reply stanza
102
+ def publish(node, payload, host = nil)
103
+ stanza = Stanza::PubSub::Publish.new(send_to(host), node, :set, payload)
104
+ request(stanza) { |n| yield n if block_given? }
105
+ end
106
+
107
+ # Delete items from a node
108
+ #
109
+ # @param [#to_s] node the node to delete from
110
+ # @param [Array<#to_s>] ids a list of ids to delete
111
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
112
+ # @yield [Blather::Stanza] stanza the reply stanza
113
+ def retract(node, ids = [], host = nil)
114
+ stanza = Stanza::PubSub::Retract.new(send_to(host), node, :set, ids)
115
+ request(stanza) { |n| yield n if block_given? }
116
+ end
117
+
118
+ # Create a node
119
+ #
120
+ # @param [#to_s] node the node to create
121
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
122
+ # @yield [Blather::Stanza] stanza the reply stanza
123
+ def create(node, host = nil)
124
+ stanza = Stanza::PubSub::Create.new(:set, send_to(host), node)
125
+ request(stanza) { |n| yield n if block_given? }
126
+ end
127
+
128
+ # Purge all node items
129
+ #
130
+ # @param [#to_s] node the node to purge
131
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
132
+ # @yield [Blather::Stanza] stanza the reply stanza
133
+ def purge(node, host = nil)
134
+ stanza = Stanza::PubSubOwner::Purge.new(:set, send_to(host), node)
135
+ request(stanza) { |n| yield n if block_given? }
136
+ end
137
+
138
+ # Delete a node
139
+ #
140
+ # @param [#to_s] node the node to delete
141
+ # @param [#to_s] host the PubSub host (defaults to the initialized host)
142
+ # @yield [Blather::Stanza] stanza the reply stanza
143
+ def delete(node, host = nil)
144
+ stanza = Stanza::PubSubOwner::Delete.new(:set, send_to(host), node)
145
+ request(stanza) { |n| yield n if block_given? }
146
+ end
147
+
148
+ private
149
+ def request(node, method = nil, callback = nil, &block)
150
+ unless block_given?
151
+ block = lambda do |node|
152
+ callback.call(method ? node.__send__(method) : node)
153
+ end
154
+ end
155
+
156
+ client.write_with_handler(node, &block)
157
+ end
158
+
159
+ def send_to(host = nil)
160
+ raise 'You must provide a host' unless (host ||= @host)
161
+ host
162
+ end
163
+
164
+ def client
165
+ @client
166
+ end
167
+ end # PubSub
168
+
169
+ end # DSL
170
+ end # Blather