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

Jun 26, 2008

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('my-bot@gmail.com', "bot-password")


As is sending a message:

messenger.deliver("a-real-person@gmail.com", "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("a-real-person@gmail.com", "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('my-bot@gmail.com', "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.

Hi, I'm Glenn! 👋 I'm currently Director of Product @ HashiCorp, and we're hiring! If you'd like to come and work with me and help make Terraform Cloud even more amazing we have multiple positions opening in Product ManagementDesign, and Engineering & Engineering Management across a range of levels (i.e., junior through to senior). Please send in an application ASAP so we can get in touch.