From 5e494cf979ef71a15b3669713c0630b89d9d4234 Mon Sep 17 00:00:00 2001 From: jenz Date: Thu, 26 Feb 2026 11:18:16 +0100 Subject: [PATCH] initial commit of direct message monitoring --- stoat_view_messages/python/README.md | 4 + stoat_view_messages/python/last_msg_id.txt | 0 stoat_view_messages/python/main.py | 135 ++++++++++++++++++ stoat_view_messages/python/settings.py | 4 + .../systemctl/stoat_view_messages.service | 10 ++ .../systemctl/stoat_view_messages.timer | 8 ++ 6 files changed, 161 insertions(+) create mode 100644 stoat_view_messages/python/README.md create mode 100644 stoat_view_messages/python/last_msg_id.txt create mode 100644 stoat_view_messages/python/main.py create mode 100644 stoat_view_messages/python/settings.py create mode 100644 stoat_view_messages/systemctl/stoat_view_messages.service create mode 100644 stoat_view_messages/systemctl/stoat_view_messages.timer diff --git a/stoat_view_messages/python/README.md b/stoat_view_messages/python/README.md new file mode 100644 index 0000000..3bcaf8d --- /dev/null +++ b/stoat_view_messages/python/README.md @@ -0,0 +1,4 @@ +pymongo +requests + +simple nsa simulator where we monitor URLS and files send in direct messages to ensure that we can have DM's enabled. diff --git a/stoat_view_messages/python/last_msg_id.txt b/stoat_view_messages/python/last_msg_id.txt new file mode 100644 index 0000000..e69de29 diff --git a/stoat_view_messages/python/main.py b/stoat_view_messages/python/main.py new file mode 100644 index 0000000..3cc7dc7 --- /dev/null +++ b/stoat_view_messages/python/main.py @@ -0,0 +1,135 @@ +#!/home/nonroot/stoat_view_messages/venv/bin/python3 + +import pymongo +import requests +import re +import time +import os +import shutil +from settings import stoat_token, stoat_url, MONGO_URI, admin_channel_id + +def update_last_msg_id(msg_id): + with open("last_msg_id.txt", 'w') as f: + f.write(msg_id) + +def get_last_msg_id_read(): + with open("last_msg_id.txt", 'r') as f: + return f.readline() + +def send_post_message_to_stoat(report): + headers = { + "x-bot-token": f"{stoat_token}", + "Content-Type": "application/json" + } + data = { + "content": report + } + response = requests.post(stoat_url, headers=headers, json=data) + +def get_channel_recipients(db, channel_id): + channel = db.channels.find_one({ "_id": channel_id }) + if channel: + return channel.get("recipients", []) + return [] + +def get_dm_channel_ids(db): + return [c["_id"] for c in db.channels.find({ "channel_type": "DirectMessage" })] + +def process_messages(db): + last_msg_id = get_last_msg_id_read() + dm_channels = get_dm_channel_ids(db) + + query = { + "channel": { "$in": dm_channels }, + "$or": [ + { "content": { "$regex": r"(https?://|www\.)\S+", "$options": "i" } }, + { "attachments": { "$exists": True, "$ne": [] } } + ] + } + + if last_msg_id: + query["_id"] = { "$gt": last_msg_id } + messages = list(db.messages.find(query).sort("_id", 1)) + + for msg in messages: + recipients = get_channel_recipients(db, msg["channel"]) + content = msg.get("content", "") + attachments = msg.get("attachments", []) + report = f"**DM Flag** | From: <@{msg['author']}> → To: {' '.join([f'<@{r}>' for r in recipients if r != msg['author']])}\n" + + if content: + report += f"Content: {content}\n" + + extra = "" + if attachments: + #EDIT ME + attachment_links = "\n".join([f"https://yourdomain.com/autumn/attachments/{att['_id']}/{att['filename']}" for att in attachments]) + report += attachment_links + extra = "and files" + report += f""" \nto delete the message {extra} from the system type: "Delete {msg['_id']}". After being processed will your delete command be deleted as well. Remember to manually ban the user if they post problematic content and to also delete the bot report if the content is problematic.""" + send_post_message_to_stoat(report) + + last_msg_id = msg["_id"] + + return last_msg_id + +def check_if_message_to_delete(db): + delete_requests = db.messages.find({ + "channel": admin_channel_id, + "content": { "$regex": "^Delete ", "$options": "" } + }) + + for cmd_msg in delete_requests: + target_msg_id = cmd_msg["content"].split(" ")[1].strip() + print(f"Processing delete command for message: {target_msg_id}") + + target_msg = db.messages.find_one({ "_id": target_msg_id }) + if not target_msg: + print(f"Target message {target_msg_id} not found") + # delete the Delete command message from admin channel + db.messages.delete_one({ "_id": cmd_msg["_id"] }) + print(f"Cleaned up command message {cmd_msg['_id']}") + continue + + attachments = target_msg.get("attachments", []) + hashes = [att["hash"] for att in attachments] + + if hashes: + # find all attachment IDs with these hashes + matching_attachments = db.attachments.find({ "hash": { "$in": hashes } }) + affected_msg_ids = [att["used_for"]["id"] for att in matching_attachments if att.get("used_for", {}).get("type") == "Message"] + + # delete all messages that reference these files + result = db.messages.delete_many({ "_id": { "$in": affected_msg_ids } }) + print(f"Deleted {result.deleted_count} message(s) referencing these attachments") + + # delete attachment records + db.attachments.delete_many({ "hash": { "$in": hashes } }) + + # delete physical files + for hash_val in hashes: + #EDIT ME + hash_dir = f"/home/user/server/data/minio/revolt-uploads/{hash_val}" + if os.path.exists(hash_dir): + shutil.rmtree(hash_dir) + print(f"Deleted from disk: {hash_val}") + else: + # no attachments, just delete the single message + db.messages.delete_one({ "_id": target_msg_id }) + print(f"Deleted message {target_msg_id}") + + # delete the Delete command message from admin channel + db.messages.delete_one({ "_id": cmd_msg["_id"] }) + print(f"Cleaned up command message {cmd_msg['_id']}") + +def main(): + client = pymongo.MongoClient(MONGO_URI) + db = client["revolt"] + while True: + last_msg_id = process_messages(db) + update_last_msg_id(last_msg_id) + check_if_message_to_delete(db) + time.sleep(60) + +if __name__ == '__main__': + main() diff --git a/stoat_view_messages/python/settings.py b/stoat_view_messages/python/settings.py new file mode 100644 index 0000000..63f14c9 --- /dev/null +++ b/stoat_view_messages/python/settings.py @@ -0,0 +1,4 @@ +stoat_token = "" +stoat_url = "" +MONGO_URI = "" +admin_channel_id = "" diff --git a/stoat_view_messages/systemctl/stoat_view_messages.service b/stoat_view_messages/systemctl/stoat_view_messages.service new file mode 100644 index 0000000..ba6d95d --- /dev/null +++ b/stoat_view_messages/systemctl/stoat_view_messages.service @@ -0,0 +1,10 @@ +[Unit] +Description=monitors Direct Messages for urls and attachments to make sure people dont post problematic shit + +[Service] +Type=simple +User=root +Environment=PYTHONUNBUFFERED=1 +Environment=PATH=/home/nonroot/stoat_view_messages/venv/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin +WorkingDirectory=/home/nonroot/stoat_view_messages +ExecStart=/home/nonroot/stoat_view_messages/main.py diff --git a/stoat_view_messages/systemctl/stoat_view_messages.timer b/stoat_view_messages/systemctl/stoat_view_messages.timer new file mode 100644 index 0000000..b0a8cb4 --- /dev/null +++ b/stoat_view_messages/systemctl/stoat_view_messages.timer @@ -0,0 +1,8 @@ +[Unit] +Description=restart message viewing service once every day at midnight + +[Timer] +OnCalendar=*-*-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 00:00:00 + +[Install] +WantedBy=multi-user.target