Class: Safubot::Twitter::Bot

Inherits:
Object
  • Object
show all
Includes:
Evented
Defined in:
lib/safubot/twitter.rb

Overview

A Twitter::Bot instance provides a Safubot::Bot with Twitter-specific processing.

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods included from Evented

#bind, #emit, #on, #once, #unbind

Constructor Details

- (Bot) initialize(options = {})

Options are passed straight through to ::TweetStream and ::Twitter, but the :username is ours and important.



283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/safubot/twitter.rb', line 283

def initialize(options={})
		defaults = { :username => nil,
		 :consumer_key => nil, :consumer_secret => nil, 
		 :oauth_token => nil, :oauth_token_secret => nil,
		 :auth_method => :oauth }

		@opts = defaults.merge(options)

		DirectMessage.ensure_index('raw.id', :unique => true)
		@username = @opts[:username]
		@client = Object::Twitter::Client.new(@opts)
end

Instance Attribute Details

- (Object) client (readonly)

Returns the value of attribute client



160
161
162
# File 'lib/safubot/twitter.rb', line 160

def client
  @client
end

- (Object) opts (readonly)

Returns the value of attribute opts



160
161
162
# File 'lib/safubot/twitter.rb', line 160

def opts
  @opts
end

- (Object) pid (readonly)

Returns the value of attribute pid



160
161
162
# File 'lib/safubot/twitter.rb', line 160

def pid
  @pid
end

- (Object) stream (readonly)

Returns the value of attribute stream



160
161
162
# File 'lib/safubot/twitter.rb', line 160

def stream
  @stream
end

- (Object) username (readonly)

Returns the value of attribute username



160
161
162
# File 'lib/safubot/twitter.rb', line 160

def username
  @username
end

Instance Method Details

- (Object) handle_message(message)

Stores a DM and creates a matching Request as needed.



180
181
182
183
# File 'lib/safubot/twitter.rb', line 180

def handle_message(message)
		return if message.sender.screen_name == @username
		DirectMessage.from(message).make_request
end

- (Object) handle_request(req)

Emit a request event unless the request is already processed.



175
176
177
# File 'lib/safubot/twitter.rb', line 175

def handle_request(req)
		emit(:request, req) unless req.nil? || req.processed
end

- (Object) handle_tweet(status)

Stores a tweet. If this tweet is directed at us, create a matching Request. Otherwise, emit a :timeline event.



187
188
189
190
191
192
193
194
# File 'lib/safubot/twitter.rb', line 187

def handle_tweet(status)
		return if status.user.screen_name == @username
		if status.text.match(/@#{@username}/i)
 Tweet.from(status).make_request
		else
 emit(:timeline, Tweet.from(status))
		end
end

- (Object) init_stream

Initializes the TweetStream client.



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/safubot/twitter.rb', line 227

def init_stream
		@stream = TweetStream::Client.new(@opts)

		@stream.on_direct_message do |message|
 req = handle_message(message)
 handle_request(req) if req.is_a? Request
		end

		@stream.on_error do |err|
 if err.match(/invalid status code: 401/)
Log.error "TweetStream authentication failure!"
 else
Log.error "Unhandled TweetStream error: #{error_report($!)}"
 end
		end

		@stream.on_inited do
 Log.info("TweetStream client is online at @#{@username} :3")
		end
end

- (Object) pull

Pulls DMs and mentions using the AJAX API. Used in tandem with the streaming API to ensure we don’t miss too much while we’re offline.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/safubot/twitter.rb', line 210

def pull
		begin
 @client.direct_messages.each do |message|
handle_message(message)
 end

 @client.mentions.each do |mention|
handle_tweet(mention)
 end
		rescue ::Twitter::Error::ServiceUnavailable
 Log.error "Twitter: Couldn't pull tweets due to temporary service unavailability."
		rescue Exception => e
 Log.error "Twitter: Unhandled error: #{error_report(e)}"
		end
end

- (Object) reply(tweet, text)

Replies to a tweet using the appropriate mentions.

Parameters:

  • tweet

    A Tweet object to respond to.

  • text

    The response text.



204
205
206
# File 'lib/safubot/twitter.rb', line 204

def reply(tweet, text)
		@client.update("#{reply_header(tweet)} #{text}", :in_reply_to_status_id => tweet.raw['id'])
end

- (Object) reply_header(tweet)

Constructs the appropriate series of mentions for a reply to this tweet.



197
198
199
# File 'lib/safubot/twitter.rb', line 197

def reply_header(tweet)
		(["@#{tweet.username}"] + (tweet.header_mentions - ["@#{@username}"])).join
end

- (Object) run

Starts our TweetStream client running in a new process.



267
268
269
270
271
272
273
# File 'lib/safubot/twitter.rb', line 267

def run
		@pid = Process.fork do
 Signal.trap("TERM") { stop }
 init_stream
 run_stream
		end
end

- (Object) run_stream

Runs the TweetStream client.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/safubot/twitter.rb', line 249

def run_stream
		begin
 @stream.userstream do |status|
req = handle_tweet(status)
handle_request(req) if req.is_a? Request
 end
		rescue Exception => e
 unless e.is_a?(Interrupt) || e.is_a?(SignalException)
Log.error "TweetStream client exited unexpectedly: #{error_report(e)}"
Log.error "Restarting TweetStream client in 5 seconds."
sleep 5; init_stream; run_stream
 end
		else
 Log.info("TweetStream client shutdown complete.")
		end
end

- (Object) send(resp)

Sends a Twitter-sourced Response to the appropriate target.



163
164
165
166
167
168
169
170
171
172
# File 'lib/safubot/twitter.rb', line 163

def send(resp)
		source = resp.request.source
		if source.is_a?(DirectMessage)
 @client.direct_message_create(source.raw['sender']['screen_name'], resp.text)
		elsif source.is_a?(Tweet)
 reply("#{source.header_mentions.join(' ')} #{resp.text}")
		else
 raise NotImplementedError, "Don't know how to send response to a #{req.source.class}!"
		end
end

- (Object) stop

Shut down the TweetStream client.



276
277
278
279
# File 'lib/safubot/twitter.rb', line 276

def stop
		@stream.stop if @stream
		Process.kill("TERM", @pid) if @pid
end