from asyncio import sleep from datetime import datetime from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.exceptions import TelegramRetryAfter from aiogram.fsm.storage.memory import MemoryStorage from aiogram.types import User, ChatAdministratorRights, BotDescription, BotShortDescription from aiogram.utils.i18n import I18n, SimpleI18nMiddleware from configs.config import BotSettings, BotEdit, Webhook, Permission from middleware.loggers import log, logger # Настройка экспорта в модули __all__ = ("dp", "bot", "BotInfo", "i18n") # FSM-хранилище и диспетчер storage: MemoryStorage = MemoryStorage() dp: Dispatcher = Dispatcher(storage=storage) dp["is_active"]: bool = True # Локализация i18n: I18n = I18n(path="locales", default_locale="ru", domain="bot") i18n_middleware: SimpleI18nMiddleware = SimpleI18nMiddleware(i18n=i18n) i18n_middleware.setup(dp) # Экземпляр бота bot: Bot = Bot( token=BotSettings.BOT_TOKEN, default=DefaultBotProperties( parse_mode=BotSettings.PARSE_MODE, disable_notification=BotSettings.DISABLE_NOTIFICATION, protect_content=BotSettings.PROTECT_CONTENT, allow_sending_without_reply=BotSettings.ALLOW_SENDING_WITHOUT_REPLY, link_preview_is_disabled=BotSettings.LINK_PREVIEW_IS_DISABLED, link_preview_prefer_small_media=BotSettings.LINK_PREVIEW_PREFER_SMALL_MEDIA, link_preview_prefer_large_media=BotSettings.LINK_PREVIEW_PREFER_LARGE_MEDIA, link_preview_show_above_text=BotSettings.LINK_PREVIEW_SHOW_ABOVE_TEXT, show_caption_above_media=BotSettings.SHOW_CAPTION_ABOVE_MEDIA, ), ) class BotInfo: """ Класс для хранения и управления информацией о боте. Все поля строго аннотированы, description заменено на widget. """ id: int | None = None url: str | None = None first_name: str | None = None last_name: str | None = None username: str | None = None widget: str | None = None # вместо description description: str | None = None # вместо short_description language_code: str = BotSettings.BOT_LANGUAGE prefix: str = BotSettings.PREFIX bot_owner: str = BotSettings.OWNER added_to_attachment_menu: bool = False supports_inline_queries: bool = False can_connect_to_business: bool = False has_main_web_app: bool = False can_join_groups: bool = False can_read_all_group_messages: bool = False rights: ChatAdministratorRights | None = None @classmethod @log(level="INFO", log_type="BOT", text="Настройка вебхука бота") async def webhook( cls, bots: Bot = bot, webhook_url: str = Webhook.WEBHOOK_URL, use_webhook: bool = Webhook.WEBHOOK ) -> None: """ Установка или удаление вебхука для бота. """ try: await bots.delete_webhook(drop_pending_updates=True) if use_webhook: if webhook_url is None: raise ValueError("Для установки вебхука необходимо указать webhook_url") try: await bots.set_webhook(webhook_url) except TelegramRetryAfter as e: logger.warning(f"Flood control при установке вебхука. Повтор через {e.retry_after} сек.") await sleep(e.retry_after) await bots.set_webhook(webhook_url) except Exception as e: logger.error(f"Ошибка при настройке вебхука: {e}") @classmethod @log(level="INFO", log_type="BOT", text="Получение информации о боте") async def info(cls, bots: Bot = bot) -> dict[str, object] | None: """ Получает и сохраняет основные данные о боте. """ try: bot_info: User = await bots.get_me() bot_description: BotDescription = await bots.get_my_description() bot_short_description: BotShortDescription = await bots.get_my_short_description() bot_rights: ChatAdministratorRights = await bot.get_my_default_administrator_rights() cls.id = bot_info.id cls.url = f"tg://user?id={cls.id}" cls.first_name = bot_info.first_name cls.last_name = bot_info.last_name cls.username = bot_info.username cls.language_code = bot_info.language_code # Описание (widget) и короткое описание (description) cls.widget = bot_description.description or "" cls.description = bot_short_description.short_description or "" cls.added_to_attachment_menu = getattr(bot_info, "added_to_attachment_menu", False) cls.supports_inline_queries = getattr(bot_info, "supports_inline_queries", False) cls.can_connect_to_business = getattr(bot_info, "can_connect_to_business", False) cls.has_main_web_app = getattr(bot_info, "has_main_web_app", False) cls.can_join_groups = getattr(bot_info, "can_join_groups", False) cls.can_read_all_group_messages = getattr(bot_info, "can_read_all_group_messages", False) cls.rights = bot_rights or None return { "id": cls.id, "url": cls.url, "first_name": cls.first_name, "last_name": cls.last_name, "username": cls.username, "language_code": cls.language_code, "widget": cls.widget, "description": cls.description, "added_to_attachment_menu": cls.added_to_attachment_menu, "supports_inline_queries": cls.supports_inline_queries, "can_connect_to_business": cls.can_connect_to_business, "has_main_web_app": cls.has_main_web_app, "can_join_groups": cls.can_join_groups, "can_read_all_group_messages": cls.can_read_all_group_messages, "prefix": cls.prefix, "bot_owner": cls.bot_owner, "rights": cls.rights, } except Exception as e: logger.error(f"Ошибка при получении информации о боте: {e}") return None @staticmethod @log(level="INFO", log_type="BOT", text="Установка прав администратора") async def set_administrator_rights(rights: ChatAdministratorRights = BotEdit.RIGHTS, bots: Bot = bot) -> None: """ Устанавливает дефолтные права администратора для бота. """ try: current_rights: ChatAdministratorRights = await bots.get_my_default_administrator_rights() if current_rights != rights: await bots.set_my_default_administrator_rights(rights=rights) await bots.set_my_default_administrator_rights(rights=rights, for_channels=True) except Exception as e: logger.error(f"Ошибка при установке прав администратора: {e}") @staticmethod @log(level="INFO", log_type="BOT", text="Обновление имени бота") async def set_name(new_name: str = BotEdit.NAME, bots: Bot = bot) -> None: """ Обновляет имя бота (от 1 до 32 символов). """ try: current_name: str = (await bots.get_me()).first_name if not (1 <= len(new_name) <= 32): raise ValueError("Имя бота должно быть от 1 до 32 символов.") if current_name != new_name: await bots.set_my_name(name=new_name) except Exception as e: logger.error(f"Ошибка при обновлении имени бота: {e}") @staticmethod @log(level="INFO", log_type="BOT", text="Обновление виджета бота") async def set_widget(new_widget: str = BotEdit.DESCRIPTION, bots: Bot = bot) -> None: """ Обновляет описание бота (widget). """ try: current_widget: BotDescription = await bots.get_my_description() if not (0 < len(new_widget) <= 255): raise ValueError("Виджет должен быть от 1 до 255 символов.") if current_widget.description != new_widget: await bots.set_my_description(description=new_widget) except Exception as e: logger.error(f"Ошибка при обновлении виджета бота: {e}") @staticmethod @log(level="INFO", log_type="BOT", text="Обновление короткого виджета бота") async def set_short_widget(new_short: str = BotEdit.SHORT_DESCRIPTION, bots: Bot = bot) -> None: """ Обновляет короткое описание (short_widget). """ try: current_short: BotShortDescription = await bots.get_my_short_description() if not (0 < len(new_short) <= 120): raise ValueError("Короткий виджет должен быть от 1 до 120 символов.") if current_short.short_description != new_short: await bots.set_my_short_description(short_description=new_short) except Exception as e: logger.error(f"Ошибка при обновлении короткого виджета бота: {e}") @staticmethod def start_info_out(out: bool = True) -> str | None: """ Формирует и выводит стартовую информацию о боте. """ try: bot_time: str = f"Бот @{BotInfo.username} запущен в {datetime.now().strftime('%S:%M:%H %d-%m-%Y')}\n" bot_name: str = f"Основное имя: {BotInfo.first_name}\n" bot_postname: str = f" Доп. имя: {BotInfo.last_name}\n" bot_description: str = f" Описание бота: {BotInfo.description}\n" bot_widget: str = f" Виджет бота: {BotInfo.widget}\n" bot_username: str = f" Юзернейм: @{BotInfo.username}\n" bot_id: str = f" ID: {BotInfo.id}\n" bot_can_join_groups: str = f" Может ли вступать в группы: {BotInfo.can_join_groups}\n" bot_can_read_all_group_messages: str = f" Чтение всех сообщений: {BotInfo.can_read_all_group_messages}\n" bot_added_to_attachment_menu: str = f" Добавлен в меню вложений: {BotInfo.added_to_attachment_menu}\n" bot_supports_inline_queries: str = f" Поддерживает инлайн-запросы: {BotInfo.supports_inline_queries}\n" bot_can_connect_to_business: str = f" Подключение к бизнес-аккаунтам: {BotInfo.can_connect_to_business}\n" bot_has_main_web_app: str = f" Основное веб-приложение: {BotInfo.has_main_web_app}\n" bot_prefixs: str = f" Префиксы команд бота: {BotInfo.prefix}\n" bot_all_info: str = ( f"{bot_name} {bot_postname} {bot_description} {bot_widget} {bot_username} " f"{bot_id} {bot_can_join_groups} {bot_can_read_all_group_messages} " f"{bot_added_to_attachment_menu} {bot_supports_inline_queries} " f"{bot_can_connect_to_business} {bot_has_main_web_app} {bot_prefixs}" ) if out: print(f"\033[34m{bot_all_info}\033[0m") with open("Logs/info.log", "w", encoding="utf-8") as log_file: log_file.write(f"{bot_time}{bot_all_info}") with open("Logs/bot_start.log", "a", encoding="utf-8") as log_start_file: log_start_file.write(f"{bot_time}\n") return bot_all_info except Exception as e: logger.error(f"Ошибка при выводе стартовой информации: {e}") return None @classmethod @log(level="INFO", log_type="START", text="Процесс запуска бота!") async def setup(cls, perm: bool = Permission.BOT_EDIT, bots: Bot = bot) -> None: """ Настройка и инициализация всех параметров бота при старте. """ try: await cls.webhook(bots=bots) await cls.info(bots=bots) if perm: await cls.set_administrator_rights(bots=bots) await cls.set_widget(bots=bots) await cls.set_short_widget(bots=bots) await cls.set_name(bots=bots) except Exception as e: logger.error(f"Ошибка при запуске настройки бота: {e}")