diff --git a/community/bot.py b/community/bot.py
index ab0982b..e2490f7 100644
--- a/community/bot.py
+++ b/community/bot.py
@@ -36,6 +36,7 @@ from mautrix.types import (
JoinRulesStateEventContent,
JoinRule,
RoomCreatePreset,
+ RelationType,
)
from mautrix.errors import MNotFound
from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
@@ -93,12 +94,15 @@ class Config(BaseProxyConfig):
helper.copy("verification_message")
helper.copy("invite_power_level")
helper.copy("room_version")
+ helper.copy("report_emojis")
+ helper.copy("auto_redact_majority")
class CommunityBot(Plugin):
_redaction_tasks: asyncio.Task = None
_verification_states: Dict[str, Dict] = {}
+ _report_counts: Dict[str, set] = {}
async def start(self) -> None:
await super().start()
@@ -1012,18 +1016,21 @@ class CommunityBot(Plugin):
pass
if self.config["notification_room"]:
- roomnamestate = await self.client.get_state_event(
- evt.room_id, "m.room.name"
- )
-
- roomname = roomnamestate.get("name") if roomnamestate else str(evt.room_id)
+ try:
+ roomnamestate = await self.client.get_state_event(
+ evt.room_id, "m.room.name"
+ )
+
+ roomname = getattr(roomnamestate, "name", str(evt.room_id))
+ except Exception:
+ roomname = str(evt.room_id)
notification_message = self.config[
"join_notification_message"
].format(
user=evt.sender,
room=roomname,
- room_id=evt.room_id # <--- Das ist neu!
+ room_id=evt.room_id
)
await self.client.send_notice(
self.config["notification_room"], html=notification_message
@@ -1295,17 +1302,87 @@ class CommunityBot(Plugin):
await self.upsert_user_timestamp(evt.sender, evt.timestamp)
@event.on(EventType.REACTION)
- async def update_reaction_timestamp(self, evt: MessageEvent) -> None:
- if not self.config_manager.is_reaction_tracking_enabled():
- pass
- else:
+ async def handle_reactions(self, evt: MessageEvent) -> None:
+ if evt.sender == self.client.mxid:
+ return
+
+ if self.config_manager.is_reaction_tracking_enabled():
+ rooms_to_manage = await self.get_space_roomlist()
+ if evt.room_id in rooms_to_manage:
+ await self.upsert_user_timestamp(evt.sender, evt.timestamp)
+
+ if not self.config.get("notification_room", ""):
+ return
+
+ relates_to = evt.content.relates_to
+ if not relates_to or relates_to.rel_type != RelationType.ANNOTATION:
+ return
+
+ emoji = relates_to.key
+ report_emojis = self.config.get("report_emojis", ["🚩", "⚠️"])
+
+ if emoji in report_emojis:
rooms_to_manage = await self.get_space_roomlist()
- # only attempt to track rooms in the space, ignore any other rooms
- # the bot may happen to be in line banlist policy rooms etc.
if evt.room_id not in rooms_to_manage:
return
- else:
- await self.upsert_user_timestamp(evt.sender, evt.timestamp)
+
+ target_event_id = relates_to.event_id
+
+ if target_event_id not in self._report_counts:
+ self._report_counts[target_event_id] = set()
+
+ if evt.sender in self._report_counts[target_event_id]:
+ return
+
+ self._report_counts[target_event_id].add(evt.sender)
+ current_reports = len(self._report_counts[target_event_id])
+
+ try:
+ roomnamestate = await self.client.get_state_event(evt.room_id, "m.room.name")
+ roomname = roomnamestate.get("name") if roomnamestate else str(evt.room_id)
+ except:
+ roomname = str(evt.room_id)
+
+ message_link = f"https://matrix.to/#/{evt.room_id}/{target_event_id}"
+
+ # --- AUTO-REDACT LOGIK ---
+ if self.config.get("auto_redact_majority", False):
+ try:
+ members = await self.client.get_joined_members(evt.room_id)
+ human_count = len([m for m in members.keys() if m != self.client.mxid])
+ threshold = human_count / 2
+
+ if current_reports > threshold:
+ await self.client.redact(
+ evt.room_id,
+ target_event_id,
+ reason=f"Auto-redacted: Reached majority vote ({current_reports}/{human_count} users)"
+ )
+
+ notification = (
+ f"Message Auto-Redacted 🗑️
"
+ f"Room: {roomname}
"
+ f"Reason: Community majority vote reached ({current_reports} out of {human_count} members).
"
+ f"Context: Original Event Link"
+ )
+ await self.client.send_notice(self.config["notification_room"], html=notification)
+
+ del self._report_counts[target_event_id]
+ return
+ except Exception as e:
+ self.log.error(f"Failed to auto-redact reported message: {e}")
+
+ if current_reports == 1:
+ notification = (
+ f"Message Reported 🚨
"
+ f"First Reporter: {evt.sender}
"
+ f"Room: {roomname}
"
+ f"Action: Click here to inspect and moderate"
+ )
+ try:
+ await self.client.send_notice(self.config["notification_room"], html=notification)
+ except Exception as e:
+ self.log.error(f"Failed to send report notification: {e}")
@command.new("community", help="manage rooms and members of a space")
async def community(self) -> None:
@@ -1350,8 +1427,10 @@ class CommunityBot(Plugin):
msg = await evt.respond("starting the ban...")
results_map = await self.ban_this_user(user, all_rooms=True)
- results = "the following users were kicked and banned:
{ban_list}
{error_list}
{ban_list}
{error_list}
{unban_list}
{error_list}
{unban_list}
{error_list}
Users inactive for between {self.config['warn_threshold_days']} and \
- {self.config['kick_threshold_days']} days:
\
- {'
'.join(report['warn_inactive'])}
Users inactive for at least {self.config['kick_threshold_days']} days:
\
- {'
'.join(report['kick_inactive'])}
Ignored users:
\
- {'
'.join(report['ignored'])}
Users inactive for between {self.config['warn_threshold_days']} and "
+ f"{self.config['kick_threshold_days']} days:
"
+ f"{'
'.join(report['warn_inactive'])}
Users inactive for at least {self.config['kick_threshold_days']} days:
"
+ f"{'
'.join(report['kick_inactive'])}
Ignored users:
"
+ f"{'
'.join(report['ignored'])}
Users inactive for between {self.config['warn_threshold_days']} and \
- {self.config['kick_threshold_days']} days:
\
- {'
'.join(report['warn_inactive'])}
Users inactive for at least {self.config['kick_threshold_days']} days:
\
- {'
'.join(report['kick_inactive'])}
Ignored users:
\
- {'
'.join(report['ignored'])}
Users inactive for between {self.config['warn_threshold_days']} and "
+ f"{self.config['kick_threshold_days']} days:
"
+ f"{'
'.join(report['warn_inactive'])}
Users inactive for at least {self.config['kick_threshold_days']} days:
"
+ f"{'
'.join(report['kick_inactive'])}
Ignored users:
"
+ f"{'
'.join(report['ignored'])}
Users inactive for between {self.config['warn_threshold_days']} and \
- {self.config['kick_threshold_days']} days:
\
- {'
'.join(report['warn_inactive'])}
Users inactive for between {self.config['warn_threshold_days']} and "
+ f"{self.config['kick_threshold_days']} days:
"
+ f"{'
'.join(report['warn_inactive'])}
Users inactive for at least {self.config['kick_threshold_days']} days:
\
- {'
'.join(report['kick_inactive'])}
Users inactive for at least {self.config['kick_threshold_days']} days:
"
+ f"{'
'.join(report['kick_inactive'])}
Ignored users:
\
- {'
'.join(report['ignored'])}
Ignored users:
"
+ f"{'
'.join(report['ignored'])}
{purge_list}
{error_list}
{purge_list}
{error_list}
{kick_list}
{error_list}
{kick_list}
{error_list}