« JRubyGems Release | Main | Versioning your IDEA module meta-data (.iml) »

February 3, 2008

Google Talking with JRuby and Smack

We've kicked off a new project that extends well beyond standard webapp frameworks and tools including significant instant messaging integration. Portions of the project will use Ruby on Rails for web functionality. I know that the open source Jabber software from Jive Software is excellent quality and feature rich. Would JRuby be a good tool for leveraging these Java libraries in a Ruby environment?

Here's a toy chat client and parrot bot I wrote for the Google Talk service using JRuby and the Java Smack API from Jive's open source portal ignite realtime.

Dependencies

I'm using a local install of JRuby 1.1RC1 and downloaded the Smack 3.0.4 API jars.

  lib/
    smack.jar
    smackx_debug.jar
    smackx.jar

SmackHelper

I wrote a Ruby module (lib/smack_helper.rb) that provides a simple wrapper around the Smack classes:

require 'java'
require 'smack.jar'
require 'smackx.jar'
require 'smackx-debug.jar'

# Helper methods for Smack API
module SmackHelper

  # Enable debug console. Do this before opening any connections
  def debug
    org.jivesoftware.smack.XMPPConnection.DEBUG_ENABLED = true;
  end

  # Connect to Google Talk service and log in
  def connect(username, password, server = 'talk.google.com', port = 5222, service = 'gmail.com')
    config = org.jivesoftware.smack.ConnectionConfiguration.new(server, port, service)
    @connection = org.jivesoftware.smack.XMPPConnection.new(config)
    @connection.connect
    @connection.login(username, password)
    @connection
  end

  # Start a chat with a user on Google Talk
  def chat(username)
    puts "Chatting with #{username}..."
    @chat = @connection.chat_manager.create_chat(username, StdoutMessageListener.new)
  end

  # Parrot back to the other participant in the current chat everything she says
  def parrot
    puts "Parroting #{@chat.participant}."
    @chat.add_message_listener ParrotMessageListener.new
  end

  # Send a message for the current chat
  def say(message)
    @chat.send_message message
  end

  # Disconnect from the Jabber service
  def disconnect
    @connection.disconnect
    @connection = nil
    @chat = nil
    puts "Disconnected."
  end

  # Implements MessageListener to parrot everything received
  class ParrotMessageListener
    include org.jivesoftware.smack.MessageListener # implement interface

    def processMessage(chat, message) # process_message does not work
      chat.send_message "Polly says '#{message.body}'?" if !message.body.empty?
    end
  end

  # Implements MessageListener to print message body to STDOUT
  class StdoutMessageListener
    include org.jivesoftware.smack.MessageListener

    def processMessage(chat, message)
      puts "#{chat.participant}: #{message.body}" if !message.body.empty?
    end
  end
end

The only thing that tripped me up here was implementing the org.jivesoftware.smack.MessageListener interface. You implement a Java interface by including it in your Ruby class. At first I implemented 'process_message' instead of 'processMessage' which is not an entirely unreasonable thing to do given JRuby's nice conversion from CamelCase to Ruby conventions in all other cases. The bummer was that I got no errors with that implementation. My listeners just didn't receive messages. JRuby's proxy implementation should probably do a better job of handling this.

chat.rb

A script to use this module might look like:

$:<< File.dirname(__FILE__) + '/lib/'
require 'smack_helper'
include SmackHelper

debug                      # enable connection debug console
connect 'asalant', '****'  # connect to Google Talk and log in
chat 'myfriend'            # chat with myfriend@gmail.com 
parrot                     # parrot everything they say

# read messages from STDIN (empty message ends chat)
while !(msg = gets.chomp).empty? 
  say msg
end

disconnect            
exit

A chat session might look like:

$ jruby chat.rb 
Chatting with myfriend...
Parroting myfriend.
You there?
myfriend: I am!
myfriend: Did you just repeat me?
No, that's my parrot.
myfriend: Oh. That's lame.

Disconnected.
$ 

irb

Or you can use it from irb:

$ jruby -S irb
irb(main):001:0> $:<< File.dirname(__FILE__) + '/lib/'
=> ["/usr/local/jruby-1.1RC1/lib/ruby/site_ruby/1.8",..."]
irb(main):002:0> require 'smack_helper'
=> true
irb(main):003:0> include SmackHelper
=> Object
irb(main):004:0> connect 'asalant', '****'
=> #
irb(main):005:0> chat 'myfriend'
Chatting with myfriend...
=> #
irb(main):006:0> say 'you there?'
=> nil
irb(main):007:0> myfriend: I am!
say 'oh, hi.'
=> nil
irb(main):008:0> disconnect
Disconnected.
=> nil
irb(main):009:0> exit

Posted by Alon Salant at February 3, 2008 2:09 PM

Comments

Hey Alon, you might also want to check out XMPP4R, a pretty nice tool for doing this in pure Ruby. See http://www.rubyinside.com/introduction-to-xmpp-and-xmpp4r-for-ruby-developers-709.html for an overview.

Posted by: Chris Kampmeier at February 3, 2008 11:57 PM

Post a comment




Remember Me?