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