Skip to content
Back to top
View this page

# Multi-User Chat (MUC) Bot

Note

If you have any issues working through this quickstart guide join the chat room at slixmpp@muc.poez.io.

If you have not yet installed Slixmpp, do so now by either checking out a version from Git.

Now that you’ve got the basic gist of using Slixmpp by following the echobot example (Slixmpp Quickstart - Echo Bot), we can use one of the bundled plugins to create a very popular XMPP starter project: a Multi-User Chat (MUC) bot. Our bot will login to an XMPP server, join an MUC chat room and “lurk” indefinitely, responding with a generic message to anyone that mentions its nickname. It will also greet members as they join the chat room.

## Joining The Room

As usual, our code will be based on the pattern explained in Slixmpp Quickstart - Echo Bot. To start, we create an `MUCBot` class based on `ClientXMPP` and which accepts parameters for the JID of the MUC room to join, and the nick that the bot will use inside the chat room. We also register an event handler for the session_start event.

    import slixmpp

    class MUCBot(slixmpp.ClientXMPP):

        def __init__(self, jid, password, room, nick):
            slixmpp.ClientXMPP.__init__(self, jid, password)

            self.room = room
            self.nick = nick

            self.add_event_handler("session_start", self.start)

After initialization, we also need to register the MUC (XEP-0045) plugin so that we can make use of the group chat plugin’s methods and events.

    xmpp.register_plugin('xep_0045')

Finally, we can make our bot join the chat room once an XMPP session has been established:

    async def start(self, event):
        await self.get_roster()
        self.send_presence()
        self.plugin['xep_0045'].join_muc(self.room,
                                         self.nick)

Note that as in Slixmpp Quickstart - Echo Bot, we need to include send an initial presence and request the roster. Next, we want to join the group chat, so we call the `join_muc` method of the MUC plugin.

Note

The `plugin` attribute is dictionary that maps to instances of plugins that we have previously registered, by their names.

## Adding Functionality

Currently, our bot just sits dormantly inside the chat room, but we would like it to respond to two distinct events by issuing a generic message in each case to the chat room. In particular, when a member mentions the bot’s nickname inside the chat room, and when a member joins the chat room.

### Responding to Mentions

Whenever a user mentions our bot’s nickname in chat, our bot will respond with a generic message resembling “I heard that, user.” We do this by examining all of the messages sent inside the chat and looking for the ones which contain the nickname string.

First, we register an event handler for the groupchat_message event inside the bot’s `__init__` function.

Note

We do not register a handler for the message event in this bot, but if we did, the group chat message would have been sent to both handlers.

    def __init__(self, jid, password, room, nick):
        slixmpp.ClientXMPP.__init__(self, jid, password)

        self.room = room
        self.nick = nick

        self.add_event_handler("session_start", self.start)
        self.add_event_handler("groupchat_message", self.muc_message)

Then, we can send our generic message whenever the bot’s nickname gets mentioned.

Warning

Always check that a message is not from yourself, otherwise you will create an infinite loop responding to your own messages.

    def muc_message(self, msg):
        if msg['mucnick'] != self.nick and self.nick in msg['body']:
            self.send_message(mto=msg['from'].bare,
                              mbody="I heard that, %s." % msg['mucnick'],
                              mtype='groupchat')

### Greeting Members

Now we want to greet member whenever they join the group chat. To do this we will use the dynamic `muc::room@server::got_online` [1] event so it’s a good idea to register an event handler for it.

Note

The groupchat_presence event is triggered whenever a presence stanza is received from any chat room, including any presences you send yourself. To limit event handling to a single room, use the events `muc::room@server::presence`, `muc::room@server::got_online`, or `muc::room@server::got_offline`.

    def __init__(self, jid, password, room, nick):
        slixmpp.ClientXMPP.__init__(self, jid, password)

        self.room = room
        self.nick = nick

        self.add_event_handler("session_start", self.start)
        self.add_event_handler("groupchat_message", self.muc_message)
        self.add_event_handler("muc::%s::got_online" % self.room,
                               self.muc_online)

Now all that’s left to do is to greet them:

    def muc_online(self, presence):
        if presence['muc']['nick'] != self.nick:
            self.send_message(mto=presence['from'].bare,
                              mbody="Hello, %s %s" % (presence['muc']['role'],
                                                      presence['muc']['nick']),
                              mtype='groupchat')

[1]

this is similar to the got_online event and is sent by the xep_0045 plugin whenever a member joins the referenced MUC chat room.

## Final Product

The final step is to create a small runner script for initialising our `MUCBot` class and adding some basic configuration options. By following the basic boilerplate pattern in Slixmpp Quickstart - Echo Bot, we arrive at the code below. To experiment with this example, you can use:

    python muc.py -d -j jid@example.com -r room@muc.example.net -n lurkbot

which will prompt for the password, log in, and join the group chat. To test, open your regular IM client and join the same group chat that you sent the bot to. You will see `lurkbot` as one of the members in the group chat, and that it greeted you upon entry. Send a message with the string “lurkbot” inside the body text, and you will also see that it responds with our pre-programmed customized message.

    #!/usr/bin/env python3

    # Slixmpp: The Slick XMPP Library
    # Copyright (C) 2010  Nathanael C. Fritz
    # This file is part of Slixmpp.
    # See the file LICENSE for copying permission.

    import logging
    from getpass import getpass
    from argparse import ArgumentParser

    import asyncio
    import slixmpp

    class MUCBot(slixmpp.ClientXMPP):

        """
        A simple Slixmpp bot that will greets those
        who enter the room, and acknowledge any messages
        that mentions the bot's nickname.
        """

        def __init__(self, jid, password, room, nick):
            slixmpp.ClientXMPP.__init__(self, jid, password)

            self.room = room
            self.nick = nick

            # The session_start event will be triggered when
            # the bot establishes its connection with the server
            # and the XML streams are ready for use. We want to
            # listen for this event so that we we can initialize
            # our roster.
            self.add_event_handler("session_start", self.start)

            # The groupchat_message event is triggered whenever a message
            # stanza is received from any chat room. If you also also
            # register a handler for the 'message' event, MUC messages
            # will be processed by both handlers.
            self.add_event_handler("groupchat_message", self.muc_message)

            # The groupchat_presence event is triggered whenever a
            # presence stanza is received from any chat room, including
            # any presences you send yourself. To limit event handling
            # to a single room, use the events muc::room@server::presence,
            # muc::room@server::got_online, or muc::room@server::got_offline.
            self.add_event_handler("muc::%s::got_online" % self.room,
                                   self.muc_online)

        async def start(self, event):
            """
            Process the session_start event.

            Typical actions for the session_start event are
            requesting the roster and broadcasting an initial
            presence stanza.

            Arguments:
                event -- An empty dictionary. The session_start
                         event does not provide any additional
                         data.
            """
            await self.get_roster()
            self.send_presence()
            self.plugin['xep_0045'].join_muc(self.room,
                                             self.nick,
                                             # If a room password is needed, use:
                                             # password=the_room_password,
                                             )

        def muc_message(self, msg):
            """
            Process incoming message stanzas from any chat room. Be aware
            that if you also have any handlers for the 'message' event,
            message stanzas may be processed by both handlers, so check
            the 'type' attribute when using a 'message' event handler.

            Whenever the bot's nickname is mentioned, respond to
            the message.

            IMPORTANT: Always check that a message is not from yourself,
                       otherwise you will create an infinite loop responding
                       to your own messages.

            This handler will reply to messages that mention
            the bot's nickname.

            Arguments:
                msg -- The received message stanza. See the documentation
                       for stanza objects and the Message stanza to see
                       how it may be used.
            """
            if msg['mucnick'] != self.nick and self.nick in msg['body']:
                self.send_message(mto=msg['from'].bare,
                                  mbody="I heard that, %s." % msg['mucnick'],
                                  mtype='groupchat')

        def muc_online(self, presence):
            """
            Process a presence stanza from a chat room. In this case,
            presences from users that have just come online are
            handled by sending a welcome message that includes
            the user's nickname and role in the room.

            Arguments:
                presence -- The received presence stanza. See the
                            documentation for the Presence stanza
                            to see how else it may be used.
            """
            if presence['muc']['nick'] != self.nick:
                self.send_message(mto=presence['from'].bare,
                                  mbody="Hello, %s %s" % (presence['muc']['role'],
                                                          presence['muc']['nick']),
                                  mtype='groupchat')

    if __name__ == '__main__':
        # Setup the command line arguments.
        parser = ArgumentParser()

        # Output verbosity options.
        parser.add_argument("-q", "--quiet", help="set logging to ERROR",
                            action="store_const", dest="loglevel",
                            const=logging.ERROR, default=logging.INFO)
        parser.add_argument("-d", "--debug", help="set logging to DEBUG",
                            action="store_const", dest="loglevel",
                            const=logging.DEBUG, default=logging.INFO)

        # JID and password options.
        parser.add_argument("-j", "--jid", dest="jid",
                            help="JID to use")
        parser.add_argument("-p", "--password", dest="password",
                            help="password to use")
        parser.add_argument("-r", "--room", dest="room",
                            help="MUC room to join")
        parser.add_argument("-n", "--nick", dest="nick",
                            help="MUC nickname")

        args = parser.parse_args()

        # Setup logging.
        logging.basicConfig(level=args.loglevel,
                            format='%(levelname)-8s %(message)s')

        if args.jid is None:
            args.jid = input("Username: ")
        if args.password is None:
            args.password = getpass("Password: ")
        if args.room is None:
            args.room = input("MUC room: ")
        if args.nick is None:
            args.nick = input("MUC nickname: ")

        # Setup the MUCBot and register plugins. Note that while plugins may
        # have interdependencies, the order in which you register them does
        # not matter.
        xmpp = MUCBot(args.jid, args.password, args.room, args.nick)
        xmpp.register_plugin('xep_0030') # Service Discovery
        xmpp.register_plugin('xep_0045') # Multi-User Chat
        xmpp.register_plugin('xep_0199') # XMPP Ping

        # Connect to the XMPP server and start processing XMPP stanzas.
        xmpp.connect()
        asyncio.get_event_loop().run_forever()