Google Talking with JRuby and Smack

Alon Salant ·

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</b

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
[/sourcecode]

SmackHelper</b

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</b

A script to use this module might look like:

$:&lt;&lt; File.dirname(__FILE__) + &#039;/lib/&#039;
require &#039;smack_helper&#039;
include SmackHelper

debug                      # enable connection debug console
connect &#039;asalant&#039;, &#039;****&#039;  # connect to Google Talk and log in
chat &#039;myfriend&#039;            # 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</b

Or you can use it from irb:

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