mrw-uppercut 0.0.1.pre1

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.
@@ -0,0 +1,90 @@
1
+ class Uppercut
2
+ class Base
3
+ def stanza(msg) #:nodoc:
4
+ return false unless connected?
5
+ send! msg
6
+ end
7
+
8
+ # Attempt to connect to the server, if not already connected.
9
+ #
10
+ # Raises a simple RuntimeError if it fails to connect. This should be
11
+ # changed eventually to be more useful.
12
+ def connect
13
+ return if connected?
14
+ connect!
15
+ raise 'Failed to connected' unless connected?
16
+ present!
17
+ end
18
+
19
+ # Disconnects from the server if it is connected.
20
+ def disconnect
21
+ disconnect! if connected?
22
+ end
23
+
24
+ # Disconnects and connects to the server.
25
+ def reconnect
26
+ disconnect
27
+ connect
28
+ end
29
+
30
+ attr_reader :client, :roster
31
+
32
+ # True if the Agent is currently connected to the Jabber server.
33
+ def connected?
34
+ @client.respond_to?(:is_connected?) && @client.is_connected?
35
+ end
36
+
37
+ private
38
+
39
+ def connect!
40
+ @connect_lock ||= Mutex.new
41
+ return if @connect_lock.locked?
42
+
43
+ client = Jabber::Client.new(@user)
44
+
45
+ @connect_lock.lock
46
+
47
+ client.connect
48
+ client.auth(@pw)
49
+ @client = client
50
+
51
+ @connect_lock.unlock
52
+ end
53
+
54
+ def disconnect!
55
+ @client.close if connected?
56
+ @client = nil
57
+ end
58
+
59
+ def present!
60
+ send! Jabber::Presence.new(nil, "Available")
61
+ end
62
+
63
+ # Taken directly from xmpp4r-simple (thanks Blaine!)
64
+ def send!(msg)
65
+ attempts = 0
66
+ begin
67
+ attempts += 1
68
+ @client.send(msg)
69
+ rescue Errno::EPIPE, IOError => e
70
+ sleep 1
71
+ disconnect!
72
+ connect!
73
+ retry unless attempts > 3
74
+ raise e
75
+ rescue Errno::ECONNRESET => e
76
+ sleep (attempts^2) * 60 + 60
77
+ disconnect!
78
+ connect!
79
+ retry unless attempts > 3
80
+ raise e
81
+ end
82
+ end
83
+
84
+ def log(error)
85
+ # todo
86
+ p error
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ class Uppercut
2
+ class Conversation < Message
3
+ attr_reader :to
4
+
5
+ def initialize(to, base) #:nodoc:
6
+ @to = to
7
+ super base
8
+ end
9
+
10
+ # Wait for another message from this contact.
11
+ #
12
+ # Expects a block which should receive one parameter, which will be a
13
+ # String.
14
+ #
15
+ # One common use of _wait_for_ is for confirmation of a sensitive action.
16
+ #
17
+ # command('foo') do |c|
18
+ # c.send 'Are you sure?'
19
+ # c.wait_for do |reply|
20
+ # do_it if reply.downcase == 'yes'
21
+ # end
22
+ # end
23
+ def wait_for(&block)
24
+ @base.redirect_from(@to.bare, &block)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ class Uppercut
2
+ class Message
3
+ attr_accessor :to, :message
4
+
5
+ def initialize(base) #:nodoc:
6
+ @base = base
7
+ end
8
+
9
+ # Send a blob of text.
10
+ def send(body=nil)
11
+ msg = Jabber::Message.new(@to)
12
+ msg.type = :chat
13
+ msg.body = body || @message
14
+ @base.stanza(msg)
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,64 @@
1
+ class Uppercut
2
+ class Notifier < Base
3
+ class << self
4
+ @@notifiers = []
5
+
6
+ def notifier(name, &block)
7
+ @@notifiers << name
8
+ define_method(name, &block)
9
+ end
10
+ end
11
+
12
+ def notify(name, data=nil)
13
+ return false unless connected?
14
+ return nil unless @@notifiers.include?(name)
15
+
16
+ send(name, Message.new(self), data)
17
+ end
18
+
19
+ def initialize(user, pw, options={})
20
+ options = DEFAULT_OPTIONS.merge(options)
21
+
22
+ initialize_queue options[:starling], options[:queue]
23
+
24
+ @user = user
25
+ @pw = pw
26
+ connect if options[:connect]
27
+ listen if options[:listen]
28
+ end
29
+
30
+ DEFAULT_OPTIONS = { :connect => true }
31
+
32
+ def listen
33
+ connect unless connected?
34
+
35
+ @listen_thread = Thread.new {
36
+ loop { notify @starling.get(@queue) }
37
+ }
38
+ end
39
+
40
+ def stop
41
+ @listen_thread.kill if listening?
42
+ end
43
+
44
+ def listening?
45
+ @listen_thread && @listen_thread.alive?
46
+ end
47
+
48
+ def inspect #:nodoc:
49
+ "<Uppercut::Notifier #{@user} " +
50
+ "#{listening? ? 'Listening' : 'Not Listening'} " +
51
+ "#{connected? ? 'Connected' : 'Disconnected'}>"
52
+ end
53
+
54
+ private
55
+
56
+ def initialize_queue(server, queue)
57
+ return unless queue && server
58
+ require 'starling'
59
+ @queue = queue
60
+ @starling = Starling.new(server)
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,381 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Uppercut::Agent do
4
+ before :each do
5
+ @agent = TestAgent.new('test@foo.com', 'pw', :connect => false)
6
+ end
7
+
8
+ describe :new do
9
+ it "connects by default" do
10
+ agent = Uppercut::Agent.new('test@foo', 'pw')
11
+ agent.should be_connected
12
+ end
13
+
14
+ it "does not connect by default with :connect = false" do
15
+ agent = Uppercut::Agent.new('test@foo', 'pw', :connect => false)
16
+ agent.should_not be_connected
17
+ end
18
+
19
+ it "starts to listen with :listen = true" do
20
+ agent = Uppercut::Agent.new('test@foo', 'pw', :listen => true)
21
+ agent.should be_listening
22
+ end
23
+
24
+ it "initializes @redirects with a blank hash" do
25
+ agent = Uppercut::Agent.new('test@foo', 'pw', :connect => false)
26
+ agent.instance_eval { @redirects }.should == {}
27
+ end
28
+
29
+ it "populates @pw and @user" do
30
+ agent = Uppercut::Agent.new('test@foo', 'pw')
31
+ agent.instance_eval { @pw }.should == 'pw'
32
+ agent.instance_eval { @user }.should == 'test@foo'
33
+ end
34
+
35
+ it "populates @allowed_roster with :roster option" do
36
+ jids = %w(bob@foo fred@foo)
37
+ agent = Uppercut::Agent.new('test@foo', 'pw', :roster => jids)
38
+ agent.instance_eval { @allowed_roster }.should == jids
39
+ end
40
+ end
41
+
42
+ describe :connect do
43
+ it "does not try to connect if already connected" do
44
+ @agent.connect
45
+ old_client = @agent.client
46
+
47
+ @agent.connect
48
+ (@agent.client == old_client).should == true
49
+ end
50
+
51
+ it "connects if disconnected" do
52
+ @agent.should_not be_connected
53
+
54
+ old_client = @agent.client
55
+
56
+ @agent.connect
57
+ (@agent.client == old_client).should_not == true
58
+ end
59
+
60
+ it "sends a Presence notification" do
61
+ @agent.connect
62
+ @agent.client.sent.first.class.should == Jabber::Presence
63
+ end
64
+ end
65
+
66
+ describe :disconnect do
67
+ it "does not try to disconnect if not connected" do
68
+ @agent.client.should be_nil
69
+ @agent.instance_eval { @client = :foo }
70
+
71
+ @agent.disconnect
72
+ @agent.client.should == :foo
73
+ end
74
+
75
+ it "sets @client to nil" do
76
+ @agent.connect
77
+ @agent.client.should_not be_nil
78
+
79
+ @agent.disconnect
80
+ @agent.client.should be_nil
81
+ end
82
+ end
83
+
84
+ describe :reconnect do
85
+ it "calls disconnect then connect" do
86
+ @agent.should_receive(:disconnect).once.ordered
87
+ @agent.should_receive(:connect).once.ordered
88
+
89
+ @agent.reconnect
90
+ end
91
+ end
92
+
93
+ describe :connected? do
94
+ it "returns true if client#is_connected? is true" do
95
+ @agent.connect
96
+ @agent.client.instance_eval { @connected = true }
97
+ @agent.should be_connected
98
+ end
99
+ end
100
+
101
+ describe :listen do
102
+ it "connects if not connected" do
103
+ @agent.listen
104
+ @agent.should be_connected
105
+ end
106
+
107
+ it "spins off a new thread in @listen_thread" do
108
+ @agent.listen
109
+ @agent.instance_eval { @listen_thread.class }.should == Thread
110
+ end
111
+
112
+ it "creates a receive message callback" do
113
+ @agent.listen
114
+ @agent.client.on_message.class.should == Proc
115
+ end
116
+
117
+ it "creates a subscription request callback" do
118
+ @agent.listen
119
+ @agent.roster.on_subscription_request.class.should == Proc
120
+ end
121
+
122
+ it "calls dispatch when receving a message" do
123
+ @agent.listen
124
+ @agent.should_receive(:dispatch)
125
+ @agent.client.receive_message("foo@bar.com", "test")
126
+ end
127
+
128
+ describe 'presence callbacks' do
129
+ it 'processes :signon presence callback' do
130
+ @agent.listen
131
+ new_presence = Jabber::Presence.new(nil, nil)
132
+ old_presence = Jabber::Presence.new(nil, nil)
133
+ old_presence.type = :unavailable
134
+
135
+ @agent.roster.receive_presence(Jabber::Roster::Helper::RosterItem.new, old_presence, new_presence)
136
+ @agent.instance_eval { @last_callback }.should == :signon
137
+ end
138
+
139
+ it 'processes :signoff presence callback' do
140
+ @agent.listen
141
+ presence = Jabber::Presence.new(nil, nil)
142
+ presence.type = :unavailable
143
+
144
+ @agent.roster.receive_presence(Jabber::Roster::Helper::RosterItem.new, nil, presence)
145
+ @agent.instance_eval { @last_callback }.should == :signoff
146
+ end
147
+
148
+ it 'processes :status_change presence callback' do
149
+ @agent.listen
150
+
151
+ old_presence = Jabber::Presence.new(nil, nil)
152
+ new_presence = Jabber::Presence.new(nil, nil)
153
+ new_presence.show = :away
154
+
155
+ @agent.roster.receive_presence(Jabber::Roster::Helper::RosterItem.new, old_presence, new_presence)
156
+ @agent.instance_eval { @last_callback }.should == :status_change
157
+ end
158
+
159
+ it 'processes :status_message_change presence callback' do
160
+ @agent.listen
161
+
162
+ old_presence = Jabber::Presence.new(nil, nil)
163
+ old_presence.status = 'chicka chicka yeaaaaah'
164
+
165
+ new_presence = Jabber::Presence.new(nil, nil)
166
+ new_presence.status = 'thom yorke is the man'
167
+
168
+ @agent.roster.receive_presence(Jabber::Roster::Helper::RosterItem.new, old_presence, new_presence)
169
+ @agent.instance_eval { @last_callback }.should == :status_message_change
170
+ end
171
+
172
+ it 'processes :subscribe presence callback' do
173
+ @agent.listen
174
+ @agent.roster.should_receive :add
175
+ @agent.roster.should_receive :accept_subscription
176
+
177
+ presence = Jabber::Presence.new(nil, nil)
178
+ presence.type = :subscribe
179
+
180
+ @agent.roster.receive_subscription_request(Jabber::Roster::Helper::RosterItem.new, presence)
181
+ @agent.instance_eval { @last_callback }.should == :subscribe
182
+ end
183
+
184
+ it 'processes :subscription_approval presence callback' do
185
+ @agent.listen
186
+
187
+ presence = Jabber::Presence.new(nil, nil)
188
+ presence.type = :subscribed
189
+
190
+ @agent.roster.receive_subscription(Jabber::Roster::Helper::RosterItem.new, presence)
191
+ @agent.instance_eval { @last_callback }.should == :subscription_approval
192
+ end
193
+
194
+ it 'processes :subscription_denial presence callback' do
195
+ @agent.listen
196
+
197
+ presence = Jabber::Presence.new(nil, nil)
198
+ presence.type = :unsubscribed
199
+
200
+ item = Jabber::Roster::Helper::RosterItem.new
201
+ item.subscription = :from
202
+
203
+ @agent.roster.receive_subscription(item, presence)
204
+ @agent.instance_eval { @last_callback }.should == :subscription_denial
205
+ end
206
+
207
+ it 'processes :unsubscribe presence callback' do
208
+ @agent.listen
209
+
210
+ presence = Jabber::Presence.new(nil, nil)
211
+ presence.type = :unsubscribe
212
+
213
+ @agent.roster.receive_subscription(Jabber::Roster::Helper::RosterItem.new, presence)
214
+ @agent.instance_eval { @last_callback }.should == :unsubscribe
215
+ end
216
+ end
217
+ end
218
+
219
+ describe :stop do
220
+ it "kills the @listen_thread" do
221
+ @agent.listen
222
+ @agent.instance_eval { @listen_thread.alive? }.should == true
223
+
224
+ @agent.stop
225
+ @agent.instance_eval { @listen_thread.alive? }.should_not == true
226
+ end
227
+ end
228
+
229
+ describe :listening? do
230
+ it "returns true if @listen_thread is alive" do
231
+ @agent.listen
232
+ @agent.instance_eval { @listen_thread.alive? }.should == true
233
+ @agent.should be_listening
234
+ end
235
+
236
+ it "returns false if @listen_thread is not alive" do
237
+ @agent.listen
238
+ @agent.stop
239
+ @agent.should_not be_listening
240
+ end
241
+
242
+ it "returns false if @listen_thread has not been set" do
243
+ @agent.should_not be_listening
244
+ end
245
+ end
246
+
247
+ describe :dispatch_presence do
248
+ it 'calls the correct callback' do
249
+ @agent.listen
250
+
251
+ presence = Jabber::Presence.new(nil, nil)
252
+ @agent.send(:dispatch_presence, :subscribe, presence)
253
+ @agent.instance_eval { @last_callback }.should == :subscribe
254
+ end
255
+ end
256
+
257
+ describe :dispatch do
258
+ it "calls the first matching command" do
259
+ msg = Jabber::Message.new(nil)
260
+ msg.body = 'hi'
261
+ msg.from = Jabber::JID.fake_jid
262
+
263
+ @agent.send(:dispatch, msg)
264
+ @agent.instance_eval { @called_hi_regex }.should_not == true
265
+ @agent.instance_eval { @called_hi }.should == true
266
+ end
267
+
268
+ it "matches by regular expression" do
269
+ msg = Jabber::Message.new(nil)
270
+ msg.body = 'high'
271
+ msg.from = Jabber::JID.fake_jid
272
+
273
+ @agent.send(:dispatch, msg)
274
+ @agent.instance_eval { @called_hi }.should_not == true
275
+ @agent.instance_eval { @called_hi_regex }.should == true
276
+ end
277
+
278
+ it "allows overwriting existing commands" do
279
+ @agent.class.command /^hi/ do |c|
280
+ c.instance_eval { @base.instance_eval { @called_hi_regex = "overwritten" } }
281
+ end
282
+
283
+ msg = Jabber::Message.new(nil)
284
+ msg.body = 'hi charles'
285
+ msg.from = Jabber::JID.fake_jid
286
+
287
+ @agent.send(:dispatch, msg)
288
+ @agent.instance_eval { @called_hi_regex }.should == "overwritten"
289
+ end
290
+
291
+ it "allows single argument for string pattern" do
292
+ msg = Jabber::Message.new(nil)
293
+ msg.body = 'hello'
294
+ msg.from = Jabber::JID.fake_jid
295
+
296
+ @agent.send(:dispatch, msg)
297
+ @agent.instance_eval { @called_hello }.should == true
298
+ end
299
+
300
+ it "allows multiple arguments for matches of regular expression pattern" do
301
+ msg = Jabber::Message.new(nil)
302
+ msg.body = 'hello i am julia, your grandmother'
303
+ msg.from = Jabber::JID.fake_jid
304
+
305
+ @agent.send(:dispatch, msg)
306
+ @agent.instance_eval { @called_hello_regex }.should == "julia is a my grandmother"
307
+ end
308
+
309
+ it "allows no argument for string pattern (and eval inside of agent instance)" do
310
+ msg = Jabber::Message.new(nil)
311
+ msg.body = 'salve'
312
+ msg.from = Jabber::JID.fake_jid
313
+
314
+ @agent.send(:dispatch, msg)
315
+ @agent.instance_eval { @called_salve }.should == true
316
+ end
317
+ end
318
+
319
+ describe :multiple_instances do
320
+ before do
321
+ @second_agent = TestAgent.new('test@foo.com', 'pw', :connect => false)
322
+ end
323
+
324
+ it "should allow separat commands" do
325
+ msg = Jabber::Message.new(nil)
326
+ msg.body = 'hi'
327
+ msg.from = Jabber::JID.fake_jid
328
+
329
+ @second_agent.send(:dispatch, msg)
330
+ @second_agent.instance_eval { @called_hi }.should == true
331
+ @agent.instance_eval { @called_hi }.should_not == true
332
+ end
333
+
334
+ it "should allow separat callbacks" do
335
+ @agent.listen
336
+
337
+ presence = Jabber::Presence.new(nil, nil)
338
+ @second_agent.send(:dispatch_presence, :subscribe, presence)
339
+ @second_agent.instance_eval { @last_callback }.should == :subscribe
340
+ @agent.instance_eval { @last_callback }.should == nil
341
+
342
+ @agent.send(:dispatch_presence, :unsubscribe, presence)
343
+ @second_agent.instance_eval { @last_callback }.should == :subscribe
344
+ @agent.instance_eval { @last_callback }.should == :unsubscribe
345
+ end
346
+ end
347
+
348
+ describe :multiple_agents do
349
+ before do
350
+ @french_agent = FrenchTestAgent.new('test@foo.com', 'pw', :connect => false)
351
+ end
352
+
353
+ it "should allow separat commands" do
354
+ msg = Jabber::Message.new(nil)
355
+ msg.body = 'hi'
356
+ msg.from = Jabber::JID.fake_jid
357
+
358
+ @french_agent.send(:dispatch, msg)
359
+ @french_agent.instance_eval { @called_hi }.should_not == true
360
+ @french_agent.instance_eval { @called_salut }.should_not == true
361
+
362
+ msg.body = 'salut'
363
+ @french_agent.send(:dispatch, msg)
364
+ @french_agent.instance_eval { @called_hi }.should_not == true
365
+ @french_agent.instance_eval { @called_salut }.should == true
366
+ end
367
+
368
+ it "should allow separat callbacks" do
369
+ @agent.listen
370
+
371
+ presence = Jabber::Presence.new(nil, nil)
372
+ @french_agent.send(:dispatch_presence, :subscribe, presence)
373
+ @french_agent.instance_eval { @last_callback }.should == nil
374
+ @agent.instance_eval { @last_callback }.should == nil
375
+
376
+ @agent.send(:dispatch_presence, :subscribe, presence)
377
+ @french_agent.instance_eval { @last_callback }.should == nil
378
+ @agent.instance_eval { @last_callback }.should == :subscribe
379
+ end
380
+ end
381
+ end