shingara-blather 0.4.8

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