From 914a0f4fd4d44d049625d2417534924cc6a2aa50 Mon Sep 17 00:00:00 2001 From: William Kray Date: Fri, 30 Aug 2024 08:43:15 -0700 Subject: [PATCH] basic wordlist message redaction add file and image redaction update readme, version bump --- README.md | 12 ++++++++++++ base-config.yaml | 16 ++++++++++++++++ community/bot.py | 42 +++++++++++++++++++++++++++++++++++++++++- maubot.yaml | 2 +- 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73b92dd..407e62b 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,18 @@ easy (or possible) to find. this subcommand (`!community roomid`) can be used to points to. with no argument passed, it will return the current room's ID, or you can pass it an alias (e.g. `!community roomid #whatisthisroom:myserver.tld`). +## message redaction + +the bot can be configured to redact messages automatically to protect your users. set `censor` to either `true`, +`false`, or a list of room IDs to enable censorship in. + +set `censor_files` to have the bot immediately redact file uploads in the censored rooms. define trigger words in +`censor_wordlist` to flag messages for automatic redaction. + +please keep in mind that wordlist-based censorship is problematic and may redact false positives. writing a matching +algorithm that is perfect is impossible. consider configuring your community such that censorship need only be applied +in a limited subset of rooms. + # installation install this like any other maubot plugin: zip the contents of this repo into a file and upload via the web interface, diff --git a/base-config.yaml b/base-config.yaml index 41de195..5610597 100644 --- a/base-config.yaml +++ b/base-config.yaml @@ -62,3 +62,19 @@ notification_room: # message to send to the notification room when someone joins one of the above rooms: join_notification_message: | User {user} has joined {room}. + +# whether to censor files/messages +# can be boolean (true/false) for all-or-nothing behavior, +# or pass a list of room IDs to only censor certain rooms. this may be helpful +# if certain rooms are publicly facing while others are more trustworthy. +# this bot, bot admins and bot moderators are immune to censorship. +censor: false + +# whether to redact file and image uploads. this will apply to all rooms defined +# in the censor variable (either boolean or a list of room IDs). +censor_files: true + +# what words should trigger message redaction if censorship is enabled? +censor_wordlist: + - 'effword' + - 'essword' diff --git a/community/bot.py b/community/bot.py index b9c67f0..c44fa4e 100644 --- a/community/bot.py +++ b/community/bot.py @@ -8,7 +8,7 @@ import re from mautrix.client import Client, InternalEventType, MembershipEventDispatcher, SyncStream from mautrix.types import (Event, StateEvent, EventID, UserID, FileInfo, EventType, MediaMessageEventContent, ReactionEvent, RedactionEvent, RoomID, - RoomAlias, PowerLevelStateEventContent) + RoomAlias, PowerLevelStateEventContent, MessageType) from mautrix.errors import MNotFound from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from maubot import Plugin, MessageEvent @@ -34,6 +34,9 @@ class Config(BaseProxyConfig): helper.copy("join_notification_message") helper.copy_dict("greeting_rooms") helper.copy_dict("greetings") + helper.copy("censor") + helper.copy("censor_wordlist") + helper.copy("censor_files") class CommunityBot(Plugin): @@ -111,6 +114,32 @@ class CommunityBot(Plugin): report["ignored"] = [ row["mxid"] for row in ignored_results ] or ["none"] return report + + def flag_message(self, msg): + if msg.content.msgtype in [MessageType.FILE, MessageType.IMAGE]: + return self.config['censor_files'] + + for w in self.config['censor_wordlist']: + try: + if bool(re.search(w, msg.content.body, re.IGNORECASE)): + self.log.debug(f"DEBUG message flagged for censorship") + return True + else: + pass + except Exception as e: + self.log.error(f"Could not parse message for flagging: {e}") + + def censor_room(self, msg): + if isinstance(self.config['censor'], bool): + self.log.debug(f"DEBUG message will be redacted because censoring is enabled") + return self.config['censor'] + elif isinstance(self.config['censor'], list): + if msg.room_id in self.config['censor']: + self.log.debug(f"DEBUG message will be redacted because censoring is enabled for THIS room") + return True + else: + return False + @event.on(InternalEventType.JOIN) async def newjoin(self, evt:StateEvent) -> None: @@ -141,6 +170,17 @@ class CommunityBot(Plugin): @event.on(EventType.ROOM_MESSAGE) async def update_message_timestamp(self, evt: MessageEvent) -> None: + if self.flag_message(evt): + # do we need to redact? + if evt.sender not in self.config['admins'] and \ + evt.sender not in self.config['moderators'] and \ + evt.sender != self.client.mxid and \ + self.censor_room(evt): + try: + await self.client.redact(evt.room_id, evt.event_id, reason="message flagged") + except Exception as e: + self.log.error(f"Flagged message could not be redacted: {e}") + if not self.config["track_messages"]: pass else: diff --git a/maubot.yaml b/maubot.yaml index 8108cae..216035d 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: org.jobmachine.communitybot -version: 0.1.6 +version: 0.1.7 license: MIT modules: - community