blather 0.4.1 → 0.4.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/README.rdoc +16 -1
- data/examples/echo.rb +1 -1
- data/examples/ping.rb +11 -0
- data/examples/pong.rb +6 -0
- data/lib/blather/client.rb +5 -3
- data/lib/blather/client/client.rb +150 -51
- data/lib/blather/client/dsl.rb +24 -0
- data/lib/blather/client/dsl/pubsub.rb +16 -16
- data/lib/blather/errors.rb +14 -3
- data/lib/blather/jid.rb +14 -22
- data/lib/blather/stanza.rb +0 -16
- data/lib/blather/stanza/iq.rb +46 -5
- data/lib/blather/stanza/message.rb +141 -9
- data/lib/blather/stanza/presence.rb +49 -5
- data/lib/blather/stanza/presence/status.rb +68 -8
- data/lib/blather/stream/client.rb +6 -0
- data/lib/blather/stream/features.rb +1 -1
- data/spec/blather/client/client_spec.rb +131 -0
- data/spec/blather/client/dsl_spec.rb +20 -0
- data/spec/blather/stanza/message_spec.rb +17 -6
- data/spec/blather/stream/client_spec.rb +20 -0
- data/spec/blather/stream/component_spec.rb +1 -1
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -101,6 +101,21 @@ The different types of guards are:
|
|
101
101
|
# Equivalent to !stanza.find('/iq/ns:pubsub', :ns => 'pubsub:namespace').empty?
|
102
102
|
iq '/iq/ns:pubsub', :ns => 'pubsub:namespace'
|
103
103
|
|
104
|
+
=== Filters
|
105
|
+
|
106
|
+
Blather provides before and after filters that work much the way regular handlers work. Filters come in a before and after
|
107
|
+
flavor. They're called in order of definition and can be guarded like handlers.
|
108
|
+
|
109
|
+
before { |s| "I'm run before any handler" }
|
110
|
+
before { |s| "I'm run next" }
|
111
|
+
|
112
|
+
before(:message) { |s| "I'm only run in front of message stanzas" }
|
113
|
+
before(nil, :id => 1) { |s| "I'll only be run when the stanza's ID == 1" }
|
114
|
+
|
115
|
+
# ... handlers
|
116
|
+
|
117
|
+
after { |s| "I'm run after everything" }
|
118
|
+
|
104
119
|
== On the Command Line:
|
105
120
|
|
106
121
|
Default usage is:
|
@@ -126,7 +141,7 @@ Command line options:
|
|
126
141
|
|
127
142
|
Jeff Smick <sprsquish@gmail.com>
|
128
143
|
|
129
|
-
|
144
|
+
=== Contributors
|
130
145
|
|
131
146
|
Nolan Darilek <nolan@thewordnerd.info>
|
132
147
|
|
data/examples/echo.rb
CHANGED
data/examples/ping.rb
ADDED
data/examples/pong.rb
ADDED
data/lib/blather/client.rb
CHANGED
@@ -7,8 +7,8 @@ options = {}
|
|
7
7
|
optparse = OptionParser.new do |opts|
|
8
8
|
opts.banner = "Run with #{$0} [options] user@server/resource password [host] [port]"
|
9
9
|
|
10
|
-
opts.on('-D', '--debug', 'Run in debug mode (you will see all XMPP communication)') do
|
11
|
-
options[:debug] =
|
10
|
+
opts.on('-D', '--debug', 'Run in debug mode (you will see all XMPP communication)') do
|
11
|
+
options[:debug] = true
|
12
12
|
end
|
13
13
|
|
14
14
|
opts.on('-d', '--daemonize', 'Daemonize the process') do |daemonize|
|
@@ -60,10 +60,12 @@ at_exit do
|
|
60
60
|
if options[:log]
|
61
61
|
log = File.new(options[:log], 'a')
|
62
62
|
log.sync = options[:debug]
|
63
|
-
Blather.logger.level = Logger::DEBUG if options[:debug]
|
64
63
|
$stdout.reopen log
|
65
64
|
$stderr.reopen $stdout
|
66
65
|
end
|
66
|
+
|
67
|
+
Blather.logger.level = Logger::DEBUG if options[:debug]
|
68
|
+
|
67
69
|
trap(:INT) { EM.stop }
|
68
70
|
trap(:TERM) { EM.stop }
|
69
71
|
EM.run { client.run }
|
@@ -2,29 +2,67 @@ require File.join(File.dirname(__FILE__), *%w[.. .. blather])
|
|
2
2
|
|
3
3
|
module Blather #:nodoc:
|
4
4
|
|
5
|
-
|
5
|
+
# = Blather Client
|
6
|
+
#
|
7
|
+
# Blather's Client class provides a set of helpers for working with common XMPP tasks such as setting up and starting
|
8
|
+
# the connection, settings status, registering and dispatching filters and handlers and roster management.
|
9
|
+
#
|
10
|
+
# Client can be used separately from the DSL if you'd like to implement your own DSL
|
11
|
+
# Here's the echo example using the client without the DSL:
|
12
|
+
#
|
13
|
+
# require 'blather/client/client'
|
14
|
+
# client = Client.setup 'echo@jabber.local', 'echo'
|
15
|
+
#
|
16
|
+
# client.register_handler(:ready) { puts "Connected ! send messages to #{client.jid.stripped}." }
|
17
|
+
#
|
18
|
+
# client.register_handler :subscription, :request? do |s|
|
19
|
+
# client.write s.approve!
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# client.register_handler :message, :chat?, :body => 'exit' do |m|
|
23
|
+
# client.write Blather::Stanza::Message.new(m.from, 'Exiting...')
|
24
|
+
# client.close
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# client.register_handler :message, :chat?, :body do |m|
|
28
|
+
# client.write Blather::Stanza::Message.new(m.from, "You sent: #{m.body}")
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
class Client
|
6
32
|
attr_reader :jid,
|
7
33
|
:roster
|
8
34
|
|
9
|
-
|
35
|
+
##
|
36
|
+
# Initialize and setup the client
|
37
|
+
# * +jid+ - the JID to login with
|
38
|
+
# * +password+ - password associated with the JID
|
39
|
+
# * +host+ - hostname or IP to connect to. If nil the stream will look one up based on the domain in the JID
|
40
|
+
# * +port+ - port to connect to
|
41
|
+
def self.setup(jid, password, host = nil, port = nil)
|
42
|
+
self.new.setup(jid, password, host, port)
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize # :nodoc:
|
10
46
|
@state = :initializing
|
11
47
|
|
12
48
|
@status = Stanza::Presence::Status.new
|
13
49
|
@handlers = {}
|
14
50
|
@tmp_handlers = {}
|
51
|
+
@filters = {:before => [], :after => []}
|
15
52
|
@roster = Roster.new self
|
16
53
|
|
17
54
|
setup_initial_handlers
|
18
55
|
end
|
19
56
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
57
|
+
##
|
58
|
+
# Get the current status. Taken from the +state+ attribute of Status
|
24
59
|
def status
|
25
60
|
@status.state
|
26
61
|
end
|
27
62
|
|
63
|
+
##
|
64
|
+
# Set the status. Status can be set with either a single value or an array containing
|
65
|
+
# [state, message, to].
|
28
66
|
def status=(state)
|
29
67
|
state, msg, to = state
|
30
68
|
|
@@ -35,77 +73,121 @@ module Blather #:nodoc:
|
|
35
73
|
write status
|
36
74
|
end
|
37
75
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
def setup(jid, password, host = nil, port = nil)
|
43
|
-
@jid = JID.new(jid)
|
44
|
-
@setup = [@jid, password]
|
45
|
-
@setup << host if host
|
46
|
-
@setup << port if port
|
47
|
-
self
|
48
|
-
end
|
49
|
-
|
76
|
+
##
|
77
|
+
# Start the connection.
|
50
78
|
def run
|
51
79
|
raise 'not setup!' unless setup?
|
52
80
|
klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
|
53
81
|
@stream = klass.start self, *@setup
|
54
82
|
end
|
55
83
|
|
84
|
+
##
|
85
|
+
# Register a filter to be run before or after the handler chain is run.
|
86
|
+
# * +type+ - the type of filter. Must be +:before+ or +:after+
|
87
|
+
# * +guards+ - guards that should be checked before the filter is called
|
88
|
+
def register_filter(type, handler = nil, *guards, &filter)
|
89
|
+
raise "Invalid filter: #{type}. Must be :before or :after" unless [:before, :after].include?(type)
|
90
|
+
@filters[type] << [guards, handler, filter]
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Register a temporary handler. Temporary handlers are based on the ID of the JID and live
|
95
|
+
# only until a stanza with said ID is received.
|
96
|
+
# * +id+ - the ID of the stanza that should be handled
|
56
97
|
def register_tmp_handler(id, &handler)
|
57
98
|
@tmp_handlers[id] = handler
|
58
99
|
end
|
59
100
|
|
101
|
+
##
|
102
|
+
# Register a handler
|
103
|
+
# * +type+ - the handler type. Should be registered in Stanza.handler_list. Blather will log a warning if it's not.
|
104
|
+
# * +guards+ - the list of guards that must be verified before the handler will be called
|
60
105
|
def register_handler(type, *guards, &handler)
|
61
|
-
|
106
|
+
check_handler type, guards
|
62
107
|
@handlers[type] ||= []
|
63
108
|
@handlers[type] << [guards, handler]
|
64
109
|
end
|
65
110
|
|
111
|
+
##
|
112
|
+
# Write data to the stream
|
66
113
|
def write(stanza)
|
67
114
|
@stream.send(stanza) if @stream
|
68
115
|
end
|
69
116
|
|
117
|
+
##
|
118
|
+
# Helper that will create a temporary handler for the stanza being sent before writing it to the stream.
|
119
|
+
#
|
120
|
+
# client.write_with_handler(stanza) { |s| "handle stanza here" }
|
121
|
+
#
|
122
|
+
# is equivalent to:
|
123
|
+
#
|
124
|
+
# client.register_tmp_handler(stanza.id) { |s| "handle stanza here" }
|
125
|
+
# client.write stanza
|
70
126
|
def write_with_handler(stanza, &handler)
|
71
127
|
register_tmp_handler stanza.id, &handler
|
72
128
|
write stanza
|
73
129
|
end
|
74
130
|
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
|
131
|
+
##
|
132
|
+
# Close the connection
|
79
133
|
def close
|
80
134
|
@stream.close_connection_after_writing
|
81
135
|
end
|
82
136
|
|
83
|
-
def
|
137
|
+
def post_init # :nodoc:
|
138
|
+
self.jid.node ? client_post_init : ready!
|
139
|
+
end
|
140
|
+
|
141
|
+
def unbind # :nodoc:
|
84
142
|
EM.stop if EM.reactor_running?
|
85
143
|
end
|
86
144
|
|
87
|
-
def receive_data(stanza)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
break if call_handler_for(type, stanza)# && (stanza.is_a?(BlatherError) || stanza.type == :iq)
|
93
|
-
end
|
145
|
+
def receive_data(stanza) # :nodoc:
|
146
|
+
catch(:halt) do
|
147
|
+
run_filters :before, stanza
|
148
|
+
handle_stanza stanza
|
149
|
+
run_filters :after, stanza
|
94
150
|
end
|
95
151
|
end
|
96
152
|
|
153
|
+
def jid=(new_jid) # :nodoc :
|
154
|
+
@jid = JID.new new_jid
|
155
|
+
end
|
156
|
+
|
157
|
+
def setup? # :nodoc:
|
158
|
+
@setup.is_a? Array
|
159
|
+
end
|
160
|
+
|
161
|
+
def setup(jid, password, host = nil, port = nil) # :nodoc:
|
162
|
+
@jid = JID.new(jid)
|
163
|
+
@setup = [@jid, password]
|
164
|
+
@setup << host if host
|
165
|
+
@setup << port if port
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
97
169
|
protected
|
98
|
-
def
|
170
|
+
def check_handler(type, guards)
|
171
|
+
Blather.logger.warn "Handler for type \"#{type}\" will never be called as it's not a registered type" unless current_handlers.include?(type)
|
172
|
+
check_guards guards
|
173
|
+
end
|
174
|
+
|
175
|
+
def current_handlers
|
176
|
+
[:ready] + Stanza.handler_list + BlatherError.handler_list
|
177
|
+
end
|
178
|
+
|
179
|
+
def setup_initial_handlers # :nodoc:
|
99
180
|
register_handler :error do |err|
|
100
181
|
raise err
|
101
182
|
end
|
102
183
|
|
103
184
|
register_handler :iq, :type => [:get, :set] do |iq|
|
104
|
-
write
|
185
|
+
write StanzaError.new(iq, 'service-unavailable', :cancel).to_node
|
105
186
|
end
|
106
187
|
|
107
188
|
register_handler :status do |status|
|
108
189
|
roster[status.from].status = status if roster[status.from]
|
190
|
+
nil
|
109
191
|
end
|
110
192
|
|
111
193
|
register_handler :roster do |node|
|
@@ -113,12 +195,12 @@ module Blather #:nodoc:
|
|
113
195
|
end
|
114
196
|
end
|
115
197
|
|
116
|
-
def ready!
|
198
|
+
def ready! # :nodoc:
|
117
199
|
@state = :ready
|
118
200
|
call_handler_for :ready, nil
|
119
201
|
end
|
120
202
|
|
121
|
-
def client_post_init
|
203
|
+
def client_post_init # :nodoc:
|
122
204
|
write_with_handler Stanza::Iq::Roster.new do |node|
|
123
205
|
roster.process node
|
124
206
|
write @status
|
@@ -126,25 +208,43 @@ module Blather #:nodoc:
|
|
126
208
|
end
|
127
209
|
end
|
128
210
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
211
|
+
def run_filters(type, stanza) # :nodoc:
|
212
|
+
@filters[type].each do |guards, handler, filter|
|
213
|
+
next if handler && !stanza.handler_heirarchy.include?(handler)
|
214
|
+
catch(:pass) { call_handler filter, guards, stanza }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def handle_stanza(stanza) # :nodoc:
|
219
|
+
if handler = @tmp_handlers.delete(stanza.id)
|
220
|
+
handler.call stanza
|
221
|
+
else
|
222
|
+
stanza.handler_heirarchy.each do |type|
|
223
|
+
break if call_handler_for(type, stanza)
|
139
224
|
end
|
140
225
|
end
|
141
226
|
end
|
142
227
|
|
228
|
+
def call_handler_for(type, stanza) # :nodoc:
|
229
|
+
return unless handler = @handlers[type]
|
230
|
+
handler.find do |guards, handler|
|
231
|
+
catch(:pass) { call_handler handler, guards, stanza }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def call_handler(handler, guards, stanza) # :nodoc:
|
236
|
+
if guards.first.respond_to?(:to_str) && !(result = stanza.find(*guards)).empty?
|
237
|
+
handler.call(stanza, result)
|
238
|
+
elsif !guarded?(guards, stanza)
|
239
|
+
handler.call(stanza)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
143
243
|
##
|
144
244
|
# If any of the guards returns FALSE this returns true
|
145
245
|
# the logic is reversed to allow short circuiting
|
146
246
|
# (why would anyone want to loop over more values than necessary?)
|
147
|
-
def guarded?(guards, stanza)
|
247
|
+
def guarded?(guards, stanza) # :nodoc:
|
148
248
|
guards.find do |guard|
|
149
249
|
case guard
|
150
250
|
when Symbol
|
@@ -156,10 +256,9 @@ module Blather #:nodoc:
|
|
156
256
|
# return FALSE unless any inequality is found
|
157
257
|
guard.find do |method, test|
|
158
258
|
value = stanza.__send__(method)
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
when Array
|
259
|
+
if test.class.respond_to?(:last_match)
|
260
|
+
!(test =~ value)
|
261
|
+
elsif test.is_a?(Array)
|
163
262
|
!test.include? value
|
164
263
|
else
|
165
264
|
test != value
|
@@ -171,7 +270,7 @@ module Blather #:nodoc:
|
|
171
270
|
end
|
172
271
|
end
|
173
272
|
|
174
|
-
def check_guards(guards)
|
273
|
+
def check_guards(guards) # :nodoc:
|
175
274
|
guards.each do |guard|
|
176
275
|
case guard
|
177
276
|
when Array
|
data/lib/blather/client/dsl.rb
CHANGED
@@ -38,6 +38,18 @@ module Blather
|
|
38
38
|
client.close
|
39
39
|
end
|
40
40
|
|
41
|
+
##
|
42
|
+
# Setup a before filter
|
43
|
+
def before(handler = nil, *guards, &block)
|
44
|
+
client.register_filter :before, handler, *guards, &block
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Setup an after filter
|
49
|
+
def after(handler = nil, *guards, &block)
|
50
|
+
client.register_filter :after, handler, *guards, &block
|
51
|
+
end
|
52
|
+
|
41
53
|
##
|
42
54
|
# Set handler for a stanza type
|
43
55
|
def handle(stanza_type, *guards, &block)
|
@@ -82,6 +94,18 @@ module Blather
|
|
82
94
|
client.jid
|
83
95
|
end
|
84
96
|
|
97
|
+
##
|
98
|
+
# Halt the handler chain
|
99
|
+
def halt
|
100
|
+
throw :halt
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Pass responsibility to the next handler
|
105
|
+
def pass
|
106
|
+
throw :pass
|
107
|
+
end
|
108
|
+
|
85
109
|
##
|
86
110
|
# Request items or info from an entity
|
87
111
|
# discover (items|info), [jid], [node] do |response|
|
@@ -27,7 +27,7 @@ module DSL
|
|
27
27
|
##
|
28
28
|
# Discover Nodes
|
29
29
|
# Yields a list of DiscoItem::Item objects
|
30
|
-
#
|
30
|
+
# * +path+ is the node's path. Default is '/'
|
31
31
|
def nodes(path = nil, host = nil, &callback)
|
32
32
|
path ||= '/'
|
33
33
|
stanza = Stanza::DiscoItems.new(:get, path)
|
@@ -38,7 +38,7 @@ module DSL
|
|
38
38
|
##
|
39
39
|
# Discover node information
|
40
40
|
# Yields a DiscoInfo node
|
41
|
-
#
|
41
|
+
# * +path+ is the node's path
|
42
42
|
def node(path, host = nil, &callback)
|
43
43
|
stanza = Stanza::DiscoInfo.new(:get, path)
|
44
44
|
stanza.to = send_to(host)
|
@@ -47,9 +47,9 @@ module DSL
|
|
47
47
|
|
48
48
|
##
|
49
49
|
# Retrieve items for a node
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
50
|
+
# * +path+ is the node's path
|
51
|
+
# * +list+ can be an array of items to retrieve
|
52
|
+
# * +max+ can be the maximum number of items to return
|
53
53
|
def items(path, list = [], max = nil, host = nil, &callback)
|
54
54
|
request Stanza::PubSub::Items.request(send_to(host), path, list, max), :items, callback
|
55
55
|
end
|
@@ -57,8 +57,8 @@ module DSL
|
|
57
57
|
##
|
58
58
|
# Subscribe to a node
|
59
59
|
# Yields the resulting Subscription object
|
60
|
-
#
|
61
|
-
#
|
60
|
+
# * +node+ is the node to subscribe to
|
61
|
+
# * +jid+ is the jid that should be used. Defaults to the stripped current JID
|
62
62
|
def subscribe(node, jid = nil, host = nil)
|
63
63
|
jid ||= DSL.client.jid.stripped
|
64
64
|
request(Stanza::PubSub::Subscribe.new(:set, send_to(host), node, jid)) { |n| yield n if block_given? }
|
@@ -67,8 +67,8 @@ module DSL
|
|
67
67
|
##
|
68
68
|
# Unsubscribe from a node
|
69
69
|
# Yields the resulting Unsubscribe object
|
70
|
-
#
|
71
|
-
#
|
70
|
+
# * +node+ is the node to subscribe to
|
71
|
+
# * +jid+ is the jid that should be used. Defaults to the stripped current JID
|
72
72
|
def unsubscribe(node, jid = nil, host = nil)
|
73
73
|
jid ||= DSL.client.jid.stripped
|
74
74
|
request(Stanza::PubSub::Unsubscribe.new(:set, send_to(host), node, jid)) { |n| yield n if block_given? }
|
@@ -77,8 +77,8 @@ module DSL
|
|
77
77
|
##
|
78
78
|
# Publish an item to a node
|
79
79
|
# Yields the resulting Publish node
|
80
|
-
#
|
81
|
-
#
|
80
|
+
# * +node+ is the node to publish to
|
81
|
+
# * +payload+ is the payload to send (see Blather::Stanza::PubSub::Publish for details)
|
82
82
|
def publish(node, payload, host = nil)
|
83
83
|
request(Stanza::PubSub::Publish.new(send_to(host), node, :set, payload)) { |n| yield n if block_given? }
|
84
84
|
end
|
@@ -86,8 +86,8 @@ module DSL
|
|
86
86
|
##
|
87
87
|
# Delete items from a node
|
88
88
|
# Yields the resulting node
|
89
|
-
#
|
90
|
-
#
|
89
|
+
# * +node+ is the node to retract items from
|
90
|
+
# * +ids+ is a list of ids to retract. This can also be a single id
|
91
91
|
def retract(node, ids = [], host = nil)
|
92
92
|
request(Stanza::PubSub::Retract.new(send_to(host), node, :set, ids)) { |n| yield n if block_given? }
|
93
93
|
end
|
@@ -96,7 +96,7 @@ module DSL
|
|
96
96
|
# Create a node
|
97
97
|
# Yields the resulting node
|
98
98
|
# This does not (yet) handle configuration
|
99
|
-
#
|
99
|
+
# * +node+ is the node to create
|
100
100
|
def create(node, host = nil)
|
101
101
|
request(Stanza::PubSub::Create.new(:set, send_to(host), node)) { |n| yield n if block_given? }
|
102
102
|
end
|
@@ -104,7 +104,7 @@ module DSL
|
|
104
104
|
##
|
105
105
|
# Purge all node items
|
106
106
|
# Yields the resulting node
|
107
|
-
#
|
107
|
+
# * +node+ is the node to purge
|
108
108
|
def purge(node, host = nil)
|
109
109
|
request(Stanza::PubSubOwner::Purge.new(:set, send_to(host), node)) { |n| yield n if block_given? }
|
110
110
|
end
|
@@ -112,7 +112,7 @@ module DSL
|
|
112
112
|
##
|
113
113
|
# Delete a node
|
114
114
|
# Yields the resulting node
|
115
|
-
#
|
115
|
+
# * +node+ is the node to delete
|
116
116
|
def delete(node, host = nil)
|
117
117
|
request(Stanza::PubSubOwner::Delete.new(:set, send_to(host), node)) { |n| yield n if block_given? }
|
118
118
|
end
|