Looking at XMPP and the XMPP4R Gem

XMPP

XMPP Official Logo


XMPP is “an open, XML-inspired protocol for near-real-time, extensible instant messaging and presence information.” — Wikipedia

The Extensible Messaging and Presence Protocol (XMPP) is an open technology for real-time communication, which powers a wide range of applications including instant messaging, presence, multi-party chat, voice and video calls, collaboration, lightweight middleware, content syndication, and generalized routing of XML data. — xmpp.org

The eXtensible Messaging and Presence Protocol, also known as Jabber (the protocol was originally named Jabber and was developed by the Jabber open-source community) is a protocol to exchange messages between 2 entities. Those messages are transmitted over the wire in the XML format. It’s used by Jabber, Google Talk, Google Wave, Pidgin, Kopete, and all sorts of open source instant messaging applications.

Advantages of XMPP

Decentralized

The XMPP network is decentralized in nature. There is no master or central server. Any one can own or run a XMPP Server.

Open Standards

The Internet Engineering Task Force has formalized XMPP as an approved instant messaging and presence technology under the name of XMPP (the latest specifications are RFC 6120 and RFC 6121). No royalties are required to implement support of these specifications and their development is not tied to a single vendor.

Stable and Secure

XMPP has been in effect since 1999 and many applications have used this protocol since. Since XMPP servers can be isolated or hosted on a private network behind secure firewalls, it is very secure.

Flexible

One of the main advantage of this protocol is its flexiblilty. Custom functionality can be built on top of XMPP very easily. The flexible nature of the protocol makes it the perfect choice for writing IM-bots, chat clients etc., as it will automatically work with all the IM services XMPP has to offer.

XMPP4R – Ruby Client Library for XMPP

XMPP4R is a Ruby wrapper over the standard XML that XMPP/Jabber/Ejabberd uses, thus allowing us to work with Ruby rather than generate XML.

Installation

gem install xmpp4r
    or
sudo gem install xmpp4r

XMPP Server

Ejabberd is a Jabber/XMPP server built using Erlang and is open-source. Another popular alternative for Ejabberd is Openfire. Installation instructions for ejabbered can be found here. We wont be using a XMPP Server like ejabbered or Openfire in this example, instead opting for a Google account, which is easy and convenient.

XMPP Bot

Building a Bot is an ideal way to introduce the XMPP protocol and the Ruby Wrapper for XMPP – XMPP4R. As such, I will be building a bot that responds to our messages and replies with custom messages or results.

Install Gems/Libraries Required

gem install xmpp4r

Connecting to the Jabber Server

require 'xmpp4r'
require 'xmpp4r/client'
include Jabber

class Bot

  attr_reader :client

  def initialize jabber_id
    @jabber_id = jabber_id
    @jabber_password = ENV['jabber_password']
  end

  def connect
    jid = JID.new(@jabber_id)
    @client = Client.new jid
    @client.connect
    @client.auth @jabber_password
    @client.send(Presence.new.set_type(:available))
    puts "Hurray...!!  Connected..!!"
  end
end

Bot.new('neo@codingarena.in').connect

The above code illustrates how to connect to our Jabber server, or in our case, Google’s servers.
The Jabber ID is provided when instantiating a new Bot instance and the password is retrieved from an environment variable. The connect method creates a new object of the Jabber client, connects to the server, and authenticates using the password specified. Also the presence is set to ‘available’. Other options that can be passed are :busy, :invisible.

Sending and Receiving Messages

Sending a message is as simple as follows:

message = Message.new("manu@codingarena.in", "Hello... Manu?")
message.type=:chat
@client.send(message)

The add_method_callback method can be used for recieving and processing the messages that our bot receives.

@client.add_message_callback do |message|
  unless message.body.nil? && message.type != :error
    puts "Received message: #{message.body}"
    #Echo the received message back to the sender.
    reply = Message.new(message.from, message.body)
    reply.type = message.type
    @client.send(reply)
  end
end

Accepting New Invites

def roster
  @roster ||= Roster::Helper.new(@client)
end

def process_invite
  roster.add_subscription_request_callback do |_, presence|
    inviter = JID::new(presence.from)
    roster.accept_subscription(inviter)
    invite(JID::new(inviter))
  end
end

Roster is our bot’s contact list. Whenever a new user adds our bot and sends an invite, the roster_add_subscription_request_callback gets triggered, which would automatically accept the new invite.

Teach our bot some commands

Having built a simple and basic bot using XMPP, we will now try to process the messages received by our bot and play around little bit.

How about we ask our bot some questions or commands and see if it responds to our message with custom or processed replies? The message received by out chat bot could be processed so that it will reply to some specific commands. For example, our bot will process ‘Help’ to return all the commands that our bot knows or ‘Time’ to reply with the current time.

@client.add_message_callback do |message|
  unless message.body.nil? && message.type != :error
    reply = case message.body
      when "Time" then reply(message, "Current time is #{Time.now}")
      when "Help" then reply(message, "Available commands are: 'Time', 'Help', 'Latest sitepoint articles.")
      when "Latest sitepoint articles" then latest_sitepoint_articles(message)
      else reply(message, "You said: #{message.body}")
    end
  end
end

def reply message, reply_content
  reply_message = Message.new(message.from, reply_content)
  reply_message.type = message.type
  @client.send reply_message
end

def latest_sitepoint_articles message
  feeds = SitepointFeedParser.get_feeds
  feeds.items.each do |item|
    reply(message, item.link)
  end
end

class SitepointFeedParser
  URL = 'http://www.sitepoint.com/feed/'
  class << self
    def get_feeds
      open(URL) do |rss|
        @feeds = RSS::Parser.parse(rss)
      end
      @feeds
    end
  end
end

When a user sends ‘Latest sitepoint articles’ our bot would parse the feeds from sitepoint.com and return the links to the latest articles. The RSS module is used for parsing the feeds, which are iterated over and each link is sent back to the user.

Daemonize the Bot

require 'rubygems'
require 'daemons'

Daemons.run('bot.rb',
  {
    app_name: 'myIMBot',
    monitor: true,
    log_output: false
  }
)

Using the daemons gem, we run our bot as a daemon. It also gives us options to start, restart, and stop the daemon using ruby im.rb start, ruby im.rb restart, and ruby im.rb stop, respectively. We can get the current status of the daemon using ruby im.rb status.

Also, monit gives us options for monitoring the daemon. The output of the daemon can be logged if log_output is set to true.

Putting It All Together

require 'xmpp4r'
require 'xmpp4r/client'
require 'xmpp4r/roster'
require 'rss'
require 'open-uri'
include Jabber

class Bot

  attr_reader :client

  def initialize jabber_id
    @jabber_id = jabber_id
    @jabber_password = ENV['jabber_password']
  end

  def connect
    jid = JID.new(@jabber_id)
    @client = Client.new jid
    @client.connect
    @client.auth @jabber_password
    @client.send(Presence.new.set_type(:available))
    puts "Hurray...!!  Connected..!!"
    on_message
  end

  private
  def on_message
    mainthread = Thread.current
    @client.add_message_callback do |message|
      unless message.body.nil? && message.type != :error
        reply = case message.body
          when "Time" then reply(message, "Current time is #{Time.now}")
          when "Help" then reply(message, "Available commands are: 'Time', 'Help'.")
          when "Latest sitepoint articles" then latest_sitepoint_articles(message)
          else reply(message, "You said: #{message.body}")
        end
      end
    end
    Thread.stop
    @client.close
  end

  def reply message, reply_content
    reply_message = Message.new(message.from, reply_content)
    reply_message.type = message.type
    @client.send reply_message
  end

  def latest_sitepoint_articles message
    feeds = SitepointFeedParser.get_feeds
    feeds.items.each do |item|
      reply(message, item.link)
    end
  end

end

class SitepointFeedParser

  URL = 'http://www.sitepoint.com/feed/'
  class << self
    def get_feeds
      open(URL) do |rss|
        @feeds = RSS::Parser.parse(rss)
      end
      @feeds
    end
  end

end

@bot = Bot.new 'manusajith@gmail.com'
@bot.connect

The code can be found here.

Conclusion

If you havent tried the XMPP or XMPP4R, I would recommend you to give it a shot. XMPP4R has a lot of other options that aren’t covered in this tutorial. Please check the official website for more details.

PS: I am one of the current maintainers of xmpp4r, if you have any questions, just give me a shout. Also, if you find any bugs/issues, please report them. Thanks in advance.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • buildakicker

    GoogleTalk/Hangouts is moving away from this in May… Too bad since i loved the VOIP service with ObiTalk and GV!