--- /dev/null
+# -*- coding: utf-8 -*-
+# SPDX-FileCopyrightText: 2014, 2020, 2023 kaliko <kaliko@azylum.org>
+# SPDX-FileCopyrightText: 2007-2012 Thomas Perl <thp.io/about>
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from hashlib import sha256
+
+from slixmpp.exceptions import XMPPError
+from slixmpp.xmlstream import tostring
+
+from .plugin import Plugin
+
+
+class RTBL(Plugin):
+ """Spam guard for MUC
+ """
+ pubsub_server = 'example.org'
+ node = 'muc_bans_sha256'
+
+ def __init__(self, bot):
+ Plugin.__init__(self, bot)
+ bot.register_plugin('xep_0059') # Mediated Information eXchange (MIX)
+ bot.register_plugin('xep_0060') # Publish-Subscribe
+ self.handlers = [
+ ('session_start', self._subscribe),
+ ('pubsub_retract', self._retract),
+ ('pubsub_publish', self._publish)
+ ]
+ self.add_handlers()
+ self.blocklist = None
+ self.bot = bot
+
+ def _exit(self):
+ self.rm_handlers()
+ self.bot.unregister_bot_plugin(self)
+
+ async def _subscribe(self, *args, **kwargs):
+ try:
+ nodes = await self.bot['xep_0060'].get_nodes(self.pubsub_server)
+ nodes_av = [_.get('node') for _ in nodes['disco_items']]
+ self.log.debug(f'nodes available: {nodes_av}')
+ if self.node not in nodes_av:
+ self.log.error(f'{self.node} node not available on {self.pubsub_server}')
+ await self._create()
+ iq = await self.bot['xep_0060'].subscribe(self.pubsub_server, self.node)
+ subscription = iq['pubsub']['subscription']
+ self.log.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
+ except XMPPError as error:
+ self.log.error('Could not subscribe %s to node %s: %s',
+ self.bot.boundjid.bare, self.node, error.format())
+ self._exit()
+ return
+ self.blocklist = await self.bot['xep_0060'].get_items(self.pubsub_server, self.node)
+ message = f'Got {len(self.blocklist["pubsub"]["items"])} items in block list'
+ self.log.info(message)
+ # Add got_online handler once the blocklist is set
+ self.bot.add_event_handler(f'muc::{self.bot.room}::got_online', self.got_online)
+
+ async def _create(self):
+ """Try to create node"""
+ try:
+ await self.bot['xep_0060'].create_node(self.pubsub_server, self.node)
+ self.log.info('Created node %s', self.node)
+ except XMPPError as err:
+ self.log.error('Could not create node %s: %s', self.node, err.format())
+ raise XMPPError(f'Could not create node {self.node}') from err
+
+ def _retract(self, msg):
+ """Handler receiving a retract item event."""
+ self.log.debug('Retracted item %s from %s' % (
+ msg['pubsub_event']['items']['retract']['id'],
+ msg['pubsub_event']['items']['node']))
+
+ async def _publish(self, msg):
+ """Handler receiving a publish item event."""
+ self.log.debug('Published item %s to %s:' % (
+ msg['pubsub_event']['items']['item']['id'],
+ msg['pubsub_event']['items']['node']))
+ data = msg['pubsub_event']['items']['item']['payload']
+ if data is not None:
+ self.log.debug(tostring(data))
+ else:
+ self.log.debug('No item content')
+ self.blocklist = await self.bot['xep_0060'].get_items(self.pubsub_server, self.node)
+
+ async def got_online(self, pres):
+ """Handler method for new MUC participants"""
+ bjid = pres['muc']['jid'].bare
+ bjid_hash = sha256(bjid.encode('utf-8')).hexdigest()
+ if bjid_hash in [_['id'] for _ in self.blocklist['pubsub']['items']]:
+ self.log.debug(f'About to ban {bjid}')
+ await self.ban(bjid, reason='rtbl <timestamp>')
+
+# VIM MODLINE
+# vim: ai ts=4 sw=4 sts=4 expandtab