anti_spam/0000755000175500017550000000000014356360277012600 5ustar debacledebacleanti_spam/CHANGELOG0000644000175500017550000000122114356360277014006 0ustar debacledebacle1.5.1 / 2020-05-03 - Rework plugin - Adapt to upstream changes in Gajim and python-nbxmpp - Use new configuration dialog - Remove pubsub blocking (Gajim handles these messages differently now) - Remove domain blocking feature (Gajim handles this) 1.4.3 / 2016-12-04 - Added filtering 'normal' type messages - User from private conference conversation permanently stored in file - Switched to GTK3 - Messages sent before the correct answer were marked as received - Fixed chat between the two antispam plugins 0.4.2 / 2016-11-28 - Added anti spam question functionality - Added README with some explanation of functionality - Added website in manifest.ini anti_spam/anti_spam.png0000644000175500017550000000146514356360277015267 0ustar debacledebaclePNG  IHDRabKGD pHYs  tIME  XLIDAT8mMh\U{޹hBLآ  BQDDBVYi Y骛R.B* Y )1N6jmN2i94]>pα1[.KB>,JX*=}FzVe28󌍍-y>avz_ZMQJEvwtt վurƐ4@Ǐc0E4M=999D9cĀFt(&EzV"8xZ+ J-,6Xo83IF+Rƻ=nQ.<|H(h- Q޳]mx7yvO^J#CL{^08yrl:Uߑ=8L`t", "Denis Fomin ", "Ilya Kanyukov " ], "description": "Block some incoming messages.", "homepage": "https://dev.gajim.org/gajim/gajim-plugins/wikis/AntiSpamPlugin", "config_dialog": true, "name": "Anti Spam", "platforms": [ "others", "linux", "darwin", "win32" ], "requirements": [ "gajim>=1.4.0" ], "short_name": "anti_spam", "version": "1.6.8" }anti_spam/config_dialog.py0000644000175500017550000001105714356360277015742 0ustar debacledebacle# This file is part of Gajim. # # Gajim is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Gajim is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . from __future__ import annotations from typing import Any from typing import cast from typing import TYPE_CHECKING from gi.repository import Gtk from gajim.gtk.settings import SettingsDialog from gajim.gtk.const import Setting from gajim.gtk.const import SettingKind from gajim.gtk.const import SettingType from gajim.plugins.plugins_i18n import _ if TYPE_CHECKING: from .anti_spam import AntiSpamPlugin class AntiSpamConfigDialog(SettingsDialog): def __init__(self, plugin: AntiSpamPlugin, parent: Gtk.Window) -> None: self.plugin = plugin msgtxt_limit = cast(int, self.plugin.config['msgtxt_limit']) max_length = '' if msgtxt_limit == 0 else msgtxt_limit settings = [ Setting(SettingKind.ENTRY, _('Limit Message Length'), SettingType.VALUE, str(max_length), callback=self._on_length_setting, data='msgtxt_limit', desc=_('Limits maximum message length (leave empty to ' 'disable)')), Setting(SettingKind.SWITCH, _('Deny Subscription Requests'), SettingType.VALUE, self.plugin.config['block_subscription_requests'], callback=self._on_setting, data='block_subscription_requests'), Setting(SettingKind.SWITCH, _('Disable XHTML for Group Chats'), SettingType.VALUE, self.plugin.config['disable_xhtml_muc'], callback=self._on_setting, data='disable_xhtml_muc', desc=_('Removes XHTML formatting from group chat ' 'messages')), Setting(SettingKind.SWITCH, _('Disable XHTML for PMs'), SettingType.VALUE, self.plugin.config['disable_xhtml_pm'], callback=self._on_setting, data='disable_xhtml_pm', desc=_('Removes XHTML formatting from private messages ' 'in group chats')), Setting(SettingKind.ENTRY, _('Anti Spam Question'), SettingType.VALUE, self.plugin.config['msgtxt_question'], callback=self._on_setting, data='msgtxt_question', desc=_('Question has to be answered in order to ' 'contact you')), Setting(SettingKind.ENTRY, _('Anti Spam Answer'), SettingType.VALUE, self.plugin.config['msgtxt_answer'], callback=self._on_setting, data='msgtxt_answer', desc=_('Correct answer to your Anti Spam Question ' '(leave empty to disable question)')), Setting(SettingKind.SWITCH, _('Anti Spam Question in Group Chats'), SettingType.VALUE, self.plugin.config['antispam_for_conference'], callback=self._on_setting, data='antispam_for_conference', desc=_('Enables anti spam question for private messages ' 'in group chats')), ] SettingsDialog.__init__(self, parent, _('Anti Spam Configuration'), Gtk.DialogFlags.MODAL, settings, '') def _on_setting(self, value: Any, data: Any) -> None: self.plugin.config[data] = value def _on_length_setting(self, value: str, data: str) -> None: try: self.plugin.config[data] = int(value) except ValueError: self.plugin.config[data] = 0 anti_spam/__init__.py0000644000175500017550000000006614356360277014713 0ustar debacledebaclefrom .anti_spam import AntiSpamPlugin # type: ignore anti_spam/anti_spam.py0000644000175500017550000000334414356360277015131 0ustar debacledebacle# This file is part of Gajim. # # Gajim is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published # by the Free Software Foundation; version 3 only. # # Gajim is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . ''' :author: Yann Leboulanger :since: 16 August 2012 :copyright: Copyright (2012) Yann Leboulanger :license: GPLv3 ''' from functools import partial from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ from anti_spam.modules import anti_spam from anti_spam.config_dialog import AntiSpamConfigDialog class AntiSpamPlugin(GajimPlugin): def init(self) -> None: self.description = _('Allows you to block various kinds of incoming ' 'messages (Spam, XHTML formatting, etc.)') self.config_dialog = partial(AntiSpamConfigDialog, self) self.config_default_values = { 'disable_xhtml_muc': (False, ''), 'disable_xhtml_pm': (False, ''), 'block_subscription_requests': (False, ''), 'msgtxt_limit': (0, ''), 'msgtxt_question': ('12 x 12 = ?', ''), 'msgtxt_answer': ('', ''), 'antispam_for_conference': (False, ''), 'block_domains': ('', ''), 'whitelist': ([], ''), } self.gui_extension_points = {} self.modules = [anti_spam] anti_spam/modules/0000755000175500017550000000000014356360277014250 5ustar debacledebacleanti_spam/modules/anti_spam.py0000644000175500017550000001414214356360277016577 0ustar debacledebacle# This file is part of Gajim. # # Gajim is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published # by the Free Software Foundation; version 3 only. # # Gajim is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . from __future__ import annotations from typing import Any from typing import cast from nbxmpp import NodeProcessed from nbxmpp.protocol import JID from nbxmpp.protocol import Message from nbxmpp.protocol import Presence from nbxmpp.structs import MessageProperties from nbxmpp.structs import PresenceProperties from nbxmpp.structs import StanzaHandler from gajim.common import app from gajim.common import ged from gajim.common.client import Client from gajim.common.events import MessageSent from gajim.common.modules.base import BaseModule # Module name name = 'AntiSpam' zeroconf = False class AntiSpam(BaseModule): def __init__(self, client: Client) -> None: BaseModule.__init__(self, client, plugin=True) self.handlers = [ StanzaHandler(name='message', callback=self._message_received, priority=48), StanzaHandler(name='presence', callback=self._subscribe_received, typ='subscribe', priority=48), ] self.register_events([ ('message-sent', ged.GUI2, self._on_message_sent), ]) for plugin in app.plugin_manager.plugins: if plugin.manifest.short_name == 'anti_spam': self._config = plugin.config self._contacted_jids: set[JID] = set() def _on_message_sent(self, event: MessageSent) -> None: # We need self._contacted_jids in order to prevent two # Anti Spam Plugins from chatting with each other. # This set contains JIDs of all outgoing chats. self._contacted_jids.add(event.jid) def _message_received(self, _con: Client, _stanza: Message, properties: MessageProperties ) -> None: if properties.is_sent_carbon: # Another device already sent a message assert properties.jid self._contacted_jids.add(properties.jid) return msg_body = properties.body if not msg_body: return if self._ask_question(properties): raise NodeProcessed msg_from = properties.jid limit = cast(int, self._config['msgtxt_limit']) if limit > 0 and len(msg_body) > limit: self._log.info('Discarded message from %s: message ' 'length exceeded' % msg_from) raise NodeProcessed if self._config['disable_xhtml_muc'] and properties.type.is_groupchat: properties.xhtml = None self._log.info('Stripped message from %s: message ' 'contained XHTML' % msg_from) if self._config['disable_xhtml_pm'] and properties.is_muc_pm: properties.xhtml = None self._log.info('Stripped message from %s: message ' 'contained XHTML' % msg_from) def _ask_question(self, properties: MessageProperties) -> bool: answer = cast(str, self._config['msgtxt_answer']) if len(answer) == 0: return False is_muc_pm = properties.is_muc_pm if is_muc_pm and not self._config['antispam_for_conference']: return False if (properties.type.value not in ('chat', 'normal') or properties.is_mam_message): return False assert properties.jid if is_muc_pm: msg_from = properties.jid else: msg_from = JID.from_string(properties.jid.bare) if msg_from in self._contacted_jids: return False # If we receive a PM or a message from an unknown user, our anti spam # question will silently be sent in the background whitelist = cast(list[str], self._config['whitelist']) if str(msg_from) in whitelist: return False roster_item = self._client.get_module('Roster').get_item(msg_from) if is_muc_pm or roster_item is None: assert properties.body if answer in properties.body.split('\n'): if str(msg_from) not in whitelist: whitelist.append(str(msg_from)) # We need to explicitly save, because 'append' does not # implement the __setitem__ method self._config.save() else: self._send_question(properties, msg_from) return True return False def _send_question(self, properties: MessageProperties, jid: JID) -> None: message = 'Anti Spam Question: %s' % self._config['msgtxt_question'] stanza = Message(to=jid, body=message, typ=properties.type.value) self._client.connection.send_stanza(stanza) self._log.info('Anti spam question sent to %s', jid) def _subscribe_received(self, _con: Client, _stanza: Presence, properties: PresenceProperties ) -> None: msg_from = properties.jid block_sub = self._config['block_subscription_requests'] roster_item = self._client.get_module('Roster').get_item(msg_from) if block_sub and roster_item is None: self._client.get_module('Presence').unsubscribed(msg_from) self._log.info('Denied subscription request from %s' % msg_from) raise NodeProcessed def get_instance(*args: Any, **kwargs: Any) -> tuple[AntiSpam, str]: return AntiSpam(*args, **kwargs), 'AntiSpam'