diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 0000000..3c0ba71 --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,118 @@ +from typing import Optional +import logging + +from discord import Intents +from discord.ext import commands + +from configs import settings +from .help import MyHelpCommand +from middleware.loggers import logger +from .storage import storage + +__all__ = ("Bot", "discbot") + + +class Bot(commands.Bot): + """ + Основной класс Discord-бота с методами настройки и запуска. + + Поддерживает передачу token, prefix, intents и help_command в конструктор. + """ + + def __init__( + self, + token: Optional[str] = None, + prefix: Optional[str] = None, + intents: Optional[Intents] = None, + help_command: Optional[commands.HelpCommand] = None, + ) -> None: + """ + :param token: Токен бота (если None — берётся из settings.BOT_TOKEN). + :param prefix: Префикс команд (если None — берётся из settings.PREFIX или '!'). + :param intents: Intents (если None — создаются стандартные + privileged). + :param help_command: Кастомная команда помощи. + """ + # Intents по умолчанию + if intents is None: + intents = Intents.default() + intents.guilds = True + intents.message_content = True # Требует включения в Developer Portal + intents.members = True + + # Префикс по умолчанию + command_prefix: str = prefix or getattr(settings, "PREFIX", "!") + + # Help-команда по умолчанию + if help_command is None: + help_command = MyHelpCommand() + + super().__init__( + command_prefix=command_prefix, + intents=intents, + help_command=help_command, + ) + + # Сохраняем токен и хранилище + self._token: Optional[str] = token + self.storage = storage # type: ignore[assignment] + + @property + def token(self) -> Optional[str]: + """ + Токен бота: сначала из конструктора, затем из settings. + """ + return self._token or settings.BOT_TOKEN + + async def setup(self) -> None: + """ + Инициализация бота: логгер, cogs, логирование discord.py. + """ + logger.setup(start=True) + logger.info(text="Настройка бота...", log_type="SYSTEM") + + await self.load_cogs() + + logging.basicConfig( + level=logging.WARNING, + format="%(asctime)s:%(levelname)s:%(name)s: %(message)s", + ) + logging.getLogger("discord").setLevel(logging.INFO) + + async def load_cogs(self) -> None: + """ + Загрузить все модули cogs. + """ + cogs: list[str] = [ + "cogs.events", + "cogs.moderation", + "cogs.blacklist", + "cogs.reminders", + ] + for cog in cogs: + try: + await self.load_extension(cog) + logger.info(f"Загружен cog: {cog}", log_type="COGS") + except Exception as e: + logger.error(f"Ошибка загрузки {cog}: {e}", log_type="COGS") + + async def start_bot(self, token: Optional[str] = None) -> None: + """ + Запуск бота с использованием сохранённого токена или переданного. + + :param token: Токен бота (если None — используется self.token). + """ + use_token: Optional[str] = token or self.token + if not use_token: + error: str = "BOT_TOKEN не задан (ни в конструкторе, ни в settings)" + logger.error(error) + raise ValueError(error) + + logger.info(text="Запуск бота...", log_type="START") + await self.start(use_token) + + +# Глобальный экземпляр — МОЖНО ПЕРЕДАВАТЬ token/prefix ПРЯМО ЗДЕСЬ +discbot: Bot = Bot( + token=settings.BOT_TOKEN, # кастомный токен + prefix=settings.PREFIX, # кастомный префикс +)