tp-blather 0.8.2
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.
- data/.autotest +13 -0
- data/.gemtest +0 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +249 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +413 -0
- data/Rakefile +20 -0
- data/TODO.md +2 -0
- data/blather.gemspec +51 -0
- data/examples/certs/README +20 -0
- data/examples/certs/ca-bundle.crt +3987 -0
- data/examples/echo.rb +19 -0
- data/examples/execute.rb +17 -0
- data/examples/ping_pong.rb +38 -0
- data/examples/print_hierarchy.rb +77 -0
- data/examples/rosterprint.rb +15 -0
- data/examples/stream_only.rb +28 -0
- data/examples/trusted_echo.rb +21 -0
- data/examples/xmpp4r/echo.rb +36 -0
- data/lib/blather.rb +112 -0
- data/lib/blather/cert_store.rb +53 -0
- data/lib/blather/client.rb +95 -0
- data/lib/blather/client/client.rb +345 -0
- data/lib/blather/client/dsl.rb +320 -0
- data/lib/blather/client/dsl/pubsub.rb +174 -0
- data/lib/blather/core_ext/eventmachine.rb +125 -0
- data/lib/blather/core_ext/ipaddr.rb +20 -0
- data/lib/blather/errors.rb +69 -0
- data/lib/blather/errors/sasl_error.rb +44 -0
- data/lib/blather/errors/stanza_error.rb +110 -0
- data/lib/blather/errors/stream_error.rb +84 -0
- data/lib/blather/file_transfer.rb +107 -0
- data/lib/blather/file_transfer/ibb.rb +68 -0
- data/lib/blather/file_transfer/s5b.rb +114 -0
- data/lib/blather/jid.rb +141 -0
- data/lib/blather/roster.rb +118 -0
- data/lib/blather/roster_item.rb +146 -0
- data/lib/blather/stanza.rb +167 -0
- data/lib/blather/stanza/disco.rb +32 -0
- data/lib/blather/stanza/disco/capabilities.rb +161 -0
- data/lib/blather/stanza/disco/disco_info.rb +205 -0
- data/lib/blather/stanza/disco/disco_items.rb +134 -0
- data/lib/blather/stanza/iq.rb +144 -0
- data/lib/blather/stanza/iq/command.rb +339 -0
- data/lib/blather/stanza/iq/ibb.rb +86 -0
- data/lib/blather/stanza/iq/ping.rb +50 -0
- data/lib/blather/stanza/iq/query.rb +53 -0
- data/lib/blather/stanza/iq/roster.rb +185 -0
- data/lib/blather/stanza/iq/s5b.rb +208 -0
- data/lib/blather/stanza/iq/si.rb +415 -0
- data/lib/blather/stanza/iq/vcard.rb +149 -0
- data/lib/blather/stanza/message.rb +428 -0
- data/lib/blather/stanza/message/muc_user.rb +119 -0
- data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
- data/lib/blather/stanza/presence.rb +172 -0
- data/lib/blather/stanza/presence/c.rb +100 -0
- data/lib/blather/stanza/presence/muc.rb +35 -0
- data/lib/blather/stanza/presence/muc_user.rb +147 -0
- data/lib/blather/stanza/presence/status.rb +218 -0
- data/lib/blather/stanza/presence/subscription.rb +100 -0
- data/lib/blather/stanza/pubsub.rb +119 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
- data/lib/blather/stanza/pubsub/create.rb +65 -0
- data/lib/blather/stanza/pubsub/errors.rb +18 -0
- data/lib/blather/stanza/pubsub/event.rb +139 -0
- data/lib/blather/stanza/pubsub/items.rb +103 -0
- data/lib/blather/stanza/pubsub/publish.rb +103 -0
- data/lib/blather/stanza/pubsub/retract.rb +92 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
- data/lib/blather/stanza/pubsub/subscription.rb +135 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
- data/lib/blather/stanza/pubsub_owner.rb +51 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
- data/lib/blather/stanza/x.rb +416 -0
- data/lib/blather/stream.rb +266 -0
- data/lib/blather/stream/client.rb +32 -0
- data/lib/blather/stream/component.rb +39 -0
- data/lib/blather/stream/features.rb +70 -0
- data/lib/blather/stream/features/register.rb +38 -0
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/features/sasl.rb +190 -0
- data/lib/blather/stream/features/session.rb +45 -0
- data/lib/blather/stream/features/tls.rb +29 -0
- data/lib/blather/stream/parser.rb +102 -0
- data/lib/blather/version.rb +3 -0
- data/lib/blather/xmpp_node.rb +94 -0
- data/spec/blather/client/client_spec.rb +687 -0
- data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
- data/spec/blather/client/dsl_spec.rb +266 -0
- data/spec/blather/errors/sasl_error_spec.rb +33 -0
- data/spec/blather/errors/stanza_error_spec.rb +129 -0
- data/spec/blather/errors/stream_error_spec.rb +108 -0
- data/spec/blather/errors_spec.rb +33 -0
- data/spec/blather/file_transfer_spec.rb +135 -0
- data/spec/blather/jid_spec.rb +87 -0
- data/spec/blather/roster_item_spec.rb +134 -0
- data/spec/blather/roster_spec.rb +107 -0
- data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
- data/spec/blather/stanza/iq/command_spec.rb +206 -0
- data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
- data/spec/blather/stanza/iq/ping_spec.rb +45 -0
- data/spec/blather/stanza/iq/query_spec.rb +64 -0
- data/spec/blather/stanza/iq/roster_spec.rb +139 -0
- data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
- data/spec/blather/stanza/iq/si_spec.rb +98 -0
- data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
- data/spec/blather/stanza/iq_spec.rb +61 -0
- data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
- data/spec/blather/stanza/message_spec.rb +282 -0
- data/spec/blather/stanza/presence/c_spec.rb +56 -0
- data/spec/blather/stanza/presence/muc_spec.rb +37 -0
- data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
- data/spec/blather/stanza/presence/status_spec.rb +144 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
- data/spec/blather/stanza/presence_spec.rb +125 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +68 -0
- data/spec/blather/stanza/x_spec.rb +231 -0
- data/spec/blather/stanza_spec.rb +134 -0
- data/spec/blather/stream/client_spec.rb +1090 -0
- data/spec/blather/stream/component_spec.rb +108 -0
- data/spec/blather/stream/parser_spec.rb +152 -0
- data/spec/blather/stream/ssl_spec.rb +32 -0
- data/spec/blather/xmpp_node_spec.rb +47 -0
- data/spec/blather_spec.rb +34 -0
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +17 -0
- data/yard/templates/default/class/html/handlers.erb +18 -0
- data/yard/templates/default/class/setup.rb +10 -0
- data/yard/templates/default/class/text/handlers.erb +1 -0
- metadata +459 -0
|
@@ -0,0 +1,345 @@
|
|
|
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
|
+
:caps
|
|
38
|
+
|
|
39
|
+
# Create a new client and set it up
|
|
40
|
+
#
|
|
41
|
+
# @param [Blather::JID, #to_s] jid the JID to authorize with
|
|
42
|
+
# @param [String] password the password to authorize with
|
|
43
|
+
# @param [String] host if this isn't set it'll be resolved off the JID's
|
|
44
|
+
# domain
|
|
45
|
+
# @param [Fixnum, String] port the port to connect to.
|
|
46
|
+
#
|
|
47
|
+
# @return [Blather::Client]
|
|
48
|
+
def self.setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil)
|
|
49
|
+
self.new.setup(jid, password, host, port, certs, connect_timeout)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def initialize # @private
|
|
53
|
+
@state = :initializing
|
|
54
|
+
|
|
55
|
+
@status = Stanza::Presence::Status.new
|
|
56
|
+
@handlers = {}
|
|
57
|
+
@tmp_handlers = {}
|
|
58
|
+
@filters = {:before => [], :after => []}
|
|
59
|
+
@roster = Roster.new self
|
|
60
|
+
@caps = Stanza::Capabilities.new
|
|
61
|
+
|
|
62
|
+
@handler_queue = GirlFriday::WorkQueue.new :handle_stanza, :size => 5 do |stanza|
|
|
63
|
+
handle_data stanza
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
setup_initial_handlers
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check whether the client is currently connected.
|
|
70
|
+
def connected?
|
|
71
|
+
setup? && !@stream.nil? && !@stream.stopped?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get the current status. Taken from the `state` attribute of Status
|
|
75
|
+
def status
|
|
76
|
+
@status.state
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Set the status. Status can be set with either a single value or an array
|
|
80
|
+
# containing
|
|
81
|
+
#
|
|
82
|
+
# [state, message, to].
|
|
83
|
+
def status=(state)
|
|
84
|
+
state, msg, to = state
|
|
85
|
+
|
|
86
|
+
status = Stanza::Presence::Status.new state, msg
|
|
87
|
+
status.to = to
|
|
88
|
+
@status = status unless to
|
|
89
|
+
|
|
90
|
+
write status
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Start the connection.
|
|
94
|
+
#
|
|
95
|
+
# The stream type used is based on the JID. If a node exists it uses
|
|
96
|
+
# Blather::Stream::Client otherwise Blather::Stream::Component
|
|
97
|
+
def run
|
|
98
|
+
raise 'not setup!' unless setup?
|
|
99
|
+
klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
|
|
100
|
+
klass.start self, *@setup
|
|
101
|
+
end
|
|
102
|
+
alias_method :connect, :run
|
|
103
|
+
|
|
104
|
+
# Register a filter to be run before or after the handler chain is run.
|
|
105
|
+
#
|
|
106
|
+
# @param [<:before, :after>] type the filter type
|
|
107
|
+
# @param [Symbol, nil] handler set the filter on a specific handler
|
|
108
|
+
# @param [guards] guards take a look at the guards documentation
|
|
109
|
+
# @yield [Blather::Stanza] stanza the incomming stanza
|
|
110
|
+
def register_filter(type, handler = nil, *guards, &filter)
|
|
111
|
+
unless [:before, :after].include?(type)
|
|
112
|
+
raise "Invalid filter: #{type}. Must be :before or :after"
|
|
113
|
+
end
|
|
114
|
+
@filters[type] << [guards, handler, filter]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Register a temporary handler. Temporary handlers are based on the ID of
|
|
118
|
+
# the JID and live only until a stanza with said ID is received.
|
|
119
|
+
#
|
|
120
|
+
# @param [#to_s] id the ID of the stanza that should be handled
|
|
121
|
+
# @yield [Blather::Stanza] stanza the incomming stanza
|
|
122
|
+
def register_tmp_handler(id, &handler)
|
|
123
|
+
@tmp_handlers[id.to_s] = handler
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Clear handlers with given guards
|
|
127
|
+
#
|
|
128
|
+
# @param [Symbol, nil] type remove filters for a specific handler
|
|
129
|
+
# @param [guards] guards take a look at the guards documentation
|
|
130
|
+
def clear_handlers(type, *guards)
|
|
131
|
+
@handlers[type].delete_if { |g, _| g == guards }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Register a handler
|
|
135
|
+
#
|
|
136
|
+
# @param [Symbol, nil] type set the filter on a specific handler
|
|
137
|
+
# @param [guards] guards take a look at the guards documentation
|
|
138
|
+
# @yield [Blather::Stanza] stanza the incomming stanza
|
|
139
|
+
def register_handler(type, *guards, &handler)
|
|
140
|
+
check_handler type, guards
|
|
141
|
+
@handlers[type] ||= []
|
|
142
|
+
@handlers[type] << [guards, handler]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Write data to the stream
|
|
146
|
+
#
|
|
147
|
+
# @param [#to_xml, #to_s] stanza the content to send down the wire
|
|
148
|
+
def write(stanza)
|
|
149
|
+
self.stream.send(stanza)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Helper that will create a temporary handler for the stanza being sent
|
|
153
|
+
# before writing it to the stream.
|
|
154
|
+
#
|
|
155
|
+
# client.write_with_handler(stanza) { |s| "handle stanza here" }
|
|
156
|
+
#
|
|
157
|
+
# is equivalent to:
|
|
158
|
+
#
|
|
159
|
+
# client.register_tmp_handler(stanza.id) { |s| "handle stanza here" }
|
|
160
|
+
# client.write stanza
|
|
161
|
+
#
|
|
162
|
+
# @param [Blather::Stanza] stanza the stanza to send down the wire
|
|
163
|
+
# @yield [Blather::Stanza] stanza the reply stanza
|
|
164
|
+
def write_with_handler(stanza, &handler)
|
|
165
|
+
register_tmp_handler stanza.id, &handler
|
|
166
|
+
write stanza
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Close the connection
|
|
170
|
+
def close
|
|
171
|
+
self.stream.close_connection_after_writing
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# @private
|
|
175
|
+
def post_init(stream, jid = nil)
|
|
176
|
+
@stream = stream
|
|
177
|
+
@jid = JID.new(jid) if jid
|
|
178
|
+
self.jid.node ? client_post_init : ready!
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# @private
|
|
182
|
+
def unbind
|
|
183
|
+
call_handler_for(:disconnected, nil) || (EM.reactor_running? && EM.stop)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @private
|
|
187
|
+
def receive_data(stanza)
|
|
188
|
+
@handler_queue << stanza
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def handle_data(stanza)
|
|
192
|
+
catch(:halt) do
|
|
193
|
+
run_filters :before, stanza
|
|
194
|
+
handle_stanza stanza
|
|
195
|
+
run_filters :after, stanza
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @private
|
|
200
|
+
def setup?
|
|
201
|
+
@setup.is_a? Array
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @private
|
|
205
|
+
def setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil)
|
|
206
|
+
@jid = JID.new(jid)
|
|
207
|
+
@setup = [@jid, password]
|
|
208
|
+
@setup << host
|
|
209
|
+
@setup << port
|
|
210
|
+
@setup << certs
|
|
211
|
+
@setup << connect_timeout
|
|
212
|
+
self
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
protected
|
|
216
|
+
|
|
217
|
+
def stream
|
|
218
|
+
@stream || raise('Stream not ready!')
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def check_handler(type, guards)
|
|
222
|
+
Blather.logger.warn "Handler for type \"#{type}\" will never be called as it's not a registered type" unless current_handlers.include?(type)
|
|
223
|
+
check_guards guards
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def current_handlers
|
|
227
|
+
[:ready, :disconnected] + Stanza.handler_list + BlatherError.handler_list
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def setup_initial_handlers
|
|
231
|
+
register_handler :error do |err|
|
|
232
|
+
raise err
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# register_handler :iq, :type => [:get, :set] do |iq|
|
|
236
|
+
# write StanzaError.new(iq, 'service-unavailable', :cancel).to_node
|
|
237
|
+
# end
|
|
238
|
+
|
|
239
|
+
register_handler :ping, :type => :get do |ping|
|
|
240
|
+
write ping.reply
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
register_handler :status do |status|
|
|
244
|
+
roster[status.from].status = status if roster[status.from]
|
|
245
|
+
nil
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
register_handler :roster do |node|
|
|
249
|
+
roster.process node
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def ready!
|
|
254
|
+
@state = :ready
|
|
255
|
+
call_handler_for :ready, nil
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def client_post_init
|
|
259
|
+
write_with_handler Stanza::Iq::Roster.new do |node|
|
|
260
|
+
roster.process node
|
|
261
|
+
write @status
|
|
262
|
+
ready!
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def run_filters(type, stanza)
|
|
267
|
+
@filters[type].each do |guards, handler, filter|
|
|
268
|
+
next if handler && !stanza.handler_hierarchy.include?(handler)
|
|
269
|
+
catch(:pass) { call_handler filter, guards, stanza }
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def handle_stanza(stanza)
|
|
274
|
+
if handler = @tmp_handlers.delete(stanza.id)
|
|
275
|
+
handler.call stanza
|
|
276
|
+
else
|
|
277
|
+
stanza.handler_hierarchy.each do |type|
|
|
278
|
+
break if call_handler_for(type, stanza)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def call_handler_for(type, stanza)
|
|
284
|
+
return unless handler = @handlers[type]
|
|
285
|
+
handler.find do |guards, handler|
|
|
286
|
+
catch(:pass) { call_handler handler, guards, stanza }
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def call_handler(handler, guards, stanza)
|
|
291
|
+
if guards.first.respond_to?(:to_str)
|
|
292
|
+
result = stanza.find(*guards)
|
|
293
|
+
handler.call(stanza, result) unless result.empty?
|
|
294
|
+
else
|
|
295
|
+
handler.call(stanza) unless guarded?(guards, stanza)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# If any of the guards returns FALSE this returns true
|
|
300
|
+
# the logic is reversed to allow short circuiting
|
|
301
|
+
# (why would anyone want to loop over more values than necessary?)
|
|
302
|
+
#
|
|
303
|
+
# @private
|
|
304
|
+
def guarded?(guards, stanza)
|
|
305
|
+
guards.find do |guard|
|
|
306
|
+
case guard
|
|
307
|
+
when Symbol
|
|
308
|
+
!stanza.__send__(guard)
|
|
309
|
+
when Array
|
|
310
|
+
# return FALSE if any item is TRUE
|
|
311
|
+
!guard.detect { |condition| !guarded?([condition], stanza) }
|
|
312
|
+
when Hash
|
|
313
|
+
# return FALSE unless any inequality is found
|
|
314
|
+
guard.find do |method, test|
|
|
315
|
+
value = stanza.__send__(method)
|
|
316
|
+
# last_match is the only method found unique to Regexp classes
|
|
317
|
+
if test.class.respond_to?(:last_match)
|
|
318
|
+
!(test =~ value.to_s)
|
|
319
|
+
elsif test.is_a?(Array)
|
|
320
|
+
!test.include? value
|
|
321
|
+
else
|
|
322
|
+
test != value
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
when Proc
|
|
326
|
+
!guard.call(stanza)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def check_guards(guards)
|
|
332
|
+
guards.each do |guard|
|
|
333
|
+
case guard
|
|
334
|
+
when Array
|
|
335
|
+
guard.each { |g| check_guards([g]) }
|
|
336
|
+
when Symbol, Proc, Hash, String
|
|
337
|
+
nil
|
|
338
|
+
else
|
|
339
|
+
raise "Bad guard: #{guard.inspect}"
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end # Client
|
|
344
|
+
|
|
345
|
+
end # Blather
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'client')
|
|
2
|
+
|
|
3
|
+
module Blather
|
|
4
|
+
|
|
5
|
+
# # Blather DSL
|
|
6
|
+
#
|
|
7
|
+
# The DSL is a set of methods that enables you to write cleaner code. Being a
|
|
8
|
+
# module means it can be included in or extend any class you may want to
|
|
9
|
+
# create.
|
|
10
|
+
#
|
|
11
|
+
# Every stanza handler is registered as a method on the DSL.
|
|
12
|
+
#
|
|
13
|
+
# @example Include the DSL in the top level namespace.
|
|
14
|
+
#
|
|
15
|
+
# require 'blather/client'
|
|
16
|
+
# when_ready { puts "Connected ! send messages to #{jid.stripped}." }
|
|
17
|
+
#
|
|
18
|
+
# subscription :request? do |s|
|
|
19
|
+
# write_to_stream s.approve!
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# message :chat?, :body => 'exit' do |m|
|
|
23
|
+
# say m.from, 'Exiting ...'
|
|
24
|
+
# shutdown
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# message :chat?, :body do |m|
|
|
28
|
+
# say m.from, "You sent: #{m.body}"
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @example Set the DSL to its own namespace.
|
|
32
|
+
#
|
|
33
|
+
# require 'blather/client/dsl'
|
|
34
|
+
# module Echo
|
|
35
|
+
# extend Blather::DSL
|
|
36
|
+
#
|
|
37
|
+
# when_ready { puts "Connected ! send messages to #{jid.stripped}." }
|
|
38
|
+
#
|
|
39
|
+
# subscription :request? do |s|
|
|
40
|
+
# write_to_stream s.approve!
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# message :chat?, :body => 'exit' do |m|
|
|
44
|
+
# say m.from, 'Exiting ...'
|
|
45
|
+
# shutdown
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# message :chat?, :body do |m|
|
|
49
|
+
# say m.from, "You sent: #{m.body}"
|
|
50
|
+
# end
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# Echo.setup 'foo@bar.com', 'foobar'
|
|
54
|
+
#
|
|
55
|
+
# EM.run { Echo.run }
|
|
56
|
+
#
|
|
57
|
+
# @example Create a class out of it
|
|
58
|
+
#
|
|
59
|
+
# require 'blather/client/dsl'
|
|
60
|
+
# class Echo
|
|
61
|
+
# include Blather::DSL
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# echo = Echo.new
|
|
65
|
+
# echo.setup 'foo@bar.com', 'foobar'
|
|
66
|
+
# echo.when_ready { puts "Connected ! send messages to #{jid.stripped}." }
|
|
67
|
+
#
|
|
68
|
+
# echo.subscription :request? do |s|
|
|
69
|
+
# write_to_stream s.approve!
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# echo.message :chat?, :body => 'exit' do |m|
|
|
73
|
+
# say m.from, 'Exiting ...'
|
|
74
|
+
# shutdown
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# echo.message :chat?, :body do |m|
|
|
78
|
+
# say m.from, "You sent: #{m.body}"
|
|
79
|
+
# end
|
|
80
|
+
#
|
|
81
|
+
# EM.run { echo.run }
|
|
82
|
+
#
|
|
83
|
+
module DSL
|
|
84
|
+
|
|
85
|
+
autoload :PubSub, File.expand_path(File.join(File.dirname(__FILE__), *%w[dsl pubsub]))
|
|
86
|
+
|
|
87
|
+
def self.append_features(o)
|
|
88
|
+
Blather::Stanza.handler_list.each do |handler_name|
|
|
89
|
+
o.__send__ :remove_method, handler_name if !o.is_a?(Class) && o.method_defined?(handler_name)
|
|
90
|
+
end
|
|
91
|
+
super
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The actual client connection
|
|
95
|
+
#
|
|
96
|
+
# @return [Blather::Client]
|
|
97
|
+
def client
|
|
98
|
+
@client ||= Client.new
|
|
99
|
+
end
|
|
100
|
+
module_function :client
|
|
101
|
+
|
|
102
|
+
# A pubsub helper
|
|
103
|
+
#
|
|
104
|
+
# @return [Blather::PubSub]
|
|
105
|
+
def pubsub
|
|
106
|
+
@pubsub ||= PubSub.new client, jid.domain
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Push data to the stream
|
|
110
|
+
# This works such that it can be chained:
|
|
111
|
+
# self << stanza1 << stanza2 << "raw data"
|
|
112
|
+
#
|
|
113
|
+
# @param [#to_xml, #to_s] stanza data to send down the wire
|
|
114
|
+
# @return [self]
|
|
115
|
+
def <<(stanza)
|
|
116
|
+
client.write stanza
|
|
117
|
+
self
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Prepare server settings
|
|
121
|
+
#
|
|
122
|
+
# @param [#to_s] jid the JID to authenticate with
|
|
123
|
+
# @param [#to_s] password the password to authenticate with
|
|
124
|
+
# @param [String] host (optional) the host to connect to (can be an IP). If
|
|
125
|
+
# this is `nil` the domain on the JID will be used
|
|
126
|
+
# @param [Fixnum, String] (optional) port the port to connect on
|
|
127
|
+
# @param [Fixnum] (optional) connection_timeout the time to wait for connection to succeed before timing out
|
|
128
|
+
def setup(jid, password, host = nil, port = nil, certs = nil, connection_timeout = nil)
|
|
129
|
+
client.setup(jid, password, host, port, certs, connection_timeout)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Connect to the server. Must be run in the EventMachine reactor
|
|
133
|
+
def run
|
|
134
|
+
client.run
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Shutdown the connection.
|
|
138
|
+
# Flushes the write buffer then stops EventMachine
|
|
139
|
+
def shutdown
|
|
140
|
+
client.close
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Setup a before filter
|
|
144
|
+
#
|
|
145
|
+
# @param [Symbol] handler (optional) the stanza handler the filter should
|
|
146
|
+
# run before
|
|
147
|
+
# @param [guards] guards (optional) a set of guards to check the stanza
|
|
148
|
+
# against
|
|
149
|
+
# @yield [Blather::Stanza] stanza
|
|
150
|
+
def before(handler = nil, *guards, &block)
|
|
151
|
+
client.register_filter :before, handler, *guards, &block
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Setup an after filter
|
|
155
|
+
#
|
|
156
|
+
# @param [Symbol] handler (optional) the stanza handler the filter should
|
|
157
|
+
# run after
|
|
158
|
+
# @param [guards] guards (optional) a set of guards to check the stanza
|
|
159
|
+
# against
|
|
160
|
+
# @yield [Blather::Stanza] stanza
|
|
161
|
+
def after(handler = nil, *guards, &block)
|
|
162
|
+
client.register_filter :after, handler, *guards, &block
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Set handler for a stanza type
|
|
166
|
+
#
|
|
167
|
+
# @param [Symbol] handler the stanza type it should handle
|
|
168
|
+
# @param [guards] guards (optional) a set of guards to check the stanza
|
|
169
|
+
# against
|
|
170
|
+
# @yield [Blather::Stanza] stanza
|
|
171
|
+
def handle(handler, *guards, &block)
|
|
172
|
+
client.register_handler handler, *guards, &block
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Wrapper for "handle :ready" (just a bit of syntactic sugar)
|
|
176
|
+
#
|
|
177
|
+
# This is run after the connection has been completely setup
|
|
178
|
+
def when_ready(&block)
|
|
179
|
+
handle :ready, &block
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Wrapper for "handle :disconnected"
|
|
183
|
+
#
|
|
184
|
+
# This is run after the connection has been shut down.
|
|
185
|
+
#
|
|
186
|
+
# @example Reconnect after a disconnection
|
|
187
|
+
# disconnected { client.run }
|
|
188
|
+
def disconnected(&block)
|
|
189
|
+
handle :disconnected, &block
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Set current status
|
|
193
|
+
#
|
|
194
|
+
# @param [Blather::Stanza::Presence::State::VALID_STATES] state the current
|
|
195
|
+
# state
|
|
196
|
+
# @param [#to_s] msg the status message to use
|
|
197
|
+
def set_status(state = nil, msg = nil)
|
|
198
|
+
client.status = state, msg
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Direct access to the roster
|
|
202
|
+
#
|
|
203
|
+
# @return [Blather::Roster]
|
|
204
|
+
def my_roster
|
|
205
|
+
client.roster
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Write data to the stream
|
|
209
|
+
#
|
|
210
|
+
# @param [#to_xml, #to_s] stanza the data to send down the wire.
|
|
211
|
+
def write_to_stream(stanza)
|
|
212
|
+
client.write stanza
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Helper method to join a MUC room
|
|
216
|
+
#
|
|
217
|
+
# @overload join(room_jid, nickname)
|
|
218
|
+
# @param [Blather::JID, #to_s] room the JID of the room to join
|
|
219
|
+
# @param [#to_s] nickname the nickname to join the room as
|
|
220
|
+
# @overload join(room_jid, nickname)
|
|
221
|
+
# @param [#to_s] room the name of the room to join
|
|
222
|
+
# @param [Blather::JID, #to_s] service the service domain the room is hosted at
|
|
223
|
+
# @param [#to_s] nickname the nickname to join the room as
|
|
224
|
+
def join(room, service, nickname = nil)
|
|
225
|
+
join = Blather::Stanza::Presence::MUC.new
|
|
226
|
+
join.to = if nickname
|
|
227
|
+
"#{room}@#{service}/#{nickname}"
|
|
228
|
+
else
|
|
229
|
+
"#{room}/#{service}"
|
|
230
|
+
end
|
|
231
|
+
client.write join
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Helper method to make sending basic messages easier
|
|
235
|
+
#
|
|
236
|
+
# @param [Blather::JID, #to_s] to the JID of the message recipient
|
|
237
|
+
# @param [#to_s] msg the message to send
|
|
238
|
+
# @param [#to_sym] the stanza method to use
|
|
239
|
+
def say(to, msg, using = :chat)
|
|
240
|
+
client.write Blather::Stanza::Message.new(to, msg, using)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# The JID according to the server
|
|
244
|
+
#
|
|
245
|
+
# @return [Blather::JID]
|
|
246
|
+
def jid
|
|
247
|
+
client.jid
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Halt the handler chain
|
|
251
|
+
#
|
|
252
|
+
# Use this to stop the propogation of the stanza though the handler chain.
|
|
253
|
+
#
|
|
254
|
+
# @example Ignore all IQ stanzas
|
|
255
|
+
#
|
|
256
|
+
# before(:iq) { halt }
|
|
257
|
+
def halt
|
|
258
|
+
throw :halt
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Pass responsibility to the next handler
|
|
262
|
+
#
|
|
263
|
+
# Use this to jump out of the current handler and let the next registered
|
|
264
|
+
# handler take care of the stanza
|
|
265
|
+
#
|
|
266
|
+
# @example Pass a message to the next handler
|
|
267
|
+
#
|
|
268
|
+
# This is contrive and should be handled with guards, but pass a message
|
|
269
|
+
# to the next handler based on the content
|
|
270
|
+
#
|
|
271
|
+
# message { |s| puts "message caught" }
|
|
272
|
+
# message { |s| pass if s.body =~ /pass along/ }
|
|
273
|
+
def pass
|
|
274
|
+
throw :pass
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Request items or info from an entity
|
|
278
|
+
# discover (items|info), [jid], [node] do |response|
|
|
279
|
+
# end
|
|
280
|
+
def discover(what, who, where, &callback)
|
|
281
|
+
stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
|
|
282
|
+
stanza.to = who
|
|
283
|
+
stanza.node = where
|
|
284
|
+
|
|
285
|
+
client.register_tmp_handler stanza.id, &callback
|
|
286
|
+
client.write stanza
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Set the capabilities of the client
|
|
290
|
+
#
|
|
291
|
+
# @param [String] node the URI
|
|
292
|
+
# @param [Array<Hash>] identities an array of identities
|
|
293
|
+
# @param [Array<Hash>] features an array of features
|
|
294
|
+
def set_caps(node, identities, features)
|
|
295
|
+
client.caps.node = node
|
|
296
|
+
client.caps.identities = identities
|
|
297
|
+
client.caps.features = features
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Send capabilities to the server
|
|
301
|
+
def send_caps
|
|
302
|
+
client.register_handler :disco_info, :type => :get, :node => client.caps.node do |s|
|
|
303
|
+
r = client.caps.dup
|
|
304
|
+
r.to = s.from
|
|
305
|
+
r.id = s.id
|
|
306
|
+
client.write r
|
|
307
|
+
end
|
|
308
|
+
client.write client.caps.c
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Generate a method for every stanza handler that exists.
|
|
312
|
+
Blather::Stanza.handler_list.each do |handler_name|
|
|
313
|
+
module_eval <<-METHOD, __FILE__, __LINE__
|
|
314
|
+
def #{handler_name}(*args, &callback)
|
|
315
|
+
handle :#{handler_name}, *args, &callback
|
|
316
|
+
end
|
|
317
|
+
METHOD
|
|
318
|
+
end
|
|
319
|
+
end # DSL
|
|
320
|
+
end # Blather
|