Glenn Gillen

Make your own IM bot in Ruby, and interface it with your Rails app

In a super secret project I'm currently working on, I've been looking for ways of making it easier for people to interact with the system without the need to log in to the website. That's included the obvious things like having a RESTful API so they can put their own services and interfaces on top, but that only works for developers or 3rd party application providers. What about your average Joe on the street, how come they get forced into having to log in to the site to update their data, etc.? Why not provide a means of carrying out simple tasks in an application they probably already have? And so an Instant Messaging interface was designed to send a instruction to a robot, and it's relatively simple to get setup.

The best thing about it is that we help give more control and time back to the user, hopefully making the system more appealing to use. No more breaking your train of thought while you go log into a site to check something, just a quick message to a bot and you can forget about it. Or conversely, you wont defer the task because the overhead in writing a reminder is possibly more than the time it would take to just do it. And for those fans of the Getting Things Done (GTD) approach to task management, it means a whole range of tasks can be suddenly just 'get done' in the process phase

So how do we give such amazing power and flexibility to our users? First you're going to need to install the xmpp4r gem which at the time of writing doesn't seem to play nice with the new version of rubygems (I think it's to do with the way new rubygems preserves permissions). The problem appears to be fixed in version 0.3.2.99, but only 0.3.2 is available as a gem. So in the interim, we are going to have to download it directly from the git repository and build it ourselves:

git clone git://github.com/ln/xmpp4r.git xmpp4r
cd xmpp4r
rake gem:install
sudo gem install xmpp4r-simple

And now some ruby code. Connecting is straight-forward:

require 'rubygems'  
require 'xmpp4r-simple'
messenger = Jabber::Simple.new([email protected]', "bot-password")

As is sending a message:

messenger.deliver("[email protected]", "Why hello there Mr. Person, your bot is here now!")

If you wish to have your service sit around and respond to messages, then something you'll need something like the following:

while true
  messenger.received_messages do |msg|  
    puts msg.body  
    messenger.deliver("[email protected]", "Got your message, thanks!")  
  end  
  sleep 2  
end

Now just to point out for those that aren't paying close attention, the above code does the following:

  1. Creates a loop that we will continually go over
  2. Call received_messages and pass any messages into a block, iterating over each one at a time
  3. Stop processing for 2 seconds so the system can take a break and we don't use up all of the resources

Of particular importance once again, I've created an infinite loop here to listen to incoming messages. DO NOT put this code in a rails app, this is the kind of process that needs to be handled outside of rails as a supporting but background task. If you try and kick it off via rails it will process indefinitely and tie up one of your mongrel instances until it dies and likely degrade the performance of your site in the process.

That's all well and good, but wasn't the whole point of this to give our users a more convenient way to interface with our site and their data? Let us have a look a a slightly more useful implementation:

require 'rubygems'  
require 'xmpp4r-simple'
messenger = Jabber::Simple.new([email protected]', "bot-password")
while true
  messenger.received_messages do |msg|
    user = User.find_by_im_name(msg.from)
    if user
      case msg.body
      when /^help /i
        messenger.deliver(msg.from, "Valid commands are......")  
      when /^status /i
        user.status = msg.body.sub(/^status /i)
        user.save
        messenger.deliver(msg.from, "Thanks #{user.first_name}, your status has been updated")  
      when /^balance\?/i
        messenger.deliver(msg.from, "#{user.first_name}, your current remaining balance is #{user.remaining_balance}")  
      else
        messenger.deliver(msg.from, "Sorry #{user.first_name}, I didn't understand that. Message me with 'help' for a list of commands")  
      end
    else
      messenger.deliver(msg.from, "Sorry, but we've not got this account registered on our system. Sign-up or update your details at http://www.mysite.com/")  
    end    
  end  
  sleep 2  
end

The easiest way to have this run now is to call it via script/runner so that we can get access to our ActiveRecord models and interface with the underlying data. All you need to do is kick this off with an init script, setup something like god for process monitoring to ensure it stays alive and keeps within some same memory and CPU constraints.

Glenn Gillen

I'm an advisor to, and investor in, early-stage tech startups. Beyond that I'm an incredibly fortunate husband and father. Working on a developer-facing tool or service? Thinking about starting one? Email me and let me know or come to one of our days to help make it a reality.