[anti_spam] Rework plugin

This commit is contained in:
Daniel Brötzmann
2020-05-01 10:18:49 +02:00
parent a42180fb2d
commit 1069535b4b
6 changed files with 291 additions and 543 deletions

View File

@@ -1,24 +1,18 @@
## [Unreleased] 1.5.1 / 2020-05-03
### Added - Rework plugin
- Sending confirmation message that anti spam check was passed. It can be disabled in plugin gui config. - 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 1.4.3 / 2016-12-04
### Added - Added filtering 'normal' type messages
- Filtering 'normal' type messages
### Changed
- User from private conference conversation permanently stored in file - User from private conference conversation permanently stored in file
- Switched to GTK3 - Switched to GTK3
- Messages sent before the correct answer were marked as received
- Fixed chat between the two antispam plugins
### Fixed 0.4.2 / 2016-11-28
- Messages that was sent before correct answer was marked as received - Added anti spam question functionality
- Chat between the two antispam plugins - Added README with some explanation of functionality
- Added website in manifest.ini
## [0.4.2] - 2016-11-28
### Added
- Anti spam question functionality
- This CHANGELOG
- README with some explanation of functionality
### Changed
- homepage in manifest.ini

View File

@@ -1,22 +0,0 @@
# Anti_spam Plugin for Gajim
This Plugin allows you to dissociate itself from the spam.
## Installation
Use special plugin, that manages automatic download and installation of others plugins, it is called Plugin Installer.
## Options
### Block pubsub
Block incoming messages from pubsub
### Message size limit
Block incoming messages that have size more than configured. Default value -1 mean that any sized messages are coming.
### Anti spam question
Block incoming messages from users not in your roster. In response, the Plugin sends a question that you configured. After correct answer(also configurable) you will receive all new messages from user.
**Attention!** All messages before correct answer will be lost.
Also you can enable this function, in Plugin config, for conference private messages. In some servers, the question in conference private does not reach your interlocutor. This can lead to the fact that you will not receive any messages from him, and he will not know it.

View File

@@ -1,278 +1,49 @@
# -*- coding: utf-8 -*- # This file is part of Gajim.
#
## 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
## Gajim is free software; you can redistribute it and/or modify # by the Free Software Foundation; version 3 only.
## 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
## Gajim is distributed in the hope that it will be useful, # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## but WITHOUT ANY WARRANTY; without even the implied warranty of # GNU General Public License for more details.
## 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 <http://www.gnu.org/licenses/>.
## You should have received a copy of the GNU General Public License #
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
''' '''
Block some incoming messages
:author: Yann Leboulanger <asterix@lagaule.org> :author: Yann Leboulanger <asterix@lagaule.org>
:since: 16 August 2012 :since: 16 August 2012
:copyright: Copyright (2012) Yann Leboulanger <asterix@lagaule.org> :copyright: Copyright (2012) Yann Leboulanger <asterix@lagaule.org>
:license: GPLv3 :license: GPLv3
''' '''
from gi.repository import Gtk from functools import partial
import nbxmpp
from gajim.common import app
from gajim.common import ged
from gajim.plugins import GajimPlugin from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import log, log_calls
from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.plugins.plugins_i18n import _ from gajim.plugins.plugins_i18n import _
from anti_spam.modules import anti_spam
from anti_spam.config_dialog import AntiSpamConfigDialog
class AntiSpamPlugin(GajimPlugin): class AntiSpamPlugin(GajimPlugin):
@log_calls('AntiSpamPlugin')
def init(self): def init(self):
self.description = _('Allows to block some kind of incoming messages') self.description = _('Allows you to block various kinds of incoming '
self.config_dialog = AntiSpamPluginConfigDialog(self) 'messages (Spam, XHTML formatting, etc.)')
self.config_dialog = partial(AntiSpamConfigDialog, self)
self.gui_extension_points = {
}
self.events_handlers = {
'atom-entry-received': (ged.POSTCORE,
self._nec_atom_entry_received),
'message-received': (ged.PRECORE,
self._nec_message_received_received),
'decrypted-message-received': (ged.PRECORE,
self._nec_decrypted_message_received_received),
'subscribe-presence-received': (ged.POSTCORE,
self._nec_subscribe_presence_received),
'message-outgoing': (ged.OUT_PRECORE,
self._nec_message_outgoing)
}
self.config_default_values = { self.config_default_values = {
'block_pubsub_messages': (False, 'If True, Gajim will block incoming messages from pubsub.'),
'disable_xhtml_muc': (False, ''), 'disable_xhtml_muc': (False, ''),
'disable_xhtml_pm': (False, ''), 'disable_xhtml_pm': (False, ''),
'block_subscription_requests': (False, ''), 'block_subscription_requests': (False, ''),
'msgtxt_limit': (-1, ''), 'msgtxt_limit': (0, ''),
'msgtxt_question': ('Please answer: 12 x 12 =', ''), 'msgtxt_question': ('12 x 12 = ?', ''),
'msgtxt_answer': ('', ''), 'msgtxt_answer': ('', ''),
'antispam_for_conference': (False, ''), 'antispam_for_conference': (False, ''),
'conference_white_list': ([], ''), # conference private chat jid's 'block_domains': ('', ''),
'block_domains': ('', ''), # comma separated list of domain names to block 'whitelist': ([], ''),
} }
self.gui_extension_points = {}
# List of outgoing jid's self.modules = [anti_spam]
# Needs to avoid chat of two anti spam plugins
# Contain all jid's where are you initiate a chat
self.outgoing_jids = []
self.block_domains = [h.strip() for h in self.config['block_domains'].split(",") if len(h.strip())]
@log_calls('AntiSpamPlugin')
def _nec_atom_entry_received(self, obj):
if self.config['block_pubsub_messages']:
log.info('discarding pubdubd message')
return True
@log_calls('AntiSpamPlugin')
def _nec_message_received_received(self, obj):
if self.config['disable_xhtml_muc'] and obj.mtype == 'groupchat':
self.remove_xhtml(obj)
if self.config['disable_xhtml_pm'] and obj.gc_control and \
obj.resource and obj.mtype == 'chat':
self.remove_xhtml(obj)
if obj.jid.split("@", 1)[1] in self.block_domains:
log.info('discarding message from %s, domain is blocked', obj.jid)
return True
return False
@log_calls('AntiSpamPlugin')
def _nec_decrypted_message_received_received(self, obj):
if not obj.msgtxt:
return False
if obj.jid.split("@", 1)[1] in self.block_domains:
log.info('discarding message from %s, domain is blocked', obj.jid)
return True
if self._nec_decrypted_message_received_question(obj):
return True
limit = self.config['msgtxt_limit']
if limit > -1 and len(obj.msgtxt) > limit:
return True
return False
@log_calls('AntiSpamPlugin')
def _nec_subscribe_presence_received(self, obj):
if self.config['block_subscription_requests'] and \
not app.contacts.get_contacts(obj.conn.name, obj.jid):
log.info('discarding subscription request from %s', obj.jid)
return True
if obj.jid.split("@", 1)[1] in self.block_domains:
log.info('discarding subscription request from %s, domain is blocked', obj.jid)
return True
@log_calls('AntiSpamPlugin')
def _nec_decrypted_message_received_question(self, obj):
if obj.mtype != 'chat' and obj.mtype != 'normal':
return False
tjid = obj.jid if obj.mtype == 'normal' else obj.fjid
if tjid in self.outgoing_jids:
return False
answer = self.config['msgtxt_answer']
if len(answer) == 0:
return False
block_conference = self.config['antispam_for_conference']
is_conference = app.contacts.is_gc_contact(obj.conn.name, obj.fjid)
if not block_conference and is_conference:
return False
jid = obj.jid if not is_conference else obj.fjid
# If we receive conference privat message or direct message from unknown user than
# anti spam question will send in background mode, without any notification for us
# There are two methods to see who wrote you and not passed filter:
# 1. Using XML console
# 2. Running Gajim with log info messages and see logs (probably gajim.log file)
if is_conference or not app.contacts.get_contacts(obj.conn.name, jid):
if not self.contain_answer(obj.msgtxt, answer):
if is_conference and jid in self.config['conference_white_list']:
return False
self.send_question(obj, jid)
return True
else:
if is_conference and jid not in self.config['conference_white_list']:
self.config['conference_white_list'].append(jid)
# Need to save because 'append' method does not implement __setitem__ method
self.config.save()
return False
@log_calls('AntiSpamPlugin')
def _nec_message_outgoing(self, obj):
if obj.type_ != 'chat' and obj.type_ != 'normal':
return
if isinstance(obj.jid, list):
for i in obj.jid:
if i not in self.outgoing_jids:
self.outgoing_jids.append(i)
else:
if obj.jid not in self.outgoing_jids:
self.outgoing_jids.append(obj.jid)
def send_question(self, obj, jid):
if obj.mtype != 'chat' and obj.mtype != 'normal':
log.info('Anti_spam wrong message type: %s', obj.mtype)
return
question = self.config['msgtxt_question']
log.info('Anti_spam enabled for %s, question: %s', jid, question)
message = _('Antispam enabled. Please answer the question. The message must only ' + \
'contain the answer. (Messages sent before the correct answer, will be lost): ') \
+ question
if obj.mtype == 'chat':
stanza = nbxmpp.Message(to=jid, body=message, typ=obj.mtype)
else: # for 'normal' type
stanza = nbxmpp.Message(to=jid, body=message, subject='Antispam enabled', typ=obj.mtype)
app.connections[obj.conn.name].connection.send(stanza)
def contain_answer(self, msg, answer):
return answer in msg.split('\n')
def remove_xhtml(self, obj):
html_node = obj.stanza.getTag('html')
if html_node:
obj.stanza.delChild(html_node)
class AntiSpamPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = Gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['anti_spam_config_vbox'])
self.config_vbox = self.xml.get_object('anti_spam_config_vbox')
self.get_child().pack_start(self.config_vbox, True, True, 0)
self.block_pubsub_messages_checkbutton = self.xml.get_object(
'block_pubsub_messages_checkbutton')
self.xml.connect_signals(self)
def on_run(self):
self.block_pubsub_messages_checkbutton.set_active(self.plugin.config[
'block_pubsub_messages'])
widget = self.xml.get_object('disable_xhtml_muc_checkbutton')
widget.set_active(self.plugin.config['disable_xhtml_muc'])
widget = self.xml.get_object('disable_xhtml_pm_checkbutton')
widget.set_active(self.plugin.config['disable_xhtml_pm'])
widget = self.xml.get_object('block_subscription_requests_checkbutton')
widget.set_active(self.plugin.config['block_subscription_requests'])
widget = self.xml.get_object('message_size_limit_entry')
widget.set_text(str(self.plugin.config['msgtxt_limit']))
widget = self.xml.get_object('antispam_question')
widget.set_text(str(self.plugin.config['msgtxt_question']))
widget = self.xml.get_object('antispam_answer')
widget.set_text(str(self.plugin.config['msgtxt_answer']))
widget = self.xml.get_object('antispam_for_conference')
widget.set_active(self.plugin.config['antispam_for_conference'])
widget = self.xml.get_object('block_domains_entry')
widget.set_text(str(self.plugin.config['block_domains']))
def on_block_pubsub_messages_checkbutton_toggled(self, button):
self.plugin.config['block_pubsub_messages'] = button.get_active()
def on_disable_xhtml_muc_checkbutton_toggled(self, button):
self.plugin.config['disable_xhtml_muc'] = button.get_active()
def on_disable_xhtml_pm_checkbutton_toggled(self, button):
self.plugin.config['disable_xhtml_pm'] = button.get_active()
def on_block_subscription_requests_checkbutton_toggled(self, button):
self.plugin.config['block_subscription_requests'] = button.get_active()
def on_message_size_limit_entry_changed(self, entry):
try:
self.plugin.config['msgtxt_limit'] = int(entry.get_text())
except Exception:
pass
def on_message_question_entry_changed(self, entry):
try:
self.plugin.config['msgtxt_question'] = entry.get_text()
except Exception:
pass
def on_message_answer_entry_changed(self, entry):
try:
self.plugin.config['msgtxt_answer'] = entry.get_text()
except Exception:
pass
def on_antispam_for_conference_checkbutton_toggled(self, button):
self.plugin.config['antispam_for_conference'] = button.get_active()
def on_block_domains_entry_changed(self, entry):
try:
block_domains = self.plugin.config['block_domains'] = entry.get_text()
self.plugin.block_domains = [h.strip() for h in block_domains.split(",") if len(h.strip())]
except Exception as e:
log.debug(str(e))

View File

@@ -0,0 +1,99 @@
# 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 <http://www.gnu.org/licenses/>.
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 _
class AntiSpamConfigDialog(SettingsDialog):
def __init__(self, plugin, parent):
self.plugin = plugin
msgtxt_limit = self.plugin.config['msgtxt_limit']
max_length = '' if msgtxt_limit == 0 else msgtxt_limit
settings = [
Setting(SettingKind.ENTRY,
_('Limit Message Length'),
SettingType.VALUE,
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, None)
def _on_setting(self, value, data):
self.plugin.config[data] = value
def _on_length_setting(self, value, data):
try:
self.plugin.config[data] = int(value)
except Exception:
self.plugin.config[data] = 0

View File

@@ -1,245 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkVBox" id="anti_spam_config_vbox">
<property name="visible">True</property>
<property name="border_width">9</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkCheckButton" id="block_pubsub_messages_checkbutton">
<property name="label" translatable="yes">Block pubsub messages</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, Gajim will block incoming messages from pubsub.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_block_pubsub_messages_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_xhtml_muc_checkbutton">
<property name="label" translatable="yes">Disable xhtml in MUCs</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, Gajim will remove XHTML from incoming multi-user chat messages</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_xhtml_muc_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_xhtml_pm_checkbutton">
<property name="label" translatable="yes">Disable xhtml in private messages</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, Gajim will remove XHTML from incoming private messages</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_xhtml_pm_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="block_subscription_requests_checkbutton">
<property name="label" translatable="yes">Block incoming subscription requests</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, Gajim will block incoming subscription requests</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_block_subscription_requests_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Message size limit:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="message_size_limit_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="changed" handler="on_message_size_limit_entry_changed"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_question">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Anti spam question (Leave an empty answer field to disable this function):</property>
<property name="wrap">True</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="position">6</property>
<property name="padding">3</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="questionbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Question:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="antispam_question">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="changed" handler="on_message_question_entry_changed"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Answer:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="antispam_answer">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="changed" handler="on_message_answer_entry_changed"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="block_domains_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="block_domains_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Block Domains:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="block_domains_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="changed" handler="on_block_domains_entry_changed"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkCheckButton" id="antispam_for_conference">
<property name="label" translatable="yes">Enable for conferences</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, anti spam question will send for conference private messages</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_antispam_for_conference_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">8</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -0,0 +1,151 @@
# 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 <http://www.gnu.org/licenses/>.
#
from nbxmpp import NodeProcessed
from nbxmpp.protocol import Message
from nbxmpp.structs import StanzaHandler
from gajim.common import app
from gajim.common import ged
from gajim.common.modules.base import BaseModule
# Module name
name = 'AntiSpam'
zeroconf = False
class AntiSpam(BaseModule):
def __init__(self, con):
BaseModule.__init__(self, con, 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.OUT_PRECORE, self._on_message_sent),
])
for plugin in app.plugin_manager.plugins:
if plugin.short_name == 'anti_spam':
self._config = plugin.config
self._contacted_jids = set()
def _on_message_sent(self, event):
if event.type_ not in ('chat', 'normal'):
return
# 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.
if isinstance(event.jid, list):
for jid in event.jid:
self._contacted_jids.add(jid)
else:
self._contacted_jids.add(event.jid)
def _message_received(self, _con, _stanza, properties):
if properties.is_sent_carbon:
# Another device already sent a message
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 = 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):
answer = 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
msg_from = properties.jid if is_muc_pm else properties.jid.getBare()
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 = self._config['whitelist']
if msg_from in whitelist:
return False
is_contact = app.contacts.get_contacts(self._account, msg_from)
if is_muc_pm or not is_contact:
if answer in properties.body.split('\n'):
if msg_from not in whitelist:
whitelist.append(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, jid):
message = 'Anti Spam Question: %s' % self._config['msgtxt_question']
stanza = Message(to=jid, body=message, typ=properties.type.value)
self._con.connection.send_stanza(stanza)
self._log.info('Anti spam question sent to %s', jid)
def _subscribe_received(self, _con, _stanza, properties):
msg_from = properties.jid
block_sub = self._config['block_subscription_requests']
is_contact = app.contacts.get_contacts(self._account, msg_from)
if block_sub and not is_contact:
self._con.get_module('Presence').unsubscribed(msg_from)
self._log.info('Denied subscription request from %s' % msg_from)
raise NodeProcessed
def get_instance(*args, **kwargs):
return AntiSpam(*args, **kwargs), 'AntiSpam'