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.
- data/History.txt +3 -0
- data/README.markdown +59 -0
- data/Rakefile +30 -0
- data/bin/switchboard +35 -0
- data/examples/echo_bot.rb +7 -0
- data/lib/switchboard.rb +1 -0
- data/lib/switchboard/client.rb +92 -0
- data/lib/switchboard/colors.rb +10 -0
- data/lib/switchboard/commands.rb +12 -0
- data/lib/switchboard/commands/command.rb +85 -0
- data/lib/switchboard/commands/config.rb +1 -0
- data/lib/switchboard/commands/config/config.rb +20 -0
- data/lib/switchboard/commands/default.rb +40 -0
- data/lib/switchboard/commands/disco.rb +3 -0
- data/lib/switchboard/commands/disco/disco.rb +13 -0
- data/lib/switchboard/commands/disco/info.rb +39 -0
- data/lib/switchboard/commands/disco/items.rb +30 -0
- data/lib/switchboard/commands/grep.rb +23 -0
- data/lib/switchboard/commands/help.rb +1 -0
- data/lib/switchboard/commands/help/help.rb +15 -0
- data/lib/switchboard/commands/last.rb +1 -0
- data/lib/switchboard/commands/last/last.rb +34 -0
- data/lib/switchboard/commands/pep.rb +3 -0
- data/lib/switchboard/commands/pep/location.rb +97 -0
- data/lib/switchboard/commands/pep/pep.rb +7 -0
- data/lib/switchboard/commands/pep/tune.rb +85 -0
- data/lib/switchboard/commands/pubsub.rb +16 -0
- data/lib/switchboard/commands/pubsub/affiliations.rb +34 -0
- data/lib/switchboard/commands/pubsub/config.rb +42 -0
- data/lib/switchboard/commands/pubsub/create.rb +41 -0
- data/lib/switchboard/commands/pubsub/delete.rb +32 -0
- data/lib/switchboard/commands/pubsub/info.rb +48 -0
- data/lib/switchboard/commands/pubsub/items.rb +40 -0
- data/lib/switchboard/commands/pubsub/listen.rb +20 -0
- data/lib/switchboard/commands/pubsub/nodes.rb +37 -0
- data/lib/switchboard/commands/pubsub/options.rb +40 -0
- data/lib/switchboard/commands/pubsub/publish.rb +44 -0
- data/lib/switchboard/commands/pubsub/pubsub.rb +25 -0
- data/lib/switchboard/commands/pubsub/purge.rb +32 -0
- data/lib/switchboard/commands/pubsub/retract.rb +38 -0
- data/lib/switchboard/commands/pubsub/subscribe.rb +34 -0
- data/lib/switchboard/commands/pubsub/subscriptions.rb +35 -0
- data/lib/switchboard/commands/pubsub/unsubscribe.rb +30 -0
- data/lib/switchboard/commands/register.rb +40 -0
- data/lib/switchboard/commands/roster.rb +5 -0
- data/lib/switchboard/commands/roster/add.rb +27 -0
- data/lib/switchboard/commands/roster/list.rb +26 -0
- data/lib/switchboard/commands/roster/online.rb +28 -0
- data/lib/switchboard/commands/roster/remove.rb +25 -0
- data/lib/switchboard/commands/roster/roster.rb +7 -0
- data/lib/switchboard/commands/unregister.rb +21 -0
- data/lib/switchboard/component.rb +36 -0
- data/lib/switchboard/core.rb +311 -0
- data/lib/switchboard/ext/delegate.rb +21 -0
- data/lib/switchboard/ext/instance_exec.rb +16 -0
- data/lib/switchboard/helpers/oauth_pubsub.rb +44 -0
- data/lib/switchboard/helpers/pubsub.rb +28 -0
- data/lib/switchboard/jacks.rb +7 -0
- data/lib/switchboard/jacks/auto_accept.rb +16 -0
- data/lib/switchboard/jacks/debug.rb +17 -0
- data/lib/switchboard/jacks/echo.rb +7 -0
- data/lib/switchboard/jacks/notify.rb +15 -0
- data/lib/switchboard/jacks/oauth_pubsub.rb +23 -0
- data/lib/switchboard/jacks/pubsub.rb +21 -0
- data/lib/switchboard/jacks/roster_debug.rb +23 -0
- data/lib/switchboard/settings.rb +49 -0
- data/lib/switchboard/switchboard.rb +12 -0
- data/lib/switchboard/version.rb +3 -0
- data/switchboard.gemspec +15 -0
- 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,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,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
|