Looking at XMPP and the XMPP4R Gem

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 = 'https://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 = 'https://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.