"""События участников: добавление бота, вход и выход пользователей.""" from __future__ import annotations import logging from aiogram import Router from aiogram.enums import ChatMemberStatus from aiogram.filters import JOIN_TRANSITION, LEAVE_TRANSITION, ChatMemberUpdatedFilter from aiogram.types import ChatMemberUpdated, FSInputFile from aiogram.utils.keyboard import InlineKeyboardBuilder from bot.services import alerts from bot.services import users as users_service from common.analytics import EventType, record from common.config import get_settings from common.texts import system, welcome from common.texts.render import render from common.texts.store import get_text logger = logging.getLogger(__name__) router = Router(name="member_events") @router.my_chat_member(ChatMemberUpdatedFilter(member_status_changed=JOIN_TRANSITION)) async def bot_added_handler(event: ChatMemberUpdated) -> None: """Бот добавлен в чат.""" settings = get_settings() if event.chat.id not in {settings.chat_id, settings.alert_chat_id}: await event.bot.send_message(chat_id=event.chat.id, text=system.MSG_BOT_FOREIGN_CHAT) await event.bot.leave_chat(event.chat.id) else: await event.bot.send_message(chat_id=event.chat.id, text=system.MSG_BOT_ADDED) @router.chat_member(ChatMemberUpdatedFilter(member_status_changed=JOIN_TRANSITION)) async def welcome_handler(event: ChatMemberUpdated) -> None: """Пользователь вошёл в чат — приветствие.""" user = event.new_chat_member.user if user.is_bot: return if event.chat.id != get_settings().chat_id: return await users_service.register_from_event(event) await alerts.notify_join( event.bot, username=user.username, user_id=user.id, chat_title=event.chat.title or "", ) builder = InlineKeyboardBuilder() builder.button(text=welcome.MSG_WELCOME_BUTTON_CAPTION, url=welcome.MSG_WELCOME_BUTTON_URL) markup = builder.as_markup() caption, body = welcome.split_welcome( await get_text("welcome.text"), await get_text("welcome.text2"), await get_text("welcome.text3"), ) caption = render(caption, {"username": user.username}) body = render(body, {"username": user.username}) # Подпись к фото ограничена 1024 символами; если не влезла — уйдёт в тело. if len(caption) > 1024: body = caption + (f"\n\n{body}" if body else "") caption = "" if body: await event.bot.send_photo( chat_id=event.chat.id, photo=FSInputFile("images/welcome_image.jpg"), caption=caption or None, ) await event.bot.send_message( chat_id=event.chat.id, text=body, reply_markup=markup, ) else: await event.bot.send_photo( chat_id=event.chat.id, photo=FSInputFile("images/welcome_image.jpg"), caption=caption or None, reply_markup=markup, ) @router.chat_member(ChatMemberUpdatedFilter(member_status_changed=LEAVE_TRANSITION)) async def goodbye_handler(event: ChatMemberUpdated) -> None: """Пользователь вышел или был исключён — удалить данные именно его.""" leaver = event.old_chat_member.user if leaver.is_bot: return actor = event.from_user kicked = event.new_chat_member.status == ChatMemberStatus.KICKED by_self = actor is None or actor.id == leaver.id record( EventType.LEAVE, user_id=leaver.id, username=leaver.username, chat_id=event.chat.id, payload={ "chat_title": event.chat.title or "", "kicked": kicked, "by_id": actor.id if actor and not by_self else 0, "by_username": actor.username if actor and not by_self else "", }, ) await users_service.delete_user(leaver.id) logger.info("Удалён пользователь %s после выхода из чата", leaver.id) # Исключение самим ботом (кнопка в админке): уведомление уже отправила админка. if kicked and actor is not None and actor.id == event.bot.id: return if kicked and not by_self: await alerts.notify_kick( event.bot, actor_username=actor.username if actor else None, actor_id=actor.id if actor else 0, username=leaver.username, user_id=leaver.id, chat_title=event.chat.title or "", ) else: await alerts.notify_leave( event.bot, username=leaver.username, user_id=leaver.id, chat_title=event.chat.title or "", )