Twitter Bots, now with OAuth Goodness
The one devoted reader of this blog (Googlebot, I'm looking at you) probably remembers that I have a couple of bots running on Twitter. Originally I was using a library called Twibot, which was nice, but never quite worked the way I wanted it to. So eventually, I ended up with my own very simple code.
I just finally updated my bots to authenticate via OAuth, a couple days before the deadline. While I was at it, I refactored most of the code into its own class, which the bots extend to add actual functionality. Here's the base class, which I call 'Skeleton'
require 'rubygems'
require 'twitter_oauth'
require 'yaml'
#
# extend Hash class to turn keys into symbols
#
class Hash
def symbolize_keys!
replace(inject({}) do |hash,(key,value)|
hash[key.to_sym] = value.is_a?(Hash) ? value.symbolize_keys! : value
hash
end)
end
end
#
# base class to handle being a twitter bot
#
class Skeleton
attr_accessor :config
attr_accessor :client
def debug(s)
puts "***** #{s}"
end
def run
load_config
login
search
replies
update_config
end
def default_opts
{
:since_id => @config.has_key?(:since_id) ? @config[:since_id] : 0
}
end
# implement search in the extended class
def search
end
# implement replies in the extended class
def replies
end
# simple wrapper for sending a message
def tweet(txt, params = {})
debug txt
@client.update txt, params
end
# track the most recent msg we've handled
def update_since_id(s)
if @config[:since_id].nil? or s["id"] > @config[:since_id]
@config[:since_id] = s["id"]
end
end
protected
#
# handle oauth for this request. if the client isn't authorized, print
# out the auth URL and get a pin code back from the user
#
def login
@client = TwitterOAuth::Client.new(
:consumer_key => @config[:consumer_key],
:consumer_secret => @config[:consumer_secret],
:token => @config[:token].nil? ? nil : @config[:token],
:secret => @config[:secret].nil? ? nil : @config[:secret]
)
if @config[:token].nil?
request_token = @client.request_token
puts "#{request_token.authorize_url}\n"
puts "Paste your PIN and hit enter when you have completed authorization."
pin = STDIN.readline.chomp
access_token = @client.authorize(
request_token.token,
request_token.secret,
:oauth_verifier => pin
)
if @client.authorized?
@config[:token] = access_token.token
@config[:secret] = access_token.secret
update_config
else
debug "OOPS"
exit
end
end
end
#
# figure out what config file to load
#
def config_file
filename = "#{File.basename($0,".rb")}.yml"
debug "load config: #{filename}"
File.expand_path(filename)
end
def load_config
tmp = {}
begin
File.open( config_file ) { |yf|
tmp = YAML::load( yf )
}
tmp.symbolize_keys! if tmp
rescue Exception => err
debug err.message
tmp = {
:since_id => 0
}
end
# defaults for now, obviously a big hack. this is for botly, at <a href="http://dev.twitter.com/apps/207151">http://dev.twitter.com/apps/207151</a>
if ! tmp.has_key?(:consumer_key)
tmp[:consumer_key] = "hjaOOEeeMpJSqZR7dvhxjg"
tmp[:consumer_secret] = "wA5iqjfCf9aeGMMItqd6ylEEZAbcm7m6R7vVpaQV0s"
end
@config = tmp
end
# write out our config file
def update_config(tmp=@config)
# update datastore
File.open(config_file, 'w') { |f| YAML.dump(tmp, f) }
end
end
</script>
And here's the actual code for my newest bot @dr_rumack:
#!/usr/bin/ruby
require 'skeleton'
class Surely < Skeleton
def search
debug "check for tweets since #{@config[:since_id]}"
#
# search twitter
#
search = @client.search('surely you must be joking', default_opts)
if search != nil
if @config[:since_id].nil? or search["max_id"].to_i > @config[:since_id]
@config[:since_id] = search["max_id"].to_i
end
search["results"].each { |s|
begin
debug s["text"]
txt = "@#{s['from_user']} I am serious, and don't call me Shirley!"
tweet txt, :in_reply_to_status_id => s["id"]
rescue Exception => e
end
}
end
end
end
@sk = Surely.new
@sk.run
Feel free to adapt this code in any way. I'd love to hear of any uses of it. I've thought about making it work more like twibot at some point, if there's any interest.