Tofu (thetofu) wrote,

Twisted Wokkel Bot

A bot is not a new thing, it has been done in IRC for a long time. In IRC, a bot is a program that listens to messages in a channel and responds to them.

Bots are also being used in XMPP and particularly Multi-User Chat. You can see a weather bot in action at the following url :

http://speeqe.com/room/zzchschat@muc.appriss.com/

Using XMPP you can extend a bot to respond to messages that are not direct chats. XMPP is xml so you can send and respond to xml messages. This makes XMPP better than IRC.

At Chesspark bots are used extensively. There is one in particular used for a King of the Hill game. It combines MUC and PubSub XMPP extensions to achieve a fun game, where you knock your opponent off the hill by winning chess games.

There are many ways to create XMPP bots, but I have currently been working on MUC support for Wokkel. From Wokkel's web page : "Wokkel is collection of enhancements on top of the Twisted networking framework, written in Python. It mostly provides a testing ground for enhancements to the Jabber/XMPP protocol implementation as found in Twisted Words, that are meant to eventually move there. " Wokkel is nice for implementing and staging things before they can go into twisted.

Using the MUC client branch, I will show you how you can create a Jabber MUC bot. We have to use the branch because it is still under development and not in trunk yet. Hopefully it will be soon. Note that everything is not fully implemented but there is enough to make a bot.

This bot will be simple and just keep track of users coming in and out of a room. You will be able to see user activity using a last command.

First we check out the developement branch. Then we install it.


svn co https://svn.ik.nu/wokkel/branches/wokkel-muc-client-support-24 wokkel

python setup.py install



After this we will need to create the python class. Will will put it in the client.py file and call it MUCLastBot.

""" A simple last bot wokkel example """
from twisted.internet import defer
import datetime
from twisted.words.protocols.jabber import jid
from wokkel import muc

class MUCLastBot(muc.MUCClient):
   """ """



All this does it import the python modules we will be using and extend the muc.MUCClient class.

Next, we need to initialize and add 'last' support and methods to the bot.


    def __init__(self, server, room, nick):
        muc.MUCClient.__init__(self)
        self.server   = server
        self.room     = room
        self.nick     = nick
        self.room_jid = jid.internJID(self.room+'@'+self.server+'/'+self.nick)
        self.last     = {}
        self.activity = None

    def _getLast(self, nick):
        return self.last.get(nick.lower())
    
    def _setLast(self, user):
        user.last = datetime.datetime.now()
        self.last[user.nick.lower()] = user
        self.activity = user



This initializes the class with attributes we need and sets up methods to support user activity.


After this, we need to have the bot join a room. This is done with the 'initialized' method in MUCClient. This method is called after the XMPP client authenticates and its observers are initialized.


    def initialized(self):
        """The bot has connected to the xmpp server, now try to join the room.
        """
        self.join(self.server, self.room, self.nick).addCallback(self.initRoom)
        
    @defer.inlineCallbacks
    def initRoom(self, room):
        """Configure the room if we just created it.
        """

        if int(room.status) == muc.STATUS_CODE_CREATED:
            config_form = yield self.getConfigureForm(self.room_jid.userhost())
            
            # set config default
            config_result = yield self.configure(self.room_jid.userhost())
            


This code will make the bot join the room after start up and then will configure room if the status shows that the room was just created. It uses the 'join' method to join the room and when the bot joins the room 'initRoom' will be called.

You will notice the decorator 'inlineCallbacks', you can ignore this. This decorator allows the method to call deferred methods and use yield to get the call back result.

You can go to http://www.twistedmatrix.com to learn more. For our purposes we will just ignore it. :)

Next, we need to handle when someone joins and parts the room.


    def userJoinedRoom(self, room, user):
        """If a user joined a room, make sure they are in the last dict
        """
        self._setLast(user)

    def userLeftRoom(self, room, user):
        self._setLast(user)



All we do is mark their last activity.

We then need to handle commands and logging messages sent by users.


    def receivedGroupChat(self, room, user, body):
        # check if this message addresses the bot
        cmd       = None
        user_nick = None
        try:
            cmd, user_nick = body.split(" ")
        except ValueError:
            # value error means it was a one word body
            cmd = body
        cmd = cmd.replace("!", "")
        method = getattr(self, 'cmd_'+cmd, None)
        if method:
            method(room, user_nick)
        
        # log last message
        user.last_message = body
        self._setLast(user)




This method takes a groupchat message and processes it to see if there is a bot command. It also logs last message activity for the user.

So, the command we want to use is 'last', we need that method inorder to complete the bot.


   def cmd_last(self, room, user_nick):
        """
        """
        if user_nick is None:
            # show last person to do something
            self._sendLast(room, self.activity)
        else:
            u = self._getLast(user_nick)
            if u:
                self._sendLast(room, u)
            else:
                self.groupChat(self.room_jid, 'Sorry %s, That person is unknown to me.' % (user.nick,))

    def _sendLast(self, room, user):
        """ Grab last information from user and room and send it to the room.
        """
        last_message   = getattr(user,'last_message', '')
        last_stamp     = getattr(user,'last', '')
        
        if room.inRoster(user):
            message = """%s is in this room and said '%s' at %s.""" % (user.nick, last_message, last_stamp)
        else:
            message = """%s left this room at %s and last said '%s'.""" % (user.nick, last_stamp, last_message)

        self.groupChat(self.room_jid, message)


The last command checks the activity for the user given, or if no user is given it will then show the last active user. It will also tell you it does not know the user if it has never seen them in the room.

The '_sendLast' method sends that information back to the room as a groupchat.

The bot is now complete, we will need a conf file to start it up. To do this I will use a .tac file for twisted.


from twisted.application import service
from twisted.words.protocols.jabber import jid
from wokkel.client import XMPPClient

from client import MUCLastBot

application = service.Application("lastbot")

xmppclient = XMPPClient(jid.internJID("test@thetofu.com/lastbot"), "test")
xmppclient.logTraffic = True
mucbot = MUCLastBot('chat.speeqe.com','last', 'LastBot')
mucbot.setHandlerParent(xmppclient)
xmppclient.setServiceParent(application)



Simply use twistd to run the bot.


twisted -y muc.tac



That is it, have fun and look forward to more XMPP support in wokkel and twisted!

You can download muc.tac and client.py at the following url:

http://people.chesspark.com/~tofu/wokkel/lastbot/
Tags: bot, jabber, muc, python, twisted, wokkel, xmpp
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic
  • 3 comments