Create a Google Talk bot with Node.js: Part One

Flattr the Create a Google Talk bot with Node.js: Part One post

Programming a chat bot was once the domain of the hardcore hacker tapping packets as they passed over the wire from proprietary client applications to closed source servers, but not any more!

With the open Extensible Messaging and Presence Protocol (XMPP) once closed networks are becoming accessible to the rest of us. I selected Google Talk as it is probably the most well known implementation of XMPP and it is easy and free to sign up for, but Windows Live Messenger, AIM and Skype all support it to some extent.

As an open protocol XMPP is supported by a large number of messaging networks and clients for just about all web enabled devices, which means your bot will also be able to communicate with users on other XMPP servers such as Jabber.

Note

This article was originally published in the March 2012 issue of .net Magazine. You can also download a PDF of the original article.

There is a demo bot for this article documented at http://njsbot.simonholywell.com and the complete code is on github.

Let's get set up

Firstly, an admission; I use Linux as my main desktop operating system so these setup instructions are taken from that environment, but also apply to Mac OS systems. On Windows you can either use Cygwin or a Linux virtual machine such as Ubuntu.

I am going to assume that you are familiar with Node.js and already have it and the NPM (Node Package Manager) installed and working. If not then there is great installation documentation on both their respective websites.

One of the Node.js packages (node-xmpp) we are going to be using has an external dependency on libexpat1-dev for its XML parsing. Some Linux distributions and Mac OS appear to come with this by default, but Ubuntu does not so keep this in mind as you proceed.

It also has an optional dependency on libicu-dev, which is not necessary to get the bot up and running. If you decide to skip installing this library then you can safely ignore the StringPrep warnings the Node.js console issues when you start up your bot.

Back to NPM and we need to install the following packages so we can begin development:

npm install node-xmpp
npm install request

Optionally, although recommended, we can install the supervisor package so that we no longer need to restart the Node.js process.

npm install supervisor -g

Supervisor will continually scan the project directory for any changes and restart the Node.js process for you, which makes development a little bit easier and faster.

Lastly, the bot will need a new Google Account (https://accounts.google.com/NewAccount) so that users are able to contact it. The account name will become your bot's Jabber ID (JID) for example my bot resides at n.js.bot@gmail.com as explained at http://njsbot.simonholywell.com.

Configuration

To keep the project simple and clean we are going to move the configuration settings into a separate JavaScript file, which can be included at the top of the main bot script. Create a new file called config.js and include the following lines of code:

Add your bot's Google Account details for jid and password after which you are ready to begin programming. Create a new file in the same directory called bot.js and start by including the configuration file:

const config = require('./config.js').settings;

Then include the Node.js packages that you installed earlier and util for logging actions in the bot:

const xmpp = require('node-xmpp');
const request_helper = require('request');
const util = require('util');

Connecting to the server

The node-xmpp package makes the process of communicating with XMPP servers very easy:

const conn = new xmpp.Client(config.client);

Notice how the code is using the client element from the configuration file that you created earlier to setup the parameters for the XMPP connection.

To help our bot have the best chances of living a long life the Node.js default socket time out setting of 60 seconds should really be overridden. I am using the following settings in my project:

conn.socket.setTimeout(0);
conn.socket.setKeepAlive(true, 10000);

The keep alive setting should send white space pings through the wire to stop proxies or routers on the way from timing out the connection when it is idle. Without these settings your bot will stop communicating after a minute of inactivity.

Announcing the bot's arrival

The bot is now connected to the Google server, but nobody knows it is online so we have to announce its availability. We must now programme it to send out a presence XML stanza when it connects to the server. This is also an excellent opportunity to set a status message.

Firstly, let's create a simple function to send out the stanza:

function set_status_message(status_message) {
   var presence_elem = new xmpp.Element('presence', { })
                               .c('show').t('chat').up()
                               .c('status').t(status_message);
   conn.send(presence_elem);
}

Whilst this code may look a little complex it is in actual fact quite straightforward. Initially a new XML element called presence is created and then two child elements are added to it.

The show child has the text content of chat, which tells the server that the bot is ready to receive messages.

The up() function call causes the current element to return its parent element. So in this example it causes show to return its parent element presence. Without this the status element would be added as a child of show instead of presence.

The second child, status, is a little more obvious as it sets the status message with its text content.

Finally this whole XML element object is sent to the server using the connection (conn) that was created earlier. So the server will receive an XML element similar to the following given the previous code:

<presence>
    <show>chat</show>
    <status>I am a happy bot</status>
</presence>

For debug purposes you can see the XML in a xmpp.Element() or a stanza from the server (explained later) by calling .toString() on them.

console.log(presence_elem.toString());

That all sounds fantastic, but the function will never be run without being added to an event listener on the connection. In this case it should be triggered by the online event, which occurs right after the bot connects to the server.

conn.on('online', function() {
    set_status_message(config.status_message);
});

Once again the code is using the configuration file to set the content of the initial status message for the bot.

Meeting new people

The bot has now announced its arrival and other people can see it is available on the Google Talk network. Unfortunately if they attempt to connect with your bot in its current state their subscription requests will go unanswered.

Firstly, there is a Google specific roster call that needs to be made so that the bot will be registered to receive subscription request notifications from the server. Without including this function you will not be able see requests coming over the wire.

function request_google_roster() {
    var roster_elem = new xmpp.Element('iq', { from: conn.jid, type: 'get', id: 'google-roster'})
                        .c('query', { xmlns: 'jabber:iq:roster', 'xmlns:gr': 'google:roster', 'gr:ext': '2' });
    conn.send(roster_elem);
}

As you can see this uses the same syntax as the set_status_message() you wrote earlier although it is a fraction more complex. The objects that are passed as the second parameter to xmpp.Element() and c() are interpreted as attributes on the XML element.

To make this a little clearer I will include the XML version of the call:

<iq type='get'
    from='romeo@gmail.com/orchard'
    id='google-roster-1'>
  <query xmlns='jabber:iq:roster' xmlns:gr='google:roster' gr:ext='2'/>
</iq>

As with set_status_message() this function will need to be plumbed in with an event listener on the connection object.

if(config.allow_auto_subscribe) {
    conn.addListener('online', request_google_roster);
    conn.addListener('stanza', accept_subscription_requests);
}

As you may wish to deactivate auto acceptance of subscription requests the listener is only added if the configuration file allows it. Just like when the status message was set earlier the request_google_roster() function is set to listen to the online event. The second listener is addressed in the next section.

Making friends

The bot is now receiving subscription requests so it is time to begin accepting them with the accept_subscription_requests() function mentioned in the previous code sample.

function accept_subscription_requests(stanza) {
    if(stanza.is('presence')
       && stanza.attrs.type === 'subscribe') {
        var subscribe_elem = new xmpp.Element('presence', {
            to: stanza.attrs.from,
            type: 'subscribed'
        });
        conn.send(subscribe_elem);
    }
}

This function has already been added to a listener for the stanza event in the previous section. The event is triggered not only by subscription requests so each incoming stanza is checked for the type of subscription.

To accept the request another simple XML element is sent back to the requesting party, which is obtained from the subscription stanza they sent to the bot with stanza.attrs.from. Setting the type to subscribed accepts their subscription request whilst setting it to unsubscribed will reject their request.

Connect with your bot

This is the first point at which connecting with your bot will give you any meaningful results so let's give it a run. On your system's console run the following command (if you have not installed supervisor then substitute it for node).

supervisor bot.js

Now logging into your own Google Talk account (not the bot account you created earlier) add your bot as a contact. If the code is working properly then you will now see your bot online with the status message you set in the configuration file.

As mentioned earlier you will not need to restart the node process as supervisor will do this for you whenever you save changes to the project's files.

'til next time

In part one of the tutorial you have successfully written a bot in Node.js that can announce its presence and auto accept new subscription requests. I will guide you through adding new functionality and commands to your bot in the next instalment.

Once you have completed part two your bot will be able to provide users with help information, bounce messages back and search twitter for user supplied keywords. It will also become clear just how easy it is to add your own custom commands to your project.

Part two of this is article is now also posted to my blog - you should check it out!

Stay in touch

If you liked this article then please follow me on Twitter and let me know.

Note

Part two of this article is also published on my blog.

For now the complete code is on github.

This article was originally published in the March 2012 issue of .net Magazine.

In tandem with the Google bot tutorial I wrote four smaller articles: