switchboard 0.1.0

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 (70) hide show
  1. data/History.txt +3 -0
  2. data/README.markdown +59 -0
  3. data/Rakefile +30 -0
  4. data/bin/switchboard +35 -0
  5. data/examples/echo_bot.rb +7 -0
  6. data/lib/switchboard.rb +1 -0
  7. data/lib/switchboard/client.rb +92 -0
  8. data/lib/switchboard/colors.rb +10 -0
  9. data/lib/switchboard/commands.rb +12 -0
  10. data/lib/switchboard/commands/command.rb +85 -0
  11. data/lib/switchboard/commands/config.rb +1 -0
  12. data/lib/switchboard/commands/config/config.rb +20 -0
  13. data/lib/switchboard/commands/default.rb +40 -0
  14. data/lib/switchboard/commands/disco.rb +3 -0
  15. data/lib/switchboard/commands/disco/disco.rb +13 -0
  16. data/lib/switchboard/commands/disco/info.rb +39 -0
  17. data/lib/switchboard/commands/disco/items.rb +30 -0
  18. data/lib/switchboard/commands/grep.rb +23 -0
  19. data/lib/switchboard/commands/help.rb +1 -0
  20. data/lib/switchboard/commands/help/help.rb +15 -0
  21. data/lib/switchboard/commands/last.rb +1 -0
  22. data/lib/switchboard/commands/last/last.rb +34 -0
  23. data/lib/switchboard/commands/pep.rb +3 -0
  24. data/lib/switchboard/commands/pep/location.rb +97 -0
  25. data/lib/switchboard/commands/pep/pep.rb +7 -0
  26. data/lib/switchboard/commands/pep/tune.rb +85 -0
  27. data/lib/switchboard/commands/pubsub.rb +16 -0
  28. data/lib/switchboard/commands/pubsub/affiliations.rb +34 -0
  29. data/lib/switchboard/commands/pubsub/config.rb +42 -0
  30. data/lib/switchboard/commands/pubsub/create.rb +41 -0
  31. data/lib/switchboard/commands/pubsub/delete.rb +32 -0
  32. data/lib/switchboard/commands/pubsub/info.rb +48 -0
  33. data/lib/switchboard/commands/pubsub/items.rb +40 -0
  34. data/lib/switchboard/commands/pubsub/listen.rb +20 -0
  35. data/lib/switchboard/commands/pubsub/nodes.rb +37 -0
  36. data/lib/switchboard/commands/pubsub/options.rb +40 -0
  37. data/lib/switchboard/commands/pubsub/publish.rb +44 -0
  38. data/lib/switchboard/commands/pubsub/pubsub.rb +25 -0
  39. data/lib/switchboard/commands/pubsub/purge.rb +32 -0
  40. data/lib/switchboard/commands/pubsub/retract.rb +38 -0
  41. data/lib/switchboard/commands/pubsub/subscribe.rb +34 -0
  42. data/lib/switchboard/commands/pubsub/subscriptions.rb +35 -0
  43. data/lib/switchboard/commands/pubsub/unsubscribe.rb +30 -0
  44. data/lib/switchboard/commands/register.rb +40 -0
  45. data/lib/switchboard/commands/roster.rb +5 -0
  46. data/lib/switchboard/commands/roster/add.rb +27 -0
  47. data/lib/switchboard/commands/roster/list.rb +26 -0
  48. data/lib/switchboard/commands/roster/online.rb +28 -0
  49. data/lib/switchboard/commands/roster/remove.rb +25 -0
  50. data/lib/switchboard/commands/roster/roster.rb +7 -0
  51. data/lib/switchboard/commands/unregister.rb +21 -0
  52. data/lib/switchboard/component.rb +36 -0
  53. data/lib/switchboard/core.rb +311 -0
  54. data/lib/switchboard/ext/delegate.rb +21 -0
  55. data/lib/switchboard/ext/instance_exec.rb +16 -0
  56. data/lib/switchboard/helpers/oauth_pubsub.rb +44 -0
  57. data/lib/switchboard/helpers/pubsub.rb +28 -0
  58. data/lib/switchboard/jacks.rb +7 -0
  59. data/lib/switchboard/jacks/auto_accept.rb +16 -0
  60. data/lib/switchboard/jacks/debug.rb +17 -0
  61. data/lib/switchboard/jacks/echo.rb +7 -0
  62. data/lib/switchboard/jacks/notify.rb +15 -0
  63. data/lib/switchboard/jacks/oauth_pubsub.rb +23 -0
  64. data/lib/switchboard/jacks/pubsub.rb +21 -0
  65. data/lib/switchboard/jacks/roster_debug.rb +23 -0
  66. data/lib/switchboard/settings.rb +49 -0
  67. data/lib/switchboard/switchboard.rb +12 -0
  68. data/lib/switchboard/version.rb +3 -0
  69. data/switchboard.gemspec +15 -0
  70. metadata +136 -0
@@ -0,0 +1,40 @@
1
+ module Switchboard
2
+ module Commands
3
+ class Register < Switchboard::Command
4
+ description "Register a JID"
5
+
6
+ class Registration < Switchboard::Client
7
+ def initialize(settings = Switchboard::Settings.new, spin = false)
8
+ super(settings, false)
9
+ end
10
+
11
+ protected
12
+
13
+ def auth!
14
+ unless settings["jid"] && settings["password"]
15
+ puts "A JID and password are required to register a new account."
16
+ shutdown(false)
17
+ exit
18
+ end
19
+
20
+ # TODO consider using client.register_info.inspect
21
+ begin
22
+ puts "Registering #{settings["jid"]}"
23
+ iq = client.register(settings["password"])
24
+ rescue Jabber::ServerError => e
25
+ puts "Could not register: #{e}"
26
+ shutdown(false)
27
+ exit 1
28
+ end
29
+
30
+ # now log in
31
+ super
32
+ end
33
+ end
34
+
35
+ def self.run!
36
+ Registration.new.run!
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ require 'switchboard/commands/roster/roster'
2
+ require 'switchboard/commands/roster/add'
3
+ require 'switchboard/commands/roster/list'
4
+ require 'switchboard/commands/roster/online'
5
+ require 'switchboard/commands/roster/remove'
@@ -0,0 +1,27 @@
1
+ module Switchboard
2
+ module Commands
3
+ class Roster
4
+ class Add < Switchboard::Command
5
+ description "Add a JID to your roster"
6
+
7
+ def self.run!
8
+ switchboard = Switchboard::Client.new(Switchboard::Settings.new, false)
9
+
10
+ switchboard.on_roster_loaded do
11
+ # add the server as a contact if it wasn't already added
12
+ ARGV.each do |jid|
13
+ if roster.find(jid).empty?
14
+ puts "Adding #{jid} to my roster..."
15
+ roster.add(jid, nil, true)
16
+ end
17
+ end
18
+ end
19
+
20
+ switchboard.plug!(AutoAcceptJack)
21
+
22
+ switchboard.run!
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module Switchboard
2
+ module Commands
3
+ class Roster
4
+ class List < Switchboard::Command
5
+ description "List all roster items"
6
+
7
+ def self.run!
8
+ # TODO override settings with values from the command line
9
+ switchboard = Switchboard::Client.new(Switchboard::Settings.new, false)
10
+
11
+ switchboard.on_roster_loaded do
12
+ if roster.items.any?
13
+ puts "#{settings["jid"]}'s roster:"
14
+ puts roster.items.keys.map { |jid| jid.to_s } * "\n"
15
+ else
16
+ puts "#{settings["jid"]}'s roster is empty."
17
+ end
18
+ end
19
+
20
+ switchboard.plug!(AutoAcceptJack)
21
+ switchboard.run!
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Switchboard
2
+ module Commands
3
+ class Roster
4
+ class Online < Switchboard::Command
5
+ description "List online members of your roster"
6
+
7
+ def self.run!
8
+ switchboard = Switchboard::Client.new do
9
+ puts "Collecting presences..."
10
+ sleep 5
11
+ roster.items.each do |jid, item|
12
+ next unless item.online?
13
+ puts "#{item.jid}:"
14
+ item.each_presence do |presence|
15
+ status = [presence.show, presence.status].compact * " - "
16
+ status = "available" if status == ""
17
+ puts " /#{presence.from.resource} (#{status}) [#{presence.priority}]"
18
+ end
19
+ end
20
+ end
21
+
22
+ switchboard.plug!(AutoAcceptJack)
23
+ switchboard.run!
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module Switchboard
2
+ module Commands
3
+ class Roster
4
+ class Remove < Switchboard::Command
5
+ description "Remove a JID from your roster"
6
+
7
+ def self.run!
8
+ switchboard = Switchboard::Client.new(Switchboard::Settings.new, false)
9
+
10
+ switchboard.on_roster_loaded do
11
+ ARGV.each do |jid|
12
+ if (items = roster.find(jid)).any?
13
+ item = items.values.first
14
+ puts "Removing #{item.jid.to_s} from my roster..."
15
+ item.remove
16
+ end
17
+ end
18
+ end
19
+
20
+ switchboard.run!
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ module Switchboard
2
+ module Commands
3
+ class Roster < Switchboard::Command
4
+ description "Roster manipulation"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ module Switchboard
2
+ module Commands
3
+ class Unregister < Switchboard::Command
4
+ description "Unregister a JID"
5
+
6
+ def self.run!
7
+ switchboard = Switchboard::Client.new do
8
+ begin
9
+ client.remove_registration
10
+ rescue Jabber::ServerError => e
11
+ puts "Could not unregister #{settings["jid"]}: #{e}"
12
+ shutdown(false)
13
+ exit 1
14
+ end
15
+ end
16
+
17
+ switchboard.run!
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ module Switchboard
2
+ class Component < Core
3
+ attr_reader :component
4
+
5
+ def initialize(settings = Switchboard::Settings.new, spin = true)
6
+ super(settings, spin)
7
+
8
+ @component = Jabber::Component.new(settings["component.domain"])
9
+ end
10
+
11
+ protected
12
+
13
+ def auth!
14
+ component.auth(settings["component.secret"])
15
+ rescue Jabber::AuthenticationFailure
16
+ puts "Component authentication failed. Check your secret."
17
+ shutdown(false)
18
+ exit 1
19
+ end
20
+
21
+ def connect!
22
+ component.connect(settings["component.host"], settings["component.port"])
23
+ auth!
24
+ puts "Component connected." if debug?
25
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
26
+ puts "Couldn't connect to Jabber server at #{settings["component.host"]}:#{settings["component.port"]}.
27
+ That may mean the Jabber server isn't running or listening on that port,
28
+ or there might be firewall issues. Exiting."
29
+ exit 1
30
+ end
31
+
32
+ def stream
33
+ component
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,311 @@
1
+ begin
2
+ require 'xmpp4r'
3
+ rescue LoadError => e
4
+ lib = e.message.split("--").last.strip
5
+ puts "#{lib} is required."
6
+ exit 1
7
+ end
8
+
9
+ # allow local library modifications/additions to be loaded
10
+ $: << File.join(File.dirname(__FILE__))
11
+
12
+ require 'switchboard/ext/delegate'
13
+ require 'switchboard/ext/instance_exec'
14
+ require 'xmpp4r/roster'
15
+
16
+ module Switchboard
17
+ OPTIONS = {}
18
+ DEFAULT_OPTIONS = {
19
+ # :detach => false,
20
+ "debug" => false,
21
+ "oauth" => false,
22
+ "resource" => "switchboard"
23
+ }
24
+
25
+ class Core
26
+ include Timeout
27
+
28
+ attr_reader :jacks, :settings
29
+
30
+ # Register a hook
31
+ def self.hook(*events)
32
+ events.each do |event|
33
+ module_eval(<<-EOS, __FILE__, __LINE__)
34
+ def on_#{event}(method = nil, &block)
35
+ if block_given?
36
+ register_hook(:#{event}, &block)
37
+ elsif method
38
+ register_hook(:#{event}, method)
39
+ end
40
+ end
41
+ EOS
42
+ end
43
+ end
44
+
45
+ def initialize(settings = Switchboard::Settings.new, spin = true, &block)
46
+ # register a handler for SIGINTs
47
+ trap(:INT) do
48
+ # exit on a second ^C
49
+ trap(:INT) do
50
+ exit
51
+ end
52
+
53
+ die
54
+ end
55
+
56
+ at_exit do
57
+ die
58
+ end
59
+
60
+ @settings = settings
61
+ @loop = spin
62
+ @shutdown = false
63
+ @deferreds = {}
64
+ @main = block if block_given?
65
+ end
66
+
67
+ # Start running.
68
+ def run!
69
+ @main_thread = Thread.current
70
+
71
+ startup
72
+
73
+ @ready = true
74
+
75
+ if @main
76
+ instance_eval(&@main)
77
+ elsif loop?
78
+ sleep 1 while !shutdown?
79
+ end
80
+
81
+ shutdown
82
+ end
83
+
84
+ # TODO don't start threads yet; wait until all startup hooks have been run
85
+ def defer(callback_name, timeout = 30, &block)
86
+ puts "Deferring to #{callback_name}..." if debug?
87
+ @deferreds[callback_name.to_sym] = Thread.new(callback_name.to_sym) do |callback|
88
+
89
+ begin
90
+
91
+ timeout(timeout) do
92
+ begin
93
+ results = instance_eval(&block)
94
+ send(callback, results) if respond_to?(callback)
95
+ rescue Jabber::ServerError => e
96
+ puts "Server error: #{e}"
97
+ end
98
+ end
99
+
100
+ puts "Done with #{callback}." if debug?
101
+ # TODO make this thread-safe
102
+ @deferreds.delete(callback)
103
+
104
+ rescue Timeout::Error
105
+ puts "Deferred method timed out."
106
+ rescue
107
+ puts "An error occurred while running a deferred: #{$!}"
108
+ puts $!.backtrace * "\n"
109
+ shutdown!
110
+ end
111
+ end
112
+ end
113
+
114
+ def hook(*events)
115
+ self.class.hook(*events)
116
+ end
117
+
118
+ # Connect a jack to the switchboard
119
+ def plug!(*jacks)
120
+ @jacks ||= []
121
+ jacks.each do |jack|
122
+ puts "Connecting jack: #{jack}" if debug?
123
+ @jacks << jack
124
+ if jack.connect(self, settings) == false
125
+ puts "A jack was unable to connect. Shutting down..."
126
+ shutdown(false)
127
+ exit 1
128
+ end
129
+ end
130
+ end
131
+
132
+ def ready?
133
+ @ready
134
+ end
135
+
136
+ hook(:exception, :iq, :message, :presence, :stanza, :startup, :stream_connected, :shutdown)
137
+
138
+ protected
139
+
140
+ def connect!
141
+ raise NotImplementedError, "subclasses of Switchboard::Core must implement connect!"
142
+ end
143
+
144
+ def connected?
145
+ @connected
146
+ end
147
+
148
+ def debug?
149
+ settings["debug"]
150
+ end
151
+
152
+ def die
153
+ @deferreds.each do |name, deferred|
154
+ puts "Killing #{name}" if debug?
155
+ deferred.kill
156
+ end
157
+
158
+ shutdown!
159
+
160
+ if Thread.current != @main_thread
161
+ timeout(15) do
162
+ puts "Waiting for shutdown to complete."
163
+ sleep 0.1 until shutdown_complete?
164
+ end
165
+ end
166
+
167
+ rescue Timeout::Error
168
+ puts "Shutdown timed out."
169
+ end
170
+
171
+ def disconnect!
172
+ stream.close
173
+ end
174
+
175
+ def register_hook(name, method = nil, &block)
176
+ @hooks ||= {}
177
+ name = name.to_sym
178
+ @hooks[name] ||= []
179
+
180
+ puts "Registering #{name} hook" if debug?
181
+ if block_given?
182
+ @hooks[name] << block
183
+ else
184
+ @hooks[name] << lambda do |*args|
185
+ send(method.to_sym, *args)
186
+ end
187
+ end
188
+ end
189
+
190
+ def on(name, *args)
191
+ @hooks ||= {}
192
+ @hooks[name.to_sym] ||= []
193
+ @hooks[name.to_sym].each do |hook|
194
+ puts "Executing hook '#{name}'" if debug?
195
+ execute_hook(hook, *args)
196
+ end
197
+ end
198
+
199
+ def execute_hook(hook, *args)
200
+ timeout(1) do
201
+ instance_exec(*args, &hook)
202
+ end
203
+ rescue Timeout::Error
204
+ puts "Hook timed out, consider deferring it."
205
+ rescue
206
+ puts "An error occurred while running the hook; shutting down..."
207
+ puts $!
208
+ puts $!.backtrace * "\n"
209
+ shutdown!
210
+ raise
211
+ end
212
+
213
+ def loop?
214
+ @loop
215
+ end
216
+
217
+ def register_default_callbacks
218
+ stream.on_exception do |e, stream, where|
219
+ on(:exception, e, stream, where)
220
+
221
+ case where
222
+ when :disconnected
223
+ puts "Jabber service disconnected. Shutting down."
224
+ exit 1
225
+ when :exit
226
+ puts "Shutting down."
227
+ else
228
+ puts "Caught #{e.inspect} on #{stream} at #{where}. You might want to consider handling this."
229
+ raise e
230
+ end
231
+ end
232
+
233
+ stream.add_presence_callback do |presence|
234
+ on(:presence, presence)
235
+ on(:stanza, presence)
236
+ end
237
+
238
+ stream.add_message_callback do |message|
239
+ on(:message, message)
240
+ on(:stanza, message)
241
+ end
242
+
243
+ stream.add_iq_callback do |iq|
244
+ on(:iq, iq)
245
+ on(:stanza, iq)
246
+ end
247
+ end
248
+
249
+ def startup
250
+ begin
251
+ timeout(30) do
252
+ register_default_callbacks
253
+
254
+ connect!
255
+ @connected = true
256
+
257
+ on(:stream_connected)
258
+ end
259
+ rescue Timeout::Error
260
+ puts "Startup took too long. Shutting down."
261
+ shutdown(false)
262
+ exit 1
263
+ end
264
+
265
+ puts "Core startup completed." if debug?
266
+
267
+ # run startup hooks
268
+ on(:startup)
269
+
270
+ puts "=> Switchboard started."
271
+ end
272
+
273
+ def stream
274
+ raise NotImplementedError, "subclasses of Switchboard::Core must implement stream"
275
+ end
276
+
277
+ def shutdown!
278
+ puts "Shutdown initiated."
279
+ @shutdown = true
280
+ end
281
+
282
+ def shutdown(run_hooks = true)
283
+ if Thread.current != @main_thread
284
+ $stderr.puts "Wrong thread! You should be using #shutdown! instead."
285
+ shutdown!
286
+ return
287
+ end
288
+
289
+ while (pending = @deferreds.select { |k,d| d.alive? }.length) > 0
290
+ puts "Waiting for #{pending} thread(s) to finish" if debug?
291
+ sleep 1
292
+ end
293
+
294
+ # run shutdown hooks
295
+ on(:shutdown) if run_hooks
296
+
297
+ puts "Shutting down..." if debug?
298
+ disconnect! if connected?
299
+
300
+ @shutdown_complete = true
301
+ end
302
+
303
+ def shutdown?
304
+ @shutdown
305
+ end
306
+
307
+ def shutdown_complete?
308
+ @shutdown_complete
309
+ end
310
+ end
311
+ end