mojodna-switchboard 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/README.markdown +31 -0
  2. data/bin/switchboard +35 -0
  3. data/examples/election_results.rb +61 -0
  4. data/lib/switchboard/colors.rb +10 -0
  5. data/lib/switchboard/commands/command.rb +85 -0
  6. data/lib/switchboard/commands/config/config.rb +20 -0
  7. data/lib/switchboard/commands/config.rb +1 -0
  8. data/lib/switchboard/commands/default.rb +47 -0
  9. data/lib/switchboard/commands/help/help.rb +15 -0
  10. data/lib/switchboard/commands/help.rb +1 -0
  11. data/lib/switchboard/commands/pubsub/pubsub.rb +16 -0
  12. data/lib/switchboard/commands/pubsub/subscribe.rb +35 -0
  13. data/lib/switchboard/commands/pubsub/subscriptions.rb +32 -0
  14. data/lib/switchboard/commands/pubsub/unsubscribe.rb +31 -0
  15. data/lib/switchboard/commands/pubsub.rb +4 -0
  16. data/lib/switchboard/commands/roster/add.rb +26 -0
  17. data/lib/switchboard/commands/roster/list.rb +24 -0
  18. data/lib/switchboard/commands/roster/remove.rb +29 -0
  19. data/lib/switchboard/commands/roster/roster.rb +7 -0
  20. data/lib/switchboard/commands/roster.rb +4 -0
  21. data/lib/switchboard/commands.rb +6 -0
  22. data/lib/switchboard/core.rb +324 -0
  23. data/lib/switchboard/instance_exec.rb +16 -0
  24. data/lib/switchboard/jacks/auto_accept.rb +16 -0
  25. data/lib/switchboard/jacks/debug.rb +17 -0
  26. data/lib/switchboard/jacks/notify.rb +15 -0
  27. data/lib/switchboard/jacks/oauth_pubsub.rb +50 -0
  28. data/lib/switchboard/jacks/roster_debug.rb +23 -0
  29. data/lib/switchboard/jacks.rb +5 -0
  30. data/lib/switchboard/oauth/request_proxy/mock_request.rb +21 -0
  31. data/lib/switchboard/settings.rb +32 -0
  32. data/lib/switchboard/switchboard.rb +5 -0
  33. data/lib/switchboard/version.rb +3 -0
  34. data/lib/switchboard/xmpp4r/pubsub/helper/oauth_service_helper.rb +107 -0
  35. data/lib/switchboard.rb +1 -0
  36. data/switchboard.gemspec +14 -0
  37. metadata +113 -0
@@ -0,0 +1,324 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'xmpp4r'
4
+ rescue LoadError => e
5
+ gem = e.message.split("--").last.strip
6
+ puts "The #{gem} gem is required."
7
+ end
8
+
9
+ # allow local library modifications/additions to be loaded
10
+ $: << File.join(File.dirname(__FILE__))
11
+
12
+ require 'switchboard/instance_exec'
13
+ require 'xmpp4r/roster'
14
+
15
+
16
+ module Switchboard
17
+ class Core
18
+ include Timeout
19
+
20
+ attr_reader :client, :jacks, :roster, :settings
21
+
22
+ def initialize(settings = Switchboard::Settings.new, spin = true, &block)
23
+ # register a handler for SIGINTs
24
+ trap(:INT) do
25
+ # exit on a second ^C
26
+ trap(:INT) do
27
+ exit
28
+ end
29
+
30
+ @deferreds.each do |name, deferred|
31
+ puts "Killing #{name}" if debug?
32
+ deferred.kill
33
+ end
34
+
35
+ shutdown!
36
+ end
37
+
38
+ @settings = settings
39
+ @loop = spin
40
+ @shutdown = false
41
+ @deferreds = {}
42
+ @main = block if block_given?
43
+
44
+ # TODO jid may already have a resource, so account for that
45
+ @client = Jabber::Client.new([settings["jid"], settings["resource"]] * "/")
46
+ end
47
+
48
+ # Turn the hydrant on.
49
+ def run!
50
+ startup
51
+
52
+ if @main
53
+ instance_eval(&@main)
54
+ elsif loop?
55
+ sleep 5 while !shutdown?
56
+ end
57
+
58
+ shutdown
59
+ end
60
+
61
+ # TODO don't start threads yet; wait until all startup hooks have been run
62
+ def defer(callback_name, timeout = 30, &block)
63
+ puts "Deferring to #{callback_name}..." if debug?
64
+ @deferreds[callback_name.to_sym] = Thread.new do
65
+
66
+ begin
67
+
68
+ timeout(timeout) do
69
+ results = instance_eval(&block)
70
+ send(callback_name.to_sym, results)
71
+ end
72
+
73
+ puts "Done with #{callback_name}." if debug?
74
+ # TODO make this thread-safe
75
+ @deferreds.delete(callback_name.to_sym)
76
+
77
+ rescue Timeout::Error
78
+ puts "Deferred method timed out."
79
+ rescue
80
+ puts "An error occurred while running a deferred: #{$!}"
81
+ puts $!.backtrace * "\n"
82
+ puts "Initiating shutdown..."
83
+ @shutdown = true
84
+ end
85
+ end
86
+ end
87
+
88
+ # Connect a jack to the switchboard
89
+ def plug!(*jacks)
90
+ @jacks ||= []
91
+ jacks.each do |jack|
92
+ puts "Connecting jack: #{jack}" if debug?
93
+ @jacks << jack
94
+ jack.connect(self)
95
+ end
96
+ end
97
+
98
+ # Register a hook to run when the Jabber::Client encounters an exception.
99
+ def on_exception(&block)
100
+ register_hook(:exception, &block)
101
+ end
102
+
103
+ # Register a hook to run when iq stanzas are received.
104
+ def on_iq(&block)
105
+ register_hook(:iq, &block)
106
+ end
107
+
108
+ # Register a hook to run when message stanzas are received.
109
+ def on_message(&block)
110
+ register_hook(:message, &block)
111
+ end
112
+
113
+ # Register a hook to run when presence stanzas are received.
114
+ def on_presence(&block)
115
+ register_hook(:presence, &block)
116
+ end
117
+
118
+ def on_roster_presence(&block)
119
+ register_hook(:roster_presence, &block)
120
+ end
121
+
122
+ def on_roster_query(&block)
123
+ register_hook(:roster_query, &block)
124
+ end
125
+
126
+ def on_roster_subscription(&block)
127
+ register_hook(:roster_subscription, &block)
128
+ end
129
+
130
+ def on_roster_subscription_request(&block)
131
+ register_hook(:roster_subscription_request, &block)
132
+ end
133
+
134
+ def on_roster_loaded(&block)
135
+ register_hook(:roster_loaded, &block)
136
+ end
137
+
138
+ def on_roster_update(&block)
139
+ register_hook(:roster_update, &block)
140
+ end
141
+
142
+ # Register a startup hook.
143
+ # Hooks will be given 5 seconds to complete before moving on.
144
+ def on_startup(&block)
145
+ register_hook(:startup, &block)
146
+ end
147
+
148
+ # Register a shutdown hook.
149
+ # Hooks will be given 5 seconds to complete before moving on.
150
+ def on_shutdown(&block)
151
+ register_hook(:shutdown, &block)
152
+ end
153
+
154
+ protected
155
+
156
+ def connect!
157
+ client.connect
158
+ client.auth(settings["password"])
159
+ @roster = Jabber::Roster::Helper.new(client)
160
+ end
161
+
162
+ def connected?
163
+ @connected
164
+ end
165
+
166
+ def debug?
167
+ settings["debug"]
168
+ end
169
+
170
+ def disconnect
171
+ presence(:unavailable)
172
+ client.close
173
+ end
174
+
175
+ def register_hook(name, &block)
176
+ @hooks ||= {}
177
+ @hooks[name.to_sym] ||= []
178
+
179
+ puts "Registering #{name} hook" if debug?
180
+ @hooks[name.to_sym] << block
181
+ end
182
+
183
+ def on(name, *args)
184
+ @hooks ||= {}
185
+ @hooks[name.to_sym] ||= []
186
+ @hooks[name.to_sym].each do |hook|
187
+ execute_hook(hook, *args)
188
+ end
189
+ end
190
+
191
+ def execute_hook(hook, *args)
192
+ timeout(1) do
193
+ instance_exec(*args, &hook)
194
+ end
195
+ rescue Timeout::Error
196
+ puts "Hook timed out, consider deferring it."
197
+ rescue
198
+ puts "An error occurred while running the hook; shutting down..."
199
+ puts $!
200
+ puts $!.backtrace * "\n"
201
+ shutdown
202
+ raise
203
+ end
204
+
205
+ def loop?
206
+ @loop
207
+ end
208
+
209
+ def presence(status = nil, to = nil)
210
+ presence = Jabber::Presence.new(nil, status)
211
+ presence.to = to
212
+ client.send(presence)
213
+ end
214
+
215
+ def register_default_callbacks
216
+ client.on_exception do |e, stream, where|
217
+ on(:exception, e, stream, where)
218
+
219
+ case where
220
+ when :something
221
+ else
222
+ puts "Caught #{e.inspect} on #{stream} at #{where}. You might want to consider handling this."
223
+ raise e
224
+ end
225
+ end
226
+
227
+ client.add_presence_callback do |presence|
228
+ on(:presence, presence)
229
+ end
230
+
231
+ client.add_message_callback do |message|
232
+ on(:message, message)
233
+ end
234
+
235
+ client.add_iq_callback do |iq|
236
+ on(:iq, iq)
237
+ end
238
+ end
239
+
240
+ def register_roster_callbacks
241
+ # presence from someone on my roster
242
+ roster.add_presence_callback do |item, old_presence, new_presence|
243
+ on(:roster_presence, item, old_presence, new_presence)
244
+ end
245
+
246
+ # roster query completed (rarely used)
247
+ roster.add_query_callback do |query|
248
+ on(:roster_query, query)
249
+ end
250
+
251
+ # roster subscription
252
+ roster.add_subscription_callback do |item, presence|
253
+ # confirmation that we were able to subscribe to someone else
254
+ on(:roster_subscription, item, presence)
255
+ end
256
+
257
+ roster.add_subscription_request_callback do |item, presence|
258
+ # someone wants to subscribe to me!
259
+ on(:roster_subscription_request, item, presence)
260
+ end
261
+
262
+ # roster was updated (rarely used)
263
+ roster.add_update_callback do |old_item, new_item|
264
+ # roster has been updated; don't care
265
+ # puts "update: #{old_item.inspect}, #{new_item.inspect}"
266
+ on(:roster_update, old_item, new_item)
267
+ end
268
+ end
269
+
270
+ def startup
271
+ begin
272
+ timeout(30) do
273
+ connect!
274
+ @connected = true
275
+
276
+ register_default_callbacks
277
+ register_roster_callbacks
278
+
279
+ # tell others that we're online
280
+ presence
281
+
282
+ # wait for the roster to load
283
+ roster.wait_for_roster
284
+ end
285
+ rescue Timeout::Error
286
+ puts "Startup took too long. Shutting down."
287
+ shutdown(false)
288
+ exit 1
289
+ end
290
+
291
+ # roster has now been loaded
292
+ on(:roster_loaded)
293
+
294
+ puts "Core startup completed." if debug?
295
+
296
+ # run startup hooks
297
+ on(:startup)
298
+
299
+ puts "=> Switchboard started."
300
+ end
301
+
302
+ def shutdown!
303
+ puts "Shutdown initiated."
304
+ @shutdown = true
305
+ end
306
+
307
+ def shutdown(run_hooks = true)
308
+ while (pending = @deferreds.select { |k,d| d.alive? }.length) > 0
309
+ puts "Waiting for #{pending} thread(s) to finish" if debug?
310
+ sleep 1
311
+ end
312
+
313
+ # run shutdown hooks
314
+ on(:shutdown) if run_hooks
315
+
316
+ puts "Shutting down..." if debug?
317
+ disconnect if connected?
318
+ end
319
+
320
+ def shutdown?
321
+ @shutdown
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,16 @@
1
+ module Kernel
2
+
3
+ # Like instance_eval but allows parameters to be passed.
4
+
5
+ def instance_exec(*args, &block)
6
+ mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
7
+ Object.class_eval{ define_method(mname, &block) }
8
+ begin
9
+ ret = send(mname, *args)
10
+ ensure
11
+ Object.class_eval{ undef_method(mname) } rescue nil
12
+ end
13
+ ret
14
+ end
15
+
16
+ end
@@ -0,0 +1,16 @@
1
+ class AutoAcceptJack
2
+ def self.connect(switchboard)
3
+ # complain if subscription requests were denied
4
+ switchboard.on_roster_subscription do |item, subscription|
5
+ unless subscription.type == :subscribed
6
+ puts "My subscription request was denied!"
7
+ end
8
+ end
9
+
10
+ # auto-accept subscription requests
11
+ switchboard.on_roster_subscription_request do |item, subscription|
12
+ puts "Accepting subscription from #{subscription.from}"
13
+ roster.accept_subscription(subscription.from)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'switchboard/colors'
2
+
3
+ class DebugJack
4
+ def self.connect(switchboard)
5
+ switchboard.on_presence do |presence|
6
+ puts "<< #{presence.to_s}".green
7
+ end
8
+
9
+ switchboard.on_message do |message|
10
+ puts "<< #{message.to_s}".blue
11
+ end
12
+
13
+ switchboard.on_iq do |iq|
14
+ puts "<< #{iq.to_s}".yellow
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ class NotifyJack
2
+ def self.connect(switchboard)
3
+ switchboard.on_roster_loaded do
4
+ roster.items.each do |jid, item|
5
+ presence(nil, jid)
6
+ end
7
+ end
8
+
9
+ switchboard.on_shutdown do
10
+ roster.items.each do |jid, item|
11
+ presence(:unavailable, jid)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'oauth'
4
+ rescue LoadError => e
5
+ gem = e.message.split("--").last.strip
6
+ puts "The #{gem} gem is required."
7
+ end
8
+
9
+ require 'oauth/consumer'
10
+ require 'oauth/request_proxy/mock_request'
11
+ require 'xmpp4r/pubsub'
12
+ require 'xmpp4r/pubsub/helper/oauth_service_helper'
13
+
14
+ class OAuthPubSubJack
15
+ def self.connect(switchboard)
16
+ switchboard.on_startup do
17
+ @pubsub = Jabber::PubSub::OAuthServiceHelper.new(client, settings["pubsub.server"])
18
+
19
+ @oauth_consumer = OAuth::Consumer.new(settings["oauth.consumer_key"], settings["oauth.consumer_secret"])
20
+ @oauth_token = OAuth::Token.new(settings["oauth.token"], settings["oauth.token_secret"])
21
+ # this is Fire Eagle-specific
22
+ @general_token = OAuth::Token.new(settings["oauth.general_token"], settings["oauth.general_token_secret"])
23
+
24
+ @pubsub.add_event_callback do |event|
25
+ on(:pubsub_event, event)
26
+ end
27
+ end
28
+
29
+ # TODO add the ability to define accessors
30
+ def switchboard.general_token
31
+ @general_token
32
+ end
33
+
34
+ def switchboard.oauth_consumer
35
+ @oauth_consumer
36
+ end
37
+
38
+ def switchboard.oauth_token
39
+ @oauth_token
40
+ end
41
+
42
+ def switchboard.pubsub
43
+ @pubsub
44
+ end
45
+
46
+ def switchboard.on_pubsub_event(&block)
47
+ register_hook(:pubsub_event, &block)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ class RosterDebugJack
2
+ def self.connect(switchboard)
3
+ switchboard.on_roster_presence do |item, old_presence, new_presence|
4
+ puts "[presence] << #{item.inspect}: #{old_presence.to_s}, #{new_presence.to_s}"
5
+ end
6
+
7
+ switchboard.on_roster_query do |query|
8
+ puts "[roster query] << #{query.to_s}"
9
+ end
10
+
11
+ switchboard.on_roster_subscription do |item, subscription|
12
+ puts "[subscription] << #{item.inspect}: #{subscription.to_s}"
13
+ end
14
+
15
+ switchboard.on_roster_subscription_request do |item, subscription|
16
+ puts "[subscription request] << #{item.inspect}: #{subscription.to_s}"
17
+ end
18
+
19
+ switchboard.on_roster_update do |old_item, new_item|
20
+ puts "[update] #{old_item.inspect}, #{new_item.inspect}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ require 'jacks/auto_accept'
2
+ require 'jacks/notify'
3
+ require 'jacks/roster_debug'
4
+ require 'jacks/debug'
5
+ require 'jacks/oauth_pubsub'
@@ -0,0 +1,21 @@
1
+ require 'oauth/request_proxy/base'
2
+
3
+ module OAuth
4
+ module RequestProxy
5
+ class MockRequest < OAuth::RequestProxy::Base
6
+ proxies Hash
7
+
8
+ def parameters
9
+ @request["parameters"]
10
+ end
11
+
12
+ def method
13
+ @request["method"]
14
+ end
15
+
16
+ def uri
17
+ @request["uri"]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ module Switchboard
2
+ class Settings
3
+ DEFAULT_PATH = File.join(ENV["HOME"], ".switchboardrc")
4
+
5
+ def initialize(path = DEFAULT_PATH)
6
+ @path = path
7
+
8
+ if File.exists?(path)
9
+ @config = YAML.load(File.read(path))
10
+ end
11
+
12
+ @config ||= {}
13
+ end
14
+
15
+ def get(key)
16
+ Switchboard::Command::OPTIONS[key] || @config[key] || Switchboard::Command::DEFAULT_OPTIONS[key]
17
+ end
18
+
19
+ alias_method :[], :get
20
+
21
+ def set!(key, value)
22
+ @config[key] = value
23
+ write
24
+ end
25
+
26
+ def write
27
+ open(@path, "w") do |f|
28
+ f << @config.to_yaml
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ require 'switchboard/core'
2
+ require 'switchboard/commands'
3
+ require 'switchboard/jacks'
4
+ require 'switchboard/settings'
5
+ require 'switchboard/version'
@@ -0,0 +1,3 @@
1
+ module Switchboard
2
+ VERSION = [0, 0, 1]
3
+ end
@@ -0,0 +1,107 @@
1
+ module Jabber
2
+ module PubSub
3
+ # PubSub service helper for use with OAuth-authenticated nodes
4
+ class OAuthServiceHelper < ServiceHelper
5
+ def initialize(stream, pubsubjid)
6
+ super(stream, pubsubjid)
7
+ end
8
+
9
+ # override #get_subscriptions_from_all_nodes to add an oauth element
10
+ def get_subscriptions_from_all_nodes(oauth_consumer, oauth_token)
11
+ iq = basic_pubsub_query(:get)
12
+
13
+ iq.pubsub.add(create_oauth_node(oauth_consumer, oauth_token))
14
+
15
+ entities = iq.pubsub.add(REXML::Element.new('subscriptions'))
16
+ res = nil
17
+ @stream.send_with_id(iq) { |reply|
18
+ if reply.pubsub.first_element('subscriptions')
19
+ res = []
20
+ reply.pubsub.first_element('subscriptions').each_element('subscription') { |subscription|
21
+ res << Jabber::PubSub::Subscription.import(subscription)
22
+ }
23
+ end
24
+ }
25
+
26
+ res
27
+ end
28
+
29
+ # override #subscribe_to to add an oauth element
30
+ def subscribe_to(node, oauth_consumer, oauth_token)
31
+ iq = basic_pubsub_query(:set)
32
+ sub = REXML::Element.new('subscribe')
33
+ sub.attributes['node'] = node
34
+ sub.attributes['jid'] = @stream.jid.strip.to_s
35
+
36
+ sub.add(create_oauth_node(oauth_consumer, oauth_token))
37
+
38
+ iq.pubsub.add(sub)
39
+ res = nil
40
+ @stream.send_with_id(iq) do |reply|
41
+ pubsubanswer = reply.pubsub
42
+ if pubsubanswer.first_element('subscription')
43
+ res = PubSub::Subscription.import(pubsubanswer.first_element('subscription'))
44
+ end
45
+ end # @stream.send_with_id(iq)
46
+ res
47
+ end
48
+
49
+ # override #unsubscribe_from to add an oauth element
50
+ def unsubscribe_from(node, oauth_consumer, oauth_token, subid=nil)
51
+ iq = basic_pubsub_query(:set)
52
+ unsub = PubSub::Unsubscribe.new
53
+ unsub.node = node
54
+ unsub.jid = @stream.jid.strip
55
+
56
+ unsub.add(create_oauth_node(oauth_consumer, oauth_token))
57
+
58
+ iq.pubsub.add(unsub)
59
+ ret = false
60
+ @stream.send_with_id(iq) { |reply|
61
+ ret = reply.kind_of?(Jabber::Iq) and reply.type == :result
62
+ } # @stream.send_with_id(iq)
63
+ ret
64
+ end
65
+
66
+ protected
67
+
68
+ # add the OAuth sauce (XEP-235)
69
+ def create_oauth_node(oauth_consumer, oauth_token)
70
+ require 'oauth/signature/hmac/sha1'
71
+ require 'cgi'
72
+
73
+ request = OAuth::RequestProxy.proxy \
74
+ "method" => "iq",
75
+ "uri" => [@stream.jid.strip.to_s, @pubsubjid.strip.to_s] * "&",
76
+ "parameters" => {
77
+ "oauth_consumer_key" => oauth_consumer.key,
78
+ "oauth_token" => oauth_token.token,
79
+ "oauth_signature_method" => "HMAC-SHA1"
80
+ }
81
+
82
+ signature = OAuth::Signature.sign(request, :consumer => oauth_consumer, :token => oauth_token)
83
+
84
+ oauth = REXML::Element.new("oauth")
85
+ oauth.attributes['xmlns'] = 'urn:xmpp:oauth'
86
+
87
+ oauth_consumer_key = REXML::Element.new("oauth_consumer_key")
88
+ oauth_consumer_key.text = oauth_consumer.key
89
+ oauth.add(oauth_consumer_key)
90
+
91
+ oauth_token_node = REXML::Element.new("oauth_token")
92
+ oauth_token_node.text = oauth_token.token
93
+ oauth.add(oauth_token_node)
94
+
95
+ oauth_signature_method = REXML::Element.new("oauth_signature_method")
96
+ oauth_signature_method.text = "HMAC-SHA1"
97
+ oauth.add(oauth_signature_method)
98
+
99
+ oauth_signature = REXML::Element.new("oauth_signature")
100
+ oauth_signature.text = signature
101
+ oauth.add(oauth_signature)
102
+
103
+ oauth
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1 @@
1
+ require 'switchboard/switchboard'
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "switchboard"
3
+ s.version = "0.0.1"
4
+ s.summary = "XMPP toolkit"
5
+ s.description = "A toolkit for assembling XMPP clients and interacting with XMPP servers."
6
+ s.authors = ["Seth Fitzsimmons"]
7
+ s.email = ["seth@mojodna.net"]
8
+
9
+ s.files = ["bin", "bin/switchboard", "examples/election_results.rb", "github-test.rb", "lib", "lib/switchboard", "lib/switchboard/colors.rb", "lib/switchboard/commands", "lib/switchboard/commands/command.rb", "lib/switchboard/commands/config", "lib/switchboard/commands/config/config.rb", "lib/switchboard/commands/config.rb", "lib/switchboard/commands/default.rb", "lib/switchboard/commands/help", "lib/switchboard/commands/help/help.rb", "lib/switchboard/commands/help.rb", "lib/switchboard/commands/pubsub", "lib/switchboard/commands/pubsub/pubsub.rb", "lib/switchboard/commands/pubsub/subscribe.rb", "lib/switchboard/commands/pubsub/subscriptions.rb", "lib/switchboard/commands/pubsub/unsubscribe.rb", "lib/switchboard/commands/pubsub.rb", "lib/switchboard/commands/roster", "lib/switchboard/commands/roster/add.rb", "lib/switchboard/commands/roster/list.rb", "lib/switchboard/commands/roster/remove.rb", "lib/switchboard/commands/roster/roster.rb", "lib/switchboard/commands/roster.rb", "lib/switchboard/commands.rb", "lib/switchboard/core.rb", "lib/switchboard/instance_exec.rb", "lib/switchboard/jacks", "lib/switchboard/jacks/auto_accept.rb", "lib/switchboard/jacks/debug.rb", "lib/switchboard/jacks/notify.rb", "lib/switchboard/jacks/oauth_pubsub.rb", "lib/switchboard/jacks/roster_debug.rb", "lib/switchboard/jacks.rb", "lib/switchboard/oauth", "lib/switchboard/oauth/request_proxy", "lib/switchboard/oauth/request_proxy/mock_request.rb", "lib/switchboard/settings.rb", "lib/switchboard/switchboard.rb", "lib/switchboard/version.rb", "lib/switchboard/xmpp4r", "lib/switchboard/xmpp4r/pubsub", "lib/switchboard/xmpp4r/pubsub/helper", "lib/switchboard/xmpp4r/pubsub/helper/oauth_service_helper.rb", "lib/switchboard.rb", "README.markdown", "switchboard-0.0.1.gem", "switchboard.gemspec"]
10
+ s.executables = ["switchboard"]
11
+ s.require_paths = ["lib"]
12
+
13
+ s.add_dependency("xmpp4r")
14
+ end