Skip to Navigation

Muffinlabs!

twitter

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

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.

WhalePail is a simple web app to generate RSS feeds for a variety of Twitter data. I wrote it to help me keep track of the collection of bots I have running on Twitter. I used to just have a couple panels in TweetDeck to watch them, but TweetDeck sucks, and so does having to watch it all the time. Now, I can get a daily summary of their activity in my RSS feed, which is much easier to deal with, and I can scrap TweetDeck in favor of a much better client.

To use the site, you'll need to authenticate yourself via Twitter. Then you can setup checks for tweets, mentions, or just search for a phrase. You get the results back as an RSS feed. You can specify daily checks, or a couple times a day. This is handy if you're checking for something that might have enough volume that Twitter's API might flush it out more than daily. I might add some other options like JSON output later.

The code runs on Sinatra, and is available as twitter-rss-digest on github. I used the sinitter project as a starting point.

Twitter Bots

02
Dec 2009

I've written three bots which are chugging away on Twitter. They are:

  • @for_a_dollar
  • @iaminigomontoya
  • @Betelgeuse_3

@for_a_dollar was the first bot, and is pretty basic. If you tweet the word "Robocop", you'll get a response with a quote from the movie. It's fun, and reveals several things, but mostly that a lot of people that talk about Robocop don't know anything about the movie.

@iaminigomontoya was next, and is based on The Princess Bride. If you tweet "Inconceivable!" (with the exclamation point - very important!), you'll get a classic response. I wanted this bot to be nominally more interactive. So, if you respond to that tweet, you'll get another response. This bot is probably the most popular. People seem to fall into two categories on this one. 99% of the people who trigger this bot know exactly why they got it, and they love it. The other 1% don't have a fucking clue, and without fail seem to be conservative douches. The bot basically got into an argument, summed up nicely here:

Twitter QuoteTwitter Quote

@Betelgeuse_3 is the most recent bot. This one just seemed like it needed to be done. If you tweet the word "beetlejuice" three times, you'll get a response. I can't believe no one had come up with this yet! My main discovery on this one is that a lot of people in Brazil hit this one for some reason.

If you've stumbled upon this page because one of my bots is irritating you, just let me know and I'll stop it from happening. I only intended this to be fun, not spammish.

Everyone loves Robocop right? He's a police officer and a robot, it's the greatest pairing since PB+J. Plus he has an affection for Unicorns.

Robocop, UnicornRobocop, Unicorn

We can learn a lot from Robocop. I mean, just check out the Wikipedia entry:

Set in a crime-ridden Detroit, Michigan in the near future, RoboCop centers on a police officer who is murdered brutally and subsequently re-created as a super-human cyborg known as "RoboCop". RoboCop includes larger themes regarding the media, gentrification and human nature in addition to being an action film.

Having watched the movie 100 times during my teen years, I can assert that it definitely has all those larger themes covered. I eagerly away the Criterion Collection DVD. Robocop spawned sequels, a stage adaptation, and also a musical.

One of the best catchlines from Robocop is "I'd buy that for a dollar". From Wikipedia:

A joke among people who know RoboCop is a popular, but inane TV show with the catchphrase "I'd buy that for a dollar!", which people in the film's future universe find humorous. The star is the goofy Bixby Snyder (S.D. Nemeth). Neither the name of the show nor the character are ever revealed in the movie, although girls are heard to greet him with "Bixby!" and "Happy birthday Dave!" On the DVD commentary, Edward Neumeier comments that somehow the explanation & history of this television show was never included in the script. A deleted scene from the DVD finally reveals the show's name to be It's Not My Problem!, which is also a reference to one of the film's major themes of greed and personal satisfaction.

Allow me to introduce Bixby Snyder to Twitter. Thanks to a nifty ruby gem Twibot, anyone who mentions Robocop in a tweet will get a taste of nostalgia in return. Here's the source code if someone wants to play:

#!/usr/bin/ruby
require 'rubygems'
require 'twibot'

# Receive messages, and tweet them publicly
search "robocop", :lang => "en" do |message, params|
  tmp = client.status :get, message.id
  post_reply tmp, "I'd buy that for a dollar!"
end

You learn two things while writing a Robocop-based twitter bot:

1 - People talk about Robocop a lot more than you think on Twitter. And I don't mean the Kanye West song.
2 - Writing bots is really fun.

Syndicate content