qswarm 0.0.21 → 1.0.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/README.md +264 -1
- data/bin/qswarm +9 -6
- data/lib/qswarm.rb +17 -3
- data/lib/qswarm/agent.rb +160 -40
- data/lib/qswarm/connection.rb +25 -0
- data/lib/qswarm/connections/amqp.rb +167 -0
- data/lib/qswarm/connections/logger.rb +27 -0
- data/lib/qswarm/connections/twitter.rb +148 -0
- data/lib/qswarm/connections/xmpp.rb +92 -0
- data/lib/qswarm/dsl.rb +56 -12
- data/lib/qswarm/swarm.rb +5 -24
- data/lib/qswarm/version.rb +1 -1
- data/qswarm.gemspec +6 -2
- metadata +64 -23
- data/lib/qswarm/broker.rb +0 -118
- data/lib/qswarm/listener.rb +0 -99
- data/lib/qswarm/loggable.rb +0 -15
- data/lib/qswarm/speaker.rb +0 -76
- data/lib/qswarm/speakers/http.rb +0 -67
- data/lib/qswarm/speakers/irc.rb +0 -107
- data/lib/qswarm/speakers/mysql.rb +0 -39
- data/lib/qswarm/speakers/nc.rb +0 -41
@@ -0,0 +1,25 @@
|
|
1
|
+
module Qswarm
|
2
|
+
class Connection
|
3
|
+
attr_reader :format
|
4
|
+
|
5
|
+
def initialize(agent, name, args, &block)
|
6
|
+
@agent = agent
|
7
|
+
@name = name
|
8
|
+
@args = args
|
9
|
+
@on_connect = block_given? ? block : false
|
10
|
+
|
11
|
+
@format = args[:format] || :raw
|
12
|
+
end
|
13
|
+
|
14
|
+
def emit(payload)
|
15
|
+
@agent.emit(@name, :payload => OpenStruct.new(:raw => payload, :format => @format))
|
16
|
+
end
|
17
|
+
|
18
|
+
def sink(args, payload)
|
19
|
+
Qswarm.logger.info ">>> #{payload.raw}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'amqp'
|
2
|
+
require 'cgi'
|
3
|
+
require 'uuid'
|
4
|
+
require 'json'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
module Qswarm
|
8
|
+
module Connections
|
9
|
+
class Amqp < Qswarm::Connection
|
10
|
+
include Qswarm::DSL
|
11
|
+
|
12
|
+
# dsl_accessor :name, :host, :port, :user, :pass, :vhost, :exchange_type, :exchange_name, :durable
|
13
|
+
@@connection = {}
|
14
|
+
|
15
|
+
def initialize(agent, name, args, &block)
|
16
|
+
# Set some defaults
|
17
|
+
@host = 'localhost'
|
18
|
+
@port = 5672
|
19
|
+
@user = 'guest'
|
20
|
+
@pass = 'guest'
|
21
|
+
@vhost = ''
|
22
|
+
@durable = true
|
23
|
+
@prefetch = args[:prefetch] || 0
|
24
|
+
|
25
|
+
decode_uri(args[:uri]) if args[:uri]
|
26
|
+
|
27
|
+
@queues = {}
|
28
|
+
@channels = {}
|
29
|
+
@exchange = nil
|
30
|
+
@instances = nil
|
31
|
+
|
32
|
+
@queue_args = { :auto_delete => true, :durable => true, :exclusive => true }.merge! args[:queue_args] || {}
|
33
|
+
@subscribe_args = { :exclusive => false, :ack => false }.merge! args[:subscribe_args] || {}
|
34
|
+
@bind_args = args[:bind_args] || {}
|
35
|
+
@exchange_type = args[:exchange_type] || :direct
|
36
|
+
@exchange_name = args[:exchange_name] || ''
|
37
|
+
@exchange_args = { :durable => true }.merge! args[:exchange_args] || {}
|
38
|
+
@uuid = UUID.generate if args[:uniq]
|
39
|
+
@bind = args[:bind]
|
40
|
+
|
41
|
+
Signal.trap("INT") do
|
42
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"].close do
|
43
|
+
EM.stop { exit }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def queue(name, routing_key = '', args = nil)
|
51
|
+
@queues["#{name}/#{routing_key}"] ||= begin
|
52
|
+
Qswarm.logger.debug "Binding queue #{name}/#{routing_key}"
|
53
|
+
@queues["#{name}/#{routing_key}"] = channel(name, routing_key).queue(name, args).bind(exchange(channel(name, routing_key)), @bind_args.merge(:routing_key => routing_key))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def exchange(channel = nil)
|
58
|
+
@exchange ||= begin
|
59
|
+
@exchange = AMQP::Exchange.new(channel ||= AMQP::Channel.new(connection, :auto_recovery => true), @exchange_type, @exchange_name, @exchange_args) do |exchange|
|
60
|
+
Qswarm.logger.debug "Declared #{@exchange_type} exchange #{@vhost}/#{@exchange_name}"
|
61
|
+
exchange.on_return do |basic_return, metadata, payload|
|
62
|
+
Qswarm.logger.error "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# ruby-amqp currently limits to 1 consumer per queue (to be fixed in future) so can't pool channels
|
69
|
+
def channel(name, routing_key = '')
|
70
|
+
@channels["#{name}/#{routing_key}"] ||= begin
|
71
|
+
Qswarm.logger.debug "Opening channel for #{name}/#{routing_key}"
|
72
|
+
@channels["#{name}/#{routing_key}"] = AMQP::Channel.new(connection, AMQP::Channel.next_channel_id, :auto_recovery => true, :prefetch => @prefetch) do |c|
|
73
|
+
@channels["#{name}/#{routing_key}"].on_error do |channel, channel_close|
|
74
|
+
Qswarm.logger.error "[channel.close] Reply code = #{channel_close.reply_code}, reply text = #{channel_close.reply_text}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def connection
|
81
|
+
# Pool connections at the class level
|
82
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"] ||= begin
|
83
|
+
Qswarm.logger.debug "Connecting to AMQP broker #{self.to_s}"
|
84
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"] = AMQP.connect(self.to_s, :heartbeat => 30, :on_tcp_connection_failure => Proc.new { |settings|
|
85
|
+
Qswarm.logger.error "AMQP initial connection failure to #{settings[:host]}:#{settings[:port]}/#{settings[:vhost]}"
|
86
|
+
EM.stop
|
87
|
+
}, :on_possible_authentication_failure => Proc.new { |settings|
|
88
|
+
Qswarm.logger.error "AMQP initial authentication failed for #{settings[:host]}:#{settings[:port]}/#{settings[:vhost]}"
|
89
|
+
EM.stop
|
90
|
+
}
|
91
|
+
) do |c|
|
92
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"].on_recovery do |connection|
|
93
|
+
Qswarm.logger.debug "Recovered from AMQP network failure"
|
94
|
+
end
|
95
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"].on_tcp_connection_loss do |connection|
|
96
|
+
# reconnect in 10 seconds
|
97
|
+
Qswarm.logger.error "AMQP TCP connection lost, reconnecting in 2s"
|
98
|
+
connection.periodically_reconnect(2)
|
99
|
+
end
|
100
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"].on_connection_interruption do |connection|
|
101
|
+
Qswarm.logger.error "AMQP connection interruption"
|
102
|
+
end
|
103
|
+
# Force reconnect on heartbeat loss to cope with our funny firewall issues
|
104
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"].on_skipped_heartbeats do |connection, settings|
|
105
|
+
Qswarm.logger.error "Skipped heartbeats detected"
|
106
|
+
end
|
107
|
+
@@connection["#{@host}:#{@port}/#{@vhost}"].on_error do |connection, connection_close|
|
108
|
+
Qswarm.logger.error "AMQP connection has been closed. Reply code = #{connection_close.reply_code}, reply text = #{connection_close.reply_text}"
|
109
|
+
if connection_close.reply_code == 320
|
110
|
+
Qswarm.logger.error "Set a 30s reconnection timer"
|
111
|
+
# every 30 seconds
|
112
|
+
connection.periodically_reconnect(30)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
Qswarm.logger.debug "Connected to AMQP broker #{self.to_s}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def decode_uri(uri)
|
121
|
+
@user, @pass, @host, @port, @vhost = uri.match(/([^:]+):([^@]+)@([^:]+):([^\/]+)\/(.*)/).captures
|
122
|
+
end
|
123
|
+
|
124
|
+
def ack?
|
125
|
+
@subscribe_args[:ack]
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_s
|
129
|
+
"amqp://#{@user}:#{@pass}@#{@host}:#{@port}/#{CGI.escape('/' + @vhost)}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def status
|
133
|
+
"AMQP connection #{@name.inspect} at #{@args[:uri]}, bound to #{@args[:bind]}/#{@args[:bind_args]} on #{@args[:exchange_type].inspect} exchange #{@args[:exchange_name]}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def run
|
137
|
+
if !@bind.nil?
|
138
|
+
[*@bind].each do |bind|
|
139
|
+
queue(@agent.name.to_s + '.' + @name.to_s + @uuid ||= '', bind, @queue_args).subscribe(@subscribe_args) do |metadata, payload|
|
140
|
+
emit metadata, payload
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
dsl_call(&@on_connect) if @on_connect
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def emit(metadata, payload)
|
149
|
+
Qswarm.logger.info "[#{@agent.name.inspect}] :amqp connection #{@name.inspect} bound to #{metadata.routing_key}, received #{payload.inspect}"
|
150
|
+
|
151
|
+
@agent.emit(@name, :payload => OpenStruct.new(:raw => payload, :headers => (metadata.headers.nil? ? {} : Hash[metadata.headers.map{ |k, v| [k.to_sym, v] }]).merge(:routing_key => metadata.routing_key), :format => @format))
|
152
|
+
metadata.ack if ack?
|
153
|
+
end
|
154
|
+
|
155
|
+
def sink(args, payload)
|
156
|
+
[*args[:routing_key]].each do |routing_key|
|
157
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Sinking #{payload.raw.inspect} to AMQP routing_key #{routing_key.inspect}"
|
158
|
+
if args[:headers] || payload.headers
|
159
|
+
exchange.publish payload.raw, :routing_key => routing_key, :headers => (args[:headers] ? args[:headers] : payload.headers).merge(:routing_key => routing_key)
|
160
|
+
else
|
161
|
+
exchange.publish payload.raw, :routing_key => routing_key
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Qswarm
|
2
|
+
module Connections
|
3
|
+
class Logger < Qswarm::Connection
|
4
|
+
include Qswarm::DSL
|
5
|
+
|
6
|
+
attr_reader :format
|
7
|
+
|
8
|
+
def initialize(agent, name, args, &block)
|
9
|
+
@filename = args[:filename] || '/tmp/qswarm-logger.log'
|
10
|
+
|
11
|
+
super(agent, name, args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def emit(payload)
|
15
|
+
end
|
16
|
+
|
17
|
+
def sink(args, payload)
|
18
|
+
@file.puts payload.raw
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
@file = File.open(@filename, 'a')
|
23
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Logging to #{@filename}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'tweetstream'
|
2
|
+
require 'twitter'
|
3
|
+
require 'json'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module Qswarm
|
7
|
+
module Connections
|
8
|
+
class Twitter < Qswarm::Connection
|
9
|
+
include Qswarm::DSL
|
10
|
+
|
11
|
+
def initialize(agent, name, args, &block)
|
12
|
+
TweetStream.configure do |config|
|
13
|
+
config.consumer_key = args[:consumer_key]
|
14
|
+
config.consumer_secret = args[:consumer_secret]
|
15
|
+
config.oauth_token = args[:oauth_token]
|
16
|
+
config.oauth_token_secret = args[:oauth_token_secret]
|
17
|
+
config.auth_method = :oauth
|
18
|
+
end
|
19
|
+
|
20
|
+
@rest_client = ::Twitter::Client.new(
|
21
|
+
:consumer_key => args[:consumer_key],
|
22
|
+
:consumer_secret => args[:consumer_secret],
|
23
|
+
:oauth_token => args[:oauth_token],
|
24
|
+
:oauth_token_secret => args[:oauth_token_secret]
|
25
|
+
)
|
26
|
+
|
27
|
+
@track = args[:track]
|
28
|
+
@follow = args[:follow]
|
29
|
+
@list = args[:list]
|
30
|
+
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def emit(payload)
|
35
|
+
@agent.emit(@name, :payload => OpenStruct.new(payload))
|
36
|
+
end
|
37
|
+
|
38
|
+
def sink(metadata, payload)
|
39
|
+
Qswarm.logger.info ">>> #{payload}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def status
|
43
|
+
"Connected to tweetstream, tracking #{@track.to_s}, following #{@follow.to_s}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
begin
|
48
|
+
if @track
|
49
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Tracking keywords: " + @track.to_s
|
50
|
+
TweetStream::Client.new.track( @track.values.flatten.reject { |k| /^-/.match(k) } ) do |status|
|
51
|
+
@track.each do |group, list|
|
52
|
+
matches = []
|
53
|
+
list.each do |keyword|
|
54
|
+
# Text doesn't include any words in the phrase prefixed with -
|
55
|
+
if keyword.split(' ').select { |k| /^-/.match(k) }.none? { |word| status.text.downcase.include? word[1..-1].downcase }
|
56
|
+
# Text contains all of the words in the phrase
|
57
|
+
if keyword.split(' ').reject { |k| /^-/.match(k) }.all? { |word| status.text.downcase.include? word.downcase }
|
58
|
+
matches << keyword
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if !matches.empty?
|
64
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Sending :track/#{group.inspect} #{status.user.screen_name} :: #{status.text} :: #{matches.to_s}"
|
65
|
+
emit(:raw => status.attrs, :headers => { :type => :track, :group => group, :matches => matches })
|
66
|
+
end
|
67
|
+
end
|
68
|
+
# end.on_limit do |skip_count|
|
69
|
+
# Qswarm.logger.error "[#{@agent.name.inspect} #{@name.inspect}] There were #{skip_count} tweets missed because of rate limiting."
|
70
|
+
end.on_error do |message|
|
71
|
+
Qswarm.logger.error "[#{@agent.name.inspect} #{@name.inspect}] #{message}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if @follow
|
76
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Tracking Users: " + @follow.to_s
|
77
|
+
TweetStream::Client.new.follow( *@follow.values.flatten ) do |status|
|
78
|
+
@follow.each do |group, users|
|
79
|
+
if users.include?(status.user.id)
|
80
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Sending :follow/#{group.inspect} #{status.user.screen_name} :: #{status.text}"
|
81
|
+
emit(:raw => status.attrs, :headers => { :type => :follow, :group => group, :user_id => status.user.id })
|
82
|
+
end
|
83
|
+
end
|
84
|
+
# end.on_limit do |skip_count|
|
85
|
+
# Qswarm.logger.error "[#{@agent.name.inspect} #{@name.inspect}] There were #{skip_count} tweets missed because of rate limiting."
|
86
|
+
end.on_error do |message|
|
87
|
+
Qswarm.logger.error "[#{@agent.name.inspect} #{@name.inspect}] #{message}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
rescue TweetStream::ReconnectError
|
92
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Hit max reconnects, restarting tweetstream in 60 seconds ..."
|
93
|
+
EM.timer(60, run)
|
94
|
+
end
|
95
|
+
|
96
|
+
if @list
|
97
|
+
timer = 30
|
98
|
+
since_id = {}
|
99
|
+
|
100
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Tracking List: " + @list.to_s + " every #{timer} seconds"
|
101
|
+
|
102
|
+
@list.each do |group, lists|
|
103
|
+
lists.each do |user, slug|
|
104
|
+
@rest_client.list_timeline(user, slug).each do |status|
|
105
|
+
since_id["#{user}/#{slug}"] = status.attrs[:id] and break
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
EventMachine::PeriodicTimer.new(timer) do
|
110
|
+
lists.each do |user, slug|
|
111
|
+
begin
|
112
|
+
@rest_client.list_timeline(user, slug, { :since_id => since_id["#{user}/#{slug}"] }).each do |status|
|
113
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Sending :list/#{slug.inspect} #{status.attrs[:user][:screen_name]} :: #{status.text}"
|
114
|
+
emit(:raw => status.attrs, :headers => { :type => :list, :group => group, :user_id => user, :slug => slug })
|
115
|
+
since_id["#{user}/#{slug}"] = status.attrs[:id]
|
116
|
+
end
|
117
|
+
rescue ::Twitter::Error::ClientError
|
118
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Twitter REST API client error"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
dsl_call(&@on_connect) if @on_connect
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
__END__
|
132
|
+
|
133
|
+
end.on_reconnect do |timeout, retries|
|
134
|
+
# puts "RECONNECT #{retries}"
|
135
|
+
end
|
136
|
+
c.on_error do |message|
|
137
|
+
puts "ERROR #{message}"
|
138
|
+
end
|
139
|
+
# client.on_delete do |status_id, user_id|
|
140
|
+
# puts "DELETE #{status_id}"
|
141
|
+
# end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
# c.on_limit do |skip_count|
|
146
|
+
# puts "RATE LIMIT"
|
147
|
+
# end
|
148
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'blather/client/dsl'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Qswarm
|
5
|
+
module Connections
|
6
|
+
class QBlather
|
7
|
+
include Blather::DSL
|
8
|
+
|
9
|
+
Blather.logger.level = Logger::INFO
|
10
|
+
|
11
|
+
def on_connect(block)
|
12
|
+
self.instance_eval(&block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Xmpp < Qswarm::Connection
|
17
|
+
include Qswarm::DSL
|
18
|
+
|
19
|
+
def initialize(agent, name, args, &block)
|
20
|
+
@channels = []
|
21
|
+
@connected = false
|
22
|
+
@connection = nil
|
23
|
+
@real_name = args[:real_name] || 'Bot'
|
24
|
+
|
25
|
+
# Use the block for Blather bot DSL
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def sink(args, payload)
|
30
|
+
if @connected
|
31
|
+
# Use channel jid argument from write or from connection itself
|
32
|
+
channel = args.nil? || args[:channel].nil? ? @args[:channel] : args[:channel]
|
33
|
+
join channel;
|
34
|
+
|
35
|
+
[*channel].each do |c|
|
36
|
+
Qswarm.logger.info "[#{@agent.name.inspect} #{@name.inspect}] Sinking #{payload.raw.inspect} to XMPP channel #{c.inspect}"
|
37
|
+
@connection.say c, payload.raw, :groupchat
|
38
|
+
end
|
39
|
+
else
|
40
|
+
EventMachine::Timer.new(5,self.sink(args, payload))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def run
|
45
|
+
xmpp_connect @args[:jid], @args[:password], @args[:channel]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def join(channel)
|
51
|
+
[*channel].each do |c|
|
52
|
+
next if @channels.include? c
|
53
|
+
Qswarm.logger.debug "Joining XMPP channel #{c}"
|
54
|
+
@connection.join c, @real_name
|
55
|
+
@channels << c
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def xmpp_connect(jid, password, channel)
|
60
|
+
Qswarm.logger.debug "Connecting to XMPP server #{jid}"
|
61
|
+
|
62
|
+
s = QBlather.new
|
63
|
+
@connection = s
|
64
|
+
|
65
|
+
s.setup jid, password
|
66
|
+
|
67
|
+
s.when_ready do
|
68
|
+
Qswarm.logger.debug "Connected to XMPP server #{jid}"
|
69
|
+
@connected = true
|
70
|
+
s.on_connect(@on_connect) if @on_connect
|
71
|
+
join channel unless channel.nil?
|
72
|
+
# Hipchat has a 150s inactivity timer
|
73
|
+
EventMachine::PeriodicTimer.new(60) { s << ' ' }
|
74
|
+
end
|
75
|
+
|
76
|
+
s.disconnected do
|
77
|
+
Qswarm.logger.error "Lost XMPP connection to #{jid}, reconnecting..."
|
78
|
+
@connected = false
|
79
|
+
@connection.run
|
80
|
+
end
|
81
|
+
|
82
|
+
EM.defer do
|
83
|
+
s.run
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def status
|
88
|
+
"XMPP connected to #{@args[:jid]}, present in channels #{@channel.to_s}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|