mojodna-switchboard 0.0.1

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 (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