summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xexamples/mix.py162
-rw-r--r--slixmpp/plugins/xep_0369/mix_core.py13
-rw-r--r--slixmpp/plugins/xep_0405/mix_pam.py26
-rw-r--r--slixmpp/plugins/xep_0405/stanza.py18
4 files changed, 219 insertions, 0 deletions
diff --git a/examples/mix.py b/examples/mix.py
new file mode 100755
index 00000000..1c3a34ae
--- /dev/null
+++ b/examples/mix.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2021 Mathieu Pasquet
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+from getpass import getpass
+from argparse import ArgumentParser
+
+import slixmpp
+
+
+class MIXBot(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.rooms = set()
+ 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 mix_message event is triggered whenever a message
+ # stanza is received from any chat room.
+ self.add_event_handler("mix_message", self.mix_message)
+
+ # The mix_participant_info_publish event is triggered whenever
+ # an occupant joins or leaves the channel (not linked to
+ # actual presence)
+ self.add_event_handler("mix_participant_info_publish",
+ self.mix_joined)
+
+
+ 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.
+ """
+ # The goal here is to fetch the already joined MIX channels
+ # which are present in the roster.
+ _, mix_rooms = await self.plugin['xep_0405'].get_mix_roster()
+ for room in mix_rooms:
+ self.rooms.add(room['jid'])
+ self.send_presence()
+ if self.room not in self.rooms:
+ # If we are not joined, we need to. This will carry over
+ # the next restarts
+ await self.plugin['xep_0405'].join_channel(
+ self.room,
+ self.nick,
+ )
+
+ def mix_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['mix']['nick'] != self.nick and self.nick in msg['body']:
+ self.send_message(mto=msg['from'].bare,
+ mbody="I heard that, %s." % msg['mix']['nick'],
+ mtype='groupchat')
+
+ def mix_joined(self, event):
+ """
+ We receive a publish event whenever someone joins the MIX channel.
+ It contains the nickname of the new participant, and the JID when
+ the channel is not a "JID Hidden channel".
+ """
+ participant = event['pubsub_event']['items']['item']['mix_participant']
+ if participant['nick'] != self.nick:
+ self.send_message(mto=event['from'].bare,
+ mbody="Hello, %s" % participant['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="MIX channel to join")
+ parser.add_argument("-n", "--nick", dest="nick",
+ help="MIX 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("MIX channel: ")
+ if args.nick is None:
+ args.nick = input("MIX nickname: ")
+
+ # Setup the MIXBot and register plugins. Note that while plugins may
+ # have interdependencies, the order in which you register them does
+ # not matter.
+ xmpp = MIXBot(args.jid, args.password, args.room, args.nick)
+ xmpp.register_plugin('xep_0030') # Service Discovery
+ xmpp.register_plugin('xep_0199') # XMPP Ping
+ xmpp.register_plugin('xep_0369') # MIX Core
+ xmpp.register_plugin('xep_0405') # MIX PAM
+
+ # Connect to the XMPP server and start processing XMPP stanzas.
+ xmpp.connect()
+ xmpp.process()
diff --git a/slixmpp/plugins/xep_0369/mix_core.py b/slixmpp/plugins/xep_0369/mix_core.py
index 05b9b2fd..688d4e6a 100644
--- a/slixmpp/plugins/xep_0369/mix_core.py
+++ b/slixmpp/plugins/xep_0369/mix_core.py
@@ -72,9 +72,22 @@ class XEP_0369(BasePlugin):
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(stanza.NS)
+ self.xmpp.plugin['xep_0060'].map_node_event(
+ 'urn:xmpp:mix:nodes:participants',
+ 'mix_participant_info',
+ )
+ self.xmpp.plugin['xep_0060'].map_node_event(
+ 'urn:xmpp:mix:nodes:info',
+ 'mix_channel_info',
+ )
def plugin_end(self):
self.xmpp.plugin['xep_0030'].del_feature(feature=stanza.NS)
+ node_map = self.xmpp.plugin['xep_0060'].node_event_map
+ if 'urn:xmpp:mix:nodes:info' in node_map:
+ del node_map['urn:xmpp:mix:nodes:info']
+ if 'urn:xmpp:mix:nodes:participants' in node_map:
+ del node_map['urn:xmpp:mix:nodes:participants']
async def get_channel_info(self, channel: JID) -> InfoType:
""""
diff --git a/slixmpp/plugins/xep_0405/mix_pam.py b/slixmpp/plugins/xep_0405/mix_pam.py
index d690ae3d..f8e5a9d9 100644
--- a/slixmpp/plugins/xep_0405/mix_pam.py
+++ b/slixmpp/plugins/xep_0405/mix_pam.py
@@ -6,13 +6,16 @@
See the file LICENSE for copying permission.
"""
from typing import (
+ List,
Optional,
Set,
+ Tuple,
)
from slixmpp import JID, Iq
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.plugins import BasePlugin
+from slixmpp.stanza.roster import RosterItem
from slixmpp.plugins.xep_0405 import stanza
from slixmpp.plugins.xep_0369 import stanza as mix_stanza
@@ -86,3 +89,26 @@ class XEP_0405(BasePlugin):
iq['client_leave']['channel'] = room
iq['client_leave'].enable('mix_leave')
return await iq.send(**iqkwargs)
+
+ async def get_mix_roster(self, *,
+ ito: Optional[JID] = None,
+ ifrom: Optional[JID] = None,
+ **iqkwargs) -> Tuple[List[RosterItem], List[RosterItem]]:
+ """
+ Get the annotated roster, with MIX channels.
+
+ :return: A tuple of (contacts, mix channels) as RosterItem elements
+ """
+ iq = self.xmpp.make_iq_get(ito=ito, ifrom=ifrom)
+ iq['roster'].enable('annotate')
+ result = await iq.send(**iqkwargs)
+ self.xmpp.event("roster_update", result)
+ contacts = []
+ mix = []
+ for item in result['roster']:
+ channel = item._get_plugin('channel', check=True)
+ if channel:
+ mix.append(item)
+ else:
+ contacts.append(item)
+ return (contacts, mix)
diff --git a/slixmpp/plugins/xep_0405/stanza.py b/slixmpp/plugins/xep_0405/stanza.py
index fe221bd6..58133d98 100644
--- a/slixmpp/plugins/xep_0405/stanza.py
+++ b/slixmpp/plugins/xep_0405/stanza.py
@@ -8,6 +8,7 @@
from slixmpp import JID
from slixmpp.stanza import Iq
+from slixmpp.stanza.roster import Roster, RosterItem
from slixmpp.xmlstream import (
ElementBase,
register_stanza_plugin,
@@ -19,6 +20,7 @@ from slixmpp.plugins.xep_0369.stanza import (
)
NS = 'urn:xmpp:mix:pam:2'
+NS_ROSTER = 'urn:xmpp:mix:roster:0'
class ClientJoin(ElementBase):
@@ -35,9 +37,25 @@ class ClientLeave(ElementBase):
interfaces = {'channel'}
+class Annotate(ElementBase):
+ namespace = NS_ROSTER
+ name = 'annotate'
+ plugin_attrib = 'annotate'
+
+
+class Channel(ElementBase):
+ namespace = NS_ROSTER
+ name = 'channel'
+ plugin_attrib = 'channel'
+ interfaces = {'participant-id'}
+
+
def register_plugins():
register_stanza_plugin(Iq, ClientJoin)
register_stanza_plugin(ClientJoin, Join)
register_stanza_plugin(Iq, ClientLeave)
register_stanza_plugin(ClientLeave, Leave)
+
+ register_stanza_plugin(Roster, Annotate)
+ register_stanza_plugin(RosterItem, Channel)