Skip to content

Commit 64b4cfb

Browse files
committed
feat(moderation): add @admin mention handling for admin intervention requests
- Implemented a new message handler to process @admin mentions in group chats. - Added functionality to notify admins of intervention requests without requiring a reply. - Enhanced the admin request notification with optional reasoning and message links for better context. - Updated README to document the new @admin command usage and examples.
1 parent 7d24235 commit 64b4cfb

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Stop with `Ctrl+C`.
8181
| `/mute` | Admin | Mute a user (optionally for N minutes). |
8282
| `/unmute` | Admin | Unmute a user. |
8383
| `/report` | Anyone | Report a message. |
84+
| `@admin` | Anyone | Request admin intervention (no reply needed). |
8485

8586
### Usage
8687

@@ -89,6 +90,7 @@ Stop with `Ctrl+C`.
8990
- **Mute**: `/mute @username [minutes] [reason]`, or reply to message. Omit minutes for indefinite mute.
9091
- **Unmute**: `/unmute @username`, `/unmute user_id`, or reply to user's message
9192
- **Report**: Reply to the offending message with `/report [reason]`
93+
- **Admin request**: Type `@admin` or `@admin [message]` to notify admins (no reply needed)
9294

9395
### Captcha (secret command)
9496

@@ -113,6 +115,9 @@ New members must send the secret command in DM to the bot. Default: `python-ital
113115
4. **Test report** (as any member):
114116
- Reply to a message with `/report spam` (or any reason).
115117

118+
5. **Test @admin** (as any member):
119+
- Type `@admin` or `@admin need help` to request admin intervention (no reply needed).
120+
116121
## Architecture
117122

118123
```

src/python_italy_bot/handlers/moderation.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from telegram import Update
77
from telegram.constants import ChatMemberStatus
8-
from telegram.ext import CommandHandler, ContextTypes
8+
from telegram.ext import CommandHandler, ContextTypes, MessageHandler, filters
99

1010
from ..services.moderation import ModerationService
1111

@@ -35,6 +35,12 @@ def create_moderation_handlers(moderation_service: ModerationService) -> list:
3535
CommandHandler("unmute", _handle_unmute),
3636
CommandHandler("report", _handle_report),
3737
CommandHandler("forcegroupregistration", _handle_force_group_registration),
38+
MessageHandler(
39+
(filters.TEXT | filters.CAPTION)
40+
& filters.ChatType.GROUPS
41+
& filters.Regex(r"(?i)@admin"),
42+
_handle_admin_mention,
43+
),
3844
]
3945

4046

@@ -360,6 +366,89 @@ async def _handle_report(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
360366
)
361367

362368

369+
async def _handle_admin_mention(
370+
update: Update, context: ContextTypes.DEFAULT_TYPE
371+
) -> None:
372+
"""Handle @admin mention: notify admins of intervention request (no reply needed)."""
373+
message = update.message
374+
if message is None or message.from_user is None:
375+
return
376+
377+
chat = update.effective_chat
378+
if chat is None or chat.type == "private":
379+
return
380+
381+
text = message.text or message.caption or ""
382+
reason = _extract_reason_after_admin(text)
383+
384+
await _notify_admins_of_admin_request(
385+
context=context,
386+
chat=chat,
387+
reporter=message.from_user,
388+
message_id=message.message_id,
389+
reason=reason,
390+
)
391+
392+
await message.reply_text(
393+
"Richiesta inviata. Gli amministratori interverranno."
394+
)
395+
logger.info(
396+
"Admin request: %s in chat %s",
397+
message.from_user.id,
398+
chat.id,
399+
)
400+
401+
402+
def _extract_reason_after_admin(text: str) -> str | None:
403+
"""Extract optional reason from text after @admin."""
404+
match = re.search(r"@admin\s*(.+)?", text, re.IGNORECASE | re.DOTALL)
405+
if match and match.group(1):
406+
return match.group(1).strip() or None
407+
return None
408+
409+
410+
async def _notify_admins_of_admin_request(
411+
context: ContextTypes.DEFAULT_TYPE,
412+
chat,
413+
reporter,
414+
message_id: int,
415+
reason: str | None,
416+
) -> None:
417+
"""Send admin intervention request notification to all chat admins via private message."""
418+
try:
419+
admins = await context.bot.get_chat_administrators(chat.id)
420+
except Exception as e:
421+
logger.warning("Failed to get admins for @admin notification: %s", e)
422+
return
423+
424+
chat_title = chat.title or "Chat"
425+
reporter_name = _get_user_display_name(reporter)
426+
message_link = _build_message_link(chat, message_id)
427+
428+
report_text = f"<b>{chat_title}:</b>\n"
429+
report_text += f'Richiesta intervento da: <a href="tg://user?id={reporter.id}">{reporter_name}</a> ({reporter.id})\n'
430+
if message_link:
431+
report_text += f'Link: <a href="{message_link}">qui</a>\n'
432+
if reason:
433+
report_text += f"Messaggio: {reason}"
434+
435+
for admin in admins:
436+
if admin.user.is_bot:
437+
continue
438+
try:
439+
await context.bot.send_message(
440+
chat_id=admin.user.id,
441+
text=report_text,
442+
parse_mode="HTML",
443+
)
444+
except Exception as e:
445+
logger.debug(
446+
"Could not send @admin request to admin %s: %s",
447+
admin.user.id,
448+
e,
449+
)
450+
451+
363452
def _get_user_display_name(user) -> str:
364453
"""Get display name for a user (full name or username)."""
365454
if user.first_name:

0 commit comments

Comments
 (0)