from __future__ import annotations import asyncio import logging import sys from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.enums import ChatMemberStatus, ChatType, ParseMode from aiogram.exceptions import TelegramAPIError from aiogram.types import ChatMemberUpdated from bot.config import Config, apply_text_overrides, load_config from bot.db.repository import ClientRepository from bot.handlers.client import router as client_router from bot.handlers.manager import build_manager_router from bot.services.cooldown import CooldownService from bot.services.relay import RelayService logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s [%(name)s] %(message)s", stream=sys.stdout, ) logger = logging.getLogger(__name__) async def on_bot_added(event: ChatMemberUpdated, config: Config, relay: RelayService) -> None: """Log chat id when the bot is added to a group — helps set MANAGER_GROUP_ID.""" new = event.new_chat_member if not relay.is_self(new.user.id): return if new.status in {ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.MEMBER}: logger.info( "Bot added to chat %s (%s). Use this as MANAGER_GROUP_ID if it is the forum.", event.chat.id, event.chat.title, ) async def validate_manager_group(bot: Bot, manager_group_id: int) -> None: """Fail fast if MANAGER_GROUP_ID is wrong or the bot lacks required rights.""" try: chat = await bot.get_chat(manager_group_id) except TelegramAPIError as exc: raise RuntimeError( f"Не удалось получить чат MANAGER_GROUP_ID={manager_group_id}: {exc}. " "Проверьте id и что бот добавлен в группу." ) from exc if chat.type != ChatType.SUPERGROUP: raise RuntimeError( f"MANAGER_GROUP_ID должен быть супергруппой, сейчас type={chat.type!r} " f"(chat_id={chat.id}, title={chat.title!r})." ) if not bool(getattr(chat, "is_forum", False)): raise RuntimeError( f"В чате {chat.id} ({chat.title!r}) не включены Topics. " "Включите темы в настройках группы." ) me = await bot.get_me() try: member = await bot.get_chat_member(manager_group_id, me.id) except TelegramAPIError as exc: raise RuntimeError( f"Не удалось проверить права бота в группе {chat.id}: {exc}." ) from exc if member.status == ChatMemberStatus.CREATOR: logger.info("Manager group OK: %s (%s), bot is creator", chat.id, chat.title) return if member.status != ChatMemberStatus.ADMINISTRATOR: raise RuntimeError( f"Бот должен быть администратором группы {chat.title!r} (id={chat.id}). " f"Текущий статус: {member.status!r}." ) missing: list[str] = [] if not bool(getattr(member, "can_manage_topics", True)): missing.append("Manage topics") if not bool(getattr(member, "can_delete_messages", True)): missing.append("Delete messages") can_post = getattr(member, "can_post_messages", None) # In groups can_post_messages may be None; treat explicit False as missing. if can_post is False: missing.append("Post messages") if missing: raise RuntimeError( f"Бот — админ группы {chat.title!r}, но не хватает прав: {', '.join(missing)}. " "Выдайте их в настройках администраторов." ) logger.info("Manager group OK: %s (%s), bot is admin", chat.id, chat.title) async def run() -> None: config = load_config() apply_text_overrides(config) repo = ClientRepository(config.database_path) await repo.connect() relay = RelayService(repo, config.manager_group_id) cooldown = CooldownService(config.client_cooldown_seconds) bot = Bot( token=config.bot_token, default=DefaultBotProperties(parse_mode=ParseMode.HTML), ) me = await bot.get_me() relay.set_bot_id(me.id) try: await validate_manager_group(bot, config.manager_group_id) except RuntimeError as exc: logger.error("%s", exc) print(f"ERROR: {exc}", file=sys.stderr) await repo.close() await bot.session.close() raise SystemExit(1) from exc dp = Dispatcher() dp["config"] = config dp["repo"] = repo dp["relay"] = relay dp["cooldown"] = cooldown dp.include_router(client_router) dp.include_router(build_manager_router(config.manager_group_id)) dp.my_chat_member.register(on_bot_added) logger.info( "Starting bot @%s (manager group %s, client cooldown %.1fs)", me.username, config.manager_group_id, config.client_cooldown_seconds, ) try: await dp.start_polling( bot, allowed_updates=dp.resolve_used_update_types(), ) finally: await repo.close() await bot.session.close() def main() -> None: asyncio.run(run()) if __name__ == "__main__": main()