Skip to content

Commit 7d24235

Browse files
committed
feat(announce): add announcement handler for bot owner
- Implemented a new handler for broadcasting announcements to all registered groups. - Added support for HTML formatting and button syntax in announcements. - Integrated bot owner ID configuration to restrict command usage to the bot owner. - Updated application initialization to include the new announcement handler.
1 parent d4e41db commit 7d24235

4 files changed

Lines changed: 134 additions & 0 deletions

File tree

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ CAPTCHA_FILE_PATH=assets/regolamento.md
1919

2020
# Optional: URL to external rules/guide page (sent during verification deep link)
2121
# RULES_URL=https://example.com/rules
22+
23+
# Optional: Bot owner user ID (for /announce command)
24+
# BOT_OWNER_ID=123456789

src/python_italy_bot/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ def __init__(self) -> None:
5050
self.main_group_id: int | None = _get_optional_int("MAIN_GROUP_ID")
5151
self.local_group_ids: list[int] = _get_int_list("LOCAL_GROUP_IDS")
5252
self.rules_url: str | None = _get_optional_env("RULES_URL")
53+
self.bot_owner_id: int | None = _get_optional_int("BOT_OWNER_ID")
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""Handler for broadcasting announcements to all groups."""
2+
3+
import logging
4+
5+
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
6+
from telegram.ext import CommandHandler, ContextTypes
7+
8+
from ..config import Settings
9+
from ..services.captcha import BUTTON_URL_PATTERN
10+
from ..services.moderation import ModerationService
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
def _parse_button_urls(text: str) -> tuple[str, InlineKeyboardMarkup | None]:
16+
"""Extract buttonurl:// patterns and build InlineKeyboardMarkup.
17+
18+
Returns (clean_text, keyboard) where clean_text has button syntax removed.
19+
Multiple buttons on the same line become the same row.
20+
"""
21+
lines = text.split("\n")
22+
keyboard_rows: list[list[InlineKeyboardButton]] = []
23+
clean_lines: list[str] = []
24+
25+
for line in lines:
26+
matches = list(BUTTON_URL_PATTERN.finditer(line))
27+
if matches:
28+
row = [
29+
InlineKeyboardButton(text=m.group(1), url=m.group(2))
30+
for m in matches
31+
]
32+
keyboard_rows.append(row)
33+
clean_line = BUTTON_URL_PATTERN.sub("", line).strip()
34+
if clean_line:
35+
clean_lines.append(clean_line)
36+
else:
37+
clean_lines.append(line)
38+
39+
clean_text = "\n".join(clean_lines).strip()
40+
keyboard = InlineKeyboardMarkup(keyboard_rows) if keyboard_rows else None
41+
return clean_text, keyboard
42+
43+
44+
async def _handle_announce(
45+
update: Update,
46+
context: ContextTypes.DEFAULT_TYPE,
47+
moderation_service: ModerationService,
48+
settings: Settings,
49+
) -> None:
50+
"""Broadcast an announcement to all registered groups.
51+
52+
Only works in DM and only for the bot owner.
53+
Supports HTML formatting and [text](buttonurl://url) button syntax.
54+
"""
55+
message = update.effective_message
56+
chat = update.effective_chat
57+
user = update.effective_user
58+
59+
if not message or not chat or not user:
60+
return
61+
62+
if chat.type != "private":
63+
await message.reply_text("Questo comando funziona solo in chat privata.")
64+
return
65+
66+
if settings.bot_owner_id is None:
67+
await message.reply_text("BOT_OWNER_ID non configurato.")
68+
return
69+
70+
if user.id != settings.bot_owner_id:
71+
await message.reply_text("Solo il proprietario del bot può usare questo comando.")
72+
return
73+
74+
raw_text = message.text or ""
75+
announcement = raw_text.partition(" ")[2].strip()
76+
77+
if not announcement:
78+
await message.reply_text(
79+
"Uso: /announce <messaggio>\n\n"
80+
"Supporta HTML e bottoni: [Testo](buttonurl://url)"
81+
)
82+
return
83+
84+
clean_text, keyboard = _parse_button_urls(announcement)
85+
86+
if not clean_text:
87+
await message.reply_text("Il messaggio non può essere vuoto.")
88+
return
89+
90+
chat_ids = await moderation_service.get_all_chats()
91+
92+
if not chat_ids:
93+
await message.reply_text("Nessun gruppo registrato.")
94+
return
95+
96+
await message.reply_text(f"Invio annuncio a {len(chat_ids)} gruppi...")
97+
98+
success = 0
99+
failed = 0
100+
101+
for chat_id in chat_ids:
102+
try:
103+
await context.bot.send_message(
104+
chat_id=chat_id,
105+
text=clean_text,
106+
parse_mode="HTML",
107+
reply_markup=keyboard,
108+
)
109+
success += 1
110+
except Exception as e:
111+
failed += 1
112+
logger.warning("Failed to send announcement to %s: %s", chat_id, e)
113+
114+
await message.reply_text(f"Annuncio inviato: {success} ok, {failed} falliti.")
115+
116+
117+
def create_announce_handlers(
118+
moderation_service: ModerationService, settings: Settings
119+
) -> list[CommandHandler]:
120+
"""Create handlers for the announce command."""
121+
122+
async def announce_wrapper(
123+
update: Update, context: ContextTypes.DEFAULT_TYPE
124+
) -> None:
125+
await _handle_announce(update, context, moderation_service, settings)
126+
127+
return [CommandHandler("announce", announce_wrapper)]

src/python_italy_bot/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .config import Settings
88
from .db import create_repository
9+
from .handlers.announce import create_announce_handlers
910
from .handlers.id import create_id_handlers
1011
from .handlers.moderation import create_moderation_handlers
1112
from .handlers.settings import create_settings_handlers
@@ -49,6 +50,8 @@ async def _post_init(application) -> None:
4950
application.add_handler(handler)
5051
for handler in create_welcome_handlers(captcha_service):
5152
application.add_handler(handler)
53+
for handler in create_announce_handlers(moderation_service, settings):
54+
application.add_handler(handler)
5255
application.add_handler(create_spam_handler(spam_detector))
5356

5457

0 commit comments

Comments
 (0)