Handle supergroup joins via chat_member updates

Send verification challenges when Telegram hides join service messages, and subscribe to chat_member polling updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Artemii Peretiachenko 2026-07-09 14:46:34 +02:00
parent 1aa6aa006c
commit e84f748889
2 changed files with 66 additions and 10 deletions

View file

@ -31,7 +31,10 @@ async def main() -> None:
asyncio.create_task(sweeper_loop(bot=bot, cfg=cfg, db=db))
try:
await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
allowed_updates = list(dp.resolve_used_update_types())
if "chat_member" not in allowed_updates:
allowed_updates.append("chat_member")
await dp.start_polling(bot, allowed_updates=allowed_updates)
finally:
await db.close()

View file

@ -10,6 +10,7 @@ from aiogram.types import (
CallbackQuery,
ChatMemberAdministrator,
ChatMemberOwner,
ChatMemberUpdated,
InlineKeyboardButton,
InlineKeyboardMarkup,
Message,
@ -87,6 +88,32 @@ def _challenge_kb(chat_id: int, user_id: int) -> InlineKeyboardMarkup:
)
async def _on_user_joined(
*,
bot: Bot,
cfg: Config,
db: Database,
chat_id: int,
user_id: int,
user_full_name: str,
) -> None:
try:
if await _is_admin(bot, chat_id, user_id):
return
except TelegramBadRequest:
return
await db.delete_verified(chat_id=chat_id, user_id=user_id)
await _ensure_challenge(
bot=bot,
cfg=cfg,
db=db,
chat_id=chat_id,
user_id=user_id,
user_full_name=user_full_name,
)
async def _ensure_challenge(
*,
bot: Bot,
@ -162,15 +189,41 @@ async def on_new_members(message: Message, bot: Bot, cfg: Config, db: Database)
for user in message.new_chat_members:
if user.is_bot:
continue
try:
if await _is_admin(bot, chat_id, user.id):
continue
except TelegramBadRequest:
continue
await _on_user_joined(
bot=bot,
cfg=cfg,
db=db,
chat_id=chat_id,
user_id=user.id,
user_full_name=user.full_name,
)
# Force verification on every (re)join: prevents bypass if user was verified earlier.
await db.delete_verified(chat_id=chat_id, user_id=user.id)
await _ensure_challenge(
@router.chat_member()
async def on_chat_member_updated(
event: ChatMemberUpdated,
bot: Bot,
cfg: Config,
db: Database,
) -> None:
"""
Supergroups (especially linked discussion groups) often hide join service messages.
In that case Telegram sends chat_member updates instead of new_chat_members.
"""
chat_id = event.chat.id
if not _is_allowed_chat(cfg, chat_id):
return
user = event.new_chat_member.user
if user.is_bot:
return
old_status = event.old_chat_member.status
new_status = event.new_chat_member.status
if old_status not in ("left", "kicked") or new_status not in ("member", "restricted"):
return
await _on_user_joined(
bot=bot,
cfg=cfg,
db=db,