switchboard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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