10 Commits

Author SHA1 Message Date
Dome 0ee5f4cb1b feat(notifications): add leave, kick and ban notifications with actor pills
Extend the notification system to include member leave, kick and ban events,
mirroring the existing join notification functionality.

### Changes
- Added `_handle_leave_notifications` to process LEAVE, KICK and BAN events
- Integrated new notifications into existing membership event handlers
- Introduced `{actor}` placeholder with Matrix URI pill support
- Added `{actor_display}` as plaintext fallback
- Extended template renderer to support actor context
- Ensured consistent rendering across plaintext and HTML messages
- Added template cleanup for cases without an actor (e.g. voluntary leave)

### Templates
New placeholders:
- `{actor}` → clickable Matrix user pill
- `{actor_display}` → plain display name

### Configuration
New config keys:
- `leave_notification_message`
- `kick_notification_message`
- `ban_notification_message`

### Notes
- Fully async and non-blocking implementation
- Backwards compatible with existing templates
- No impact on join notification logic
- Scoped to rooms within configured space
2026-05-08 20:30:01 +02:00
Dome 7471da08ce Update README.md 2026-04-21 11:58:35 +02:00
Dome b8f456d108 Update README.md 2026-04-12 22:18:47 +02:00
Dome 9a5b6fef60 Update README.md 2026-04-12 22:18:10 +02:00
Dome 904b9cb9fd Merge branch 'main' of https://github.com/Domoel/Advanced-Community-Bot 2026-04-12 22:17:47 +02:00
Dome 951c509fc9 adjustments to readme.md 2026-04-12 22:16:48 +02:00
Dome 69fec3514f Update README.md 2026-04-12 22:15:53 +02:00
Dome ace8c2b27e Update README.md 2026-04-12 22:14:52 +02:00
Dome 3b2471bb4b Update README.md 2026-04-12 21:43:32 +02:00
Dome 78e0de08e5 Update README.md 2026-04-12 21:42:14 +02:00
4 changed files with 115 additions and 6 deletions
+25 -3
View File
@@ -4,7 +4,27 @@
</a> </a>
</p> </p>
# Advanced Community Bot <h1 align="center">
Advanced Community Bot
</span>
<h4 align="center">
<span style="display:inline-flex; align-items:center; gap:12px;">
Advanced Community Bot is a Maubot Bot Plugin to manage Matrix servers.
</span>
<p>
<h6 align="center">
<a href="https://ztfr.eu">🏰 Website</a>
·
<a href="https://ztfr.eu/matrix">📰 Zeitfresser Matrix Community</a>
·
<a href="https://social.ztfr.eu/@dome">🐘 Mastodon</a>
·
<a href="https://look.ztfr.eu/#/#support:ztfr.eu">💬 Supportchat</a>
</h6>
<br>
## ✨ Introduction
Advanced Community Bot is a powerful Maubot plugin designed to help you manage Matrix communities that are structured around Spaces. It combines moderation tools, automation, and community-driven workflows into a single, opinionated solution that focuses on simplicity, reliability, and clean integration with modern Matrix clients. Advanced Community Bot is a powerful Maubot plugin designed to help you manage Matrix communities that are structured around Spaces. It combines moderation tools, automation, and community-driven workflows into a single, opinionated solution that focuses on simplicity, reliability, and clean integration with modern Matrix clients.
@@ -12,8 +32,6 @@ The plugin is particularly well suited for communities that want strong control
It was originally created as Community Bot by <a href="https://github.com/williamkray/maubot-communitybot">William Kray</a>. This fork uses his bases and adds additional features and refinments to the bot. It was originally created as Community Bot by <a href="https://github.com/williamkray/maubot-communitybot">William Kray</a>. This fork uses his bases and adds additional features and refinments to the bot.
---
## 🛠 What this bot is for ## 🛠 What this bot is for
Advanced Community Bot is not meant to replace large-scale moderation frameworks like Draupnir or Mjolnir. Instead, it focuses on providing a cohesive set of tools for managing structured communities with minimal overhead. Advanced Community Bot is not meant to replace large-scale moderation frameworks like Draupnir or Mjolnir. Instead, it focuses on providing a cohesive set of tools for managing structured communities with minimal overhead.
@@ -154,6 +172,10 @@ Install the plugin like any other Maubot plugin:
Make sure the bot has sufficient permissions in your rooms (especially for kicking, banning, and redacting messages), otherwise some features will not function correctly. Make sure the bot has sufficient permissions in your rooms (especially for kicking, banning, and redacting messages), otherwise some features will not function correctly.
## 🛠 Development & Support
If you need to get support or want to participate in the active development of this software, you can <a href="https://ztfr.eu/matrix">join our Zeitfresser Matrix Community</a> or the <a href="https://look.ztfr.eu/#/#support:ztfr.eu">Development & Support Channel</a> on Matrix.
## 🧭 Final Notes ## 🧭 Final Notes
Advanced Community Bot aims to strike a balance between usability and control. It provides the tools needed to manage a structured Matrix community effectively, without overwhelming administrators with complexity. Advanced Community Bot aims to strike a balance between usability and control. It provides the tools needed to manage a structured Matrix community effectively, without overwhelming administrators with complexity.
+10 -1
View File
@@ -91,7 +91,7 @@ welcome_sleep: 0
# (optional) # (optional)
notification_room: notification_room:
# message to send to the notification room when someone joins one of the above rooms: # message to send to the notification room when someone joins or leave one of the above rooms:
# available placeholders: # available placeholders:
# - {user}: display name of the joining user (falls back to localpart or user ID) # - {user}: display name of the joining user (falls back to localpart or user ID)
# - {user_id}: full Matrix user ID # - {user_id}: full Matrix user ID
@@ -102,6 +102,15 @@ notification_room:
join_notification_message: | join_notification_message: |
{user} has joined {room}. {user} has joined {room}.
leave_notification_message: |
{user} has left {room}.
kick_notification_message: |
{user} was kicked by {actor} from {room}.
ban_notification_message: |
{user} was baned by {actor} from {room}.
# whether to censor files/messages # whether to censor files/messages
# can be boolean (true/false) for all-or-nothing behavior, # 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 # or pass a list of room IDs to only censor certain rooms. this may be helpful
+79 -1
View File
@@ -206,6 +206,8 @@ class CommunityBot(Plugin):
target_room_id: RoomID, target_room_id: RoomID,
template: str, template: str,
context: RenderContext, context: RenderContext,
actor_id: Optional[str] = None,
actor_display: Optional[str] = None,
) -> None: ) -> None:
"""Render a template once and send plaintext + HTML variants.""" """Render a template once and send plaintext + HTML variants."""
plain_text, html_message = self._render_message_template( plain_text, html_message = self._render_message_template(
@@ -214,6 +216,8 @@ class CommunityBot(Plugin):
context.user_display, context.user_display,
context.room_id, context.room_id,
context.room_text, context.room_text,
actor_id,
actor_display,
) )
await self.client.send_notice(target_room_id, plain_text, html=html_message) await self.client.send_notice(target_room_id, plain_text, html=html_message)
@@ -238,6 +242,56 @@ class CommunityBot(Plugin):
context, context,
) )
async def _handle_leave_notifications(self, evt: StateEvent, event_type: str) -> None:
"""Send notifications when a user leaves, is kicked, or banned."""
# Optional: nur relevante Räume (wie bei join)
space_rooms = await self.get_space_roomlist()
if evt.room_id not in space_rooms:
return
if not self.config["notification_room"]:
return
user_id = evt.state_key
sender = evt.sender
context = await self._build_render_context(evt.room_id, user_id)
# === Actor bestimmen ===
actor_id = None
actor_display = None
if sender != user_id:
actor_id = sender
actor_display = await self._get_user_display_name(evt.room_id, sender)
# === Event-Typ bestimmen ===
if event_type == "leave" and sender == user_id:
template = self.config["leave_notification_message"]
elif event_type == "kick":
template = self.config["kick_notification_message"]
elif event_type == "ban":
template = self.config["ban_notification_message"]
else:
return
# === Template cleanup wenn kein actor ===
if not actor_id:
template = template.replace(" von {actor}", "")
template = template.replace(" {actor}", "")
await self._send_rendered_notice(
self.config["notification_room"],
template,
context,
actor_id=actor_id,
actor_display=actor_display,
)
def _is_human_verification_enabled_for_room(self, room_id: RoomID) -> bool: def _is_human_verification_enabled_for_room(self, room_id: RoomID) -> bool:
configured = self.config["check_if_human"] configured = self.config["check_if_human"]
if isinstance(configured, bool): if isinstance(configured, bool):
@@ -408,6 +462,20 @@ class CommunityBot(Plugin):
return label, f"<a href='{href}'>{escape(label)}</a>" return label, f"<a href='{href}'>{escape(label)}</a>"
def _format_actor_pill(
self,
actor_id: Optional[str],
actor_display: Optional[str] = None,
) -> Tuple[str, str]:
"""Return plaintext and HTML variants for the {actor} placeholder."""
if not actor_id:
return "", ""
safe_display = actor_display or actor_id
href = self._matrix_uri_user(str(actor_id))
return safe_display, f"<a href='{href}'>{escape(safe_display)}</a>"
def _format_room_pill( def _format_room_pill(
self, self,
room_id: Optional[str] = None, room_id: Optional[str] = None,
@@ -434,6 +502,8 @@ class CommunityBot(Plugin):
user_display: Optional[str] = None, user_display: Optional[str] = None,
room_id: Optional[str] = None, room_id: Optional[str] = None,
room_text: Optional[str] = None, room_text: Optional[str] = None,
actor_id: Optional[str] = None,
actor_display: Optional[str] = None,
) -> Tuple[str, str]: ) -> Tuple[str, str]:
user_url = self._matrix_to_url(user_id) user_url = self._matrix_to_url(user_id)
safe_user_display = user_display or user_id safe_user_display = user_display or user_id
@@ -442,6 +512,7 @@ class CommunityBot(Plugin):
room_url = self._matrix_to_url(safe_room_id) if safe_room_id else "" room_url = self._matrix_to_url(safe_room_id) if safe_room_id else ""
user_plain, user_html = self._format_user_pill(user_id, safe_user_display) user_plain, user_html = self._format_user_pill(user_id, safe_user_display)
room_plain, room_html = self._format_room_pill(safe_room_id, safe_room_text) room_plain, room_html = self._format_room_pill(safe_room_id, safe_room_text)
actor_plain, actor_html = self._format_actor_pill(actor_id, actor_display)
plain_text = template.format( plain_text = template.format(
user=user_plain, user=user_plain,
@@ -450,6 +521,8 @@ class CommunityBot(Plugin):
room=room_plain, room=room_plain,
room_link=room_url, room_link=room_url,
room_id=safe_room_id, room_id=safe_room_id,
actor=actor_plain,
actor_display=actor_display or "",
) )
html_message = template.format( html_message = template.format(
@@ -462,7 +535,9 @@ class CommunityBot(Plugin):
if room_url if room_url
else escape(safe_room_text) else escape(safe_room_text)
), ),
room_id=escape(safe_room_id), room_id=safe_room_id,
actor=actor_html,
actor_display=escape(actor_display) if actor_display else "",
) )
return plain_text, html_message return plain_text, html_message
@@ -1331,16 +1406,19 @@ class CommunityBot(Plugin):
async def handle_leave(self, evt: StateEvent) -> None: async def handle_leave(self, evt: StateEvent) -> None:
"""Handle voluntary leave events.""" """Handle voluntary leave events."""
await self.handle_leave_events(evt) await self.handle_leave_events(evt)
await self._handle_leave_notifications(evt, "leave")
@event.on(InternalEventType.KICK) @event.on(InternalEventType.KICK)
async def handle_kick(self, evt: StateEvent) -> None: async def handle_kick(self, evt: StateEvent) -> None:
"""Handle kick events.""" """Handle kick events."""
await self.handle_leave_events(evt) await self.handle_leave_events(evt)
await self._handle_leave_notifications(evt, "kick")
@event.on(InternalEventType.BAN) @event.on(InternalEventType.BAN)
async def handle_ban(self, evt: StateEvent) -> None: async def handle_ban(self, evt: StateEvent) -> None:
"""Handle ban events.""" """Handle ban events."""
await self.handle_leave_events(evt) await self.handle_leave_events(evt)
await self._handle_leave_notifications(evt, "ban")
@event.on(InternalEventType.JOIN) @event.on(InternalEventType.JOIN)
async def newjoin(self, evt: StateEvent) -> None: async def newjoin(self, evt: StateEvent) -> None:
+1 -1
View File
@@ -2,7 +2,7 @@ maubot: 0.1.0
id: advanced-community-bot id: advanced-community-bot
name: Advanced Community Bot name: Advanced Community Bot
description: Advanced Community Bot is a Maubot Bot Plugin to manage Matrix servers. description: Advanced Community Bot is a Maubot Bot Plugin to manage Matrix servers.
version: 1.0.0 version: 1.0.1
license: MIT license: MIT
modules: modules: