From f47e8cae2f2b7a301604425976a4895a0d42e6a8 Mon Sep 17 00:00:00 2001 From: Verum Date: Mon, 23 Feb 2026 14:11:45 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/config.py | 285 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 configs/config.py diff --git a/configs/config.py b/configs/config.py new file mode 100644 index 0000000..6eb0423 --- /dev/null +++ b/configs/config.py @@ -0,0 +1,285 @@ +from pathlib import Path +from urllib.parse import urlparse, ParseResult +from typing import Optional, Any +from secrets import token_urlsafe + +from pydantic import field_validator, model_validator, Field +from pydantic_settings import BaseSettings, SettingsConfigDict +from aiogram.types import ChatAdministratorRights + + +class _Settings(BaseSettings): + """Настройки бота с комплексной валидацией""" + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + extra="ignore", + case_sensitive=False, + validate_default=True, + ) + + # ============== ОСНОВНЫЕ ПАРАМЕТРЫ ============== + # Токены бота + BOT_TOKEN: Optional[str] = None + DATABASE_PATH: Optional[str] = "data/banwords.db" + + # Параметры сообщений + PARSE_MODE: str = "HTML" + PREFIX: str = "/!.&?" + LOG_LEVEL: str = "TRACE" + + # Разрешения и логирование + BOT_EDIT: bool = False + START_INFO_CONSOLE: bool = True + START_INFO_TO_FILE: bool = True + LOG_CONSOLE: bool = True + LOG_FILE: bool = True + LOG_DIR: Path = Path('Logs') + LOG_FILE_INFO: Path = Path('bot_info.log') + LOG_ROTATION: str = '100 MB' + LOG_RETENTION: str = '7 days' + + # Вебхук + WEBHOOK: bool = False + SECRET_TOKEN: Optional[str] = '' + WEBHOOK_URL: Optional[str] = None + WEBAPP_HOST: str = "0.0.0.0" + WEBAPP_PORT: int = 3131 + LOG_LEVEL: str = "warning" + ACCES_LOG: bool = False + + # API ключи + API_KEY: Optional[str] = None + WEB_API_KEY: Optional[str] = None + WEATHER_API_KEY: Optional[str] = None + + # Идентификаторы + OWNER_ID: list[int] = [6751720805] + ADMIN_ID: list[int] = [] + + # Настройки бота + BOT_NAME: str = "Бот" + BOT_DESCRIPTION: Optional[str] = None + BOT_SHORT_DESCRIPTION: Optional[str] = None + + # ============ АВТОКОММЕНТАРИИ В КАНАЛЕ ============ + + AUTO_COMMENT_CHANNELS: str = Field( + default="", + description="ID каналов через запятую" + ) + + AUTO_COMMENT_TEXT: str = Field( + default="🔍 Нужна помощь?\n\nИспользуй наш сервис!", + description="Текст по умолчанию (HTML)" + ) + + AUTO_COMMENT_BUTTON_TEXT: str = Field( + default="🌐 Искать в Google", + description="Текст кнопки по умолчанию" + ) + + AUTO_COMMENT_BUTTON_URL: str = Field( + default="https://www.google.com", + description="URL кнопки по умолчанию" + ) + + AUTO_COMMENT_PHOTO_URL: str = Field( + default="https://via.placeholder.com/800x600.png", + description="URL фото по умолчанию" + ) + + # ================= АДМИНСКИЕ УВЕДОМЛЕНИЯ ================= + + # ID чата для уведомлений о банвордах/спаме + ADMIN_CHAT_ID: Optional[int] = -1002522785068 + + # ID топика для уведомлений о банвордах (опционально) + # Если None - уведомления идут в основной чат (General) + ADMIN_THREAD_ID: Optional[int] = None # Например: 12345 + + # ================= РЕПОРТЫ ================= + + # ID чата для репортов (если None, репорты идут владельцам в ЛС) + REPORT_CHAT_ID: Optional[int] = ADMIN_CHAT_ID # Можно тот же чат или другой + + # ID топика для репортов (опционально) + # Если None - репорты идут в основной чат (General) + REPORT_THREAD_ID: Optional[int] = None # ✅ ИСПРАВЛЕНО: было ADMIN_THREAD_ID + + + + # Права администратора + ANONYMOUS: bool = False + MANAGE_CHAT: bool = True + CHANGE_INFO: bool = True + PROMOTE_MEMBERS: bool = True + RESTRICT_MEMBERS: bool = True + POST_MESSAGE: bool = True + MANAGE_TOPICS: bool = True + INVITE_USER: bool = True + DELETE_MESSAGES: bool = True + MANAGE_VIDEO_CHATS: bool = True + EDIT_MESSAGES: bool = True + PIN_MESSAGE: bool = True + POST_STORIES: bool = True + EDIT_STORIES: bool = True + DELETE_STORIES: bool = True + + # Настройки сообщений + DISABLE_NOTIFICATION: bool = False + PROTECT_CONTENT: bool = False + ALLOW_SENDING_WITHOUT_REPLY: bool = True + LINK_PREVIEW_IS_DISABLED: bool = False + LINK_PREVIEW_PREFER_SMALL_MEDIA: bool = False + LINK_PREVIEW_PREFER_LARGE_MEDIA: bool = True + LINK_PREVIEW_SHOW_ABOVE_TEXT: bool = True + SHOW_CAPTION_ABOVE_MEDIA: bool = False + + # улучшения + ANTI_SPAM: bool = True + + # ================= ВАЛИДАТОРЫ ================= + @field_validator('PARSE_MODE') + def validate_parse_mode(cls, v: str) -> str: + allowed_modes: set[str] = {"HTML", "Markdown", "MarkdownV2"} + if v not in allowed_modes: + raise ValueError(f"Недопустимый PARSE_MODE. Допустимые: {', '.join(allowed_modes)}") + return v + + @field_validator('PREFIX') + def validate_prefix(cls, v: str) -> str: + cleaned: str = ''.join(dict.fromkeys(v)) # Удаление дубликатов с сохранением порядка + if len(cleaned) < 1: + raise ValueError("PREFIX должен содержать хотя бы один символ") + return cleaned + + @field_validator('LOG_DIR', 'LOG_FILE_INFO', mode='before') + def validate_paths(cls, v: Any) -> Path: + return Path(v) if isinstance(v, str) else v + + @field_validator('WEBHOOK_URL') + def validate_webhook_url(cls, v: Optional[str]) -> Optional[str]: + if v is None: + return v + parsed: ParseResult = urlparse(v) + if not all([parsed.scheme, parsed.netloc]): + raise ValueError("Некорректный URL вебхука") + if parsed.scheme != 'https': + raise ValueError("WEBHOOK_URL должен использовать HTTPS") + return v + + @field_validator('BOT_NAME') + def validate_non_empty(cls, v: str) -> str: + if not v.strip(): + raise ValueError("Поле не может быть пустым") + return v + + @model_validator(mode='after') + def validate_bot_token(self) -> "_Settings": + if not self.BOT_TOKEN: + raise ValueError("Требуется BOT_TOKEN для рабочего режима") + return self + + @model_validator(mode='after') + def validate_webhook_config(self) -> "_Settings": + if self.WEBHOOK and not self.WEBHOOK_URL: + raise ValueError("WEBHOOK_URL обязателен при включенном WEBHOOK") + + # ✅ Генерация SECRET_TOKEN если не установлен + if self.WEBHOOK and not self.SECRET_TOKEN: + self.SECRET_TOKEN = token_urlsafe(32) + + return self + + @model_validator(mode='after') + def validate_logging_paths(self) -> "_Settings": + if self.LOG_FILE: + self.LOG_DIR.mkdir(parents=True, exist_ok=True) + + return self + + @model_validator(mode='after') + def set_dynamic_descriptions(self) -> "_Settings": + if self.BOT_DESCRIPTION is None: + self.BOT_DESCRIPTION = f"Ваш помощник в удивительные миры! Prod. by:『@verdise』" + if self.BOT_SHORT_DESCRIPTION is None: + self.BOT_SHORT_DESCRIPTION = f"Тех.поддержка: @verdise" + return self + + # ================= СВОЙСТВА ================= + @property + def AUTO_COMMENT_CHANNELS_LIST(self) -> list[int]: + """Преобразует строку ID каналов в список""" + if not self.AUTO_COMMENT_CHANNELS: + return [] + + try: + return [ + int(channel_id.strip()) + for channel_id in self.AUTO_COMMENT_CHANNELS.split(",") + if channel_id.strip() + ] + except ValueError: + from middleware.loggers import logger # ✅ ДОБАВЬ ИМПОРТ + logger.error( + "Неверный формат AUTO_COMMENT_CHANNELS", + log_type="CONFIG" + ) + return [] + + @property + def rights(self) -> ChatAdministratorRights: + """Права администратора бота""" + return ChatAdministratorRights( + is_anonymous=self.ANONYMOUS, + can_manage_chat=self.MANAGE_CHAT, + can_delete_messages=self.DELETE_MESSAGES, + can_manage_video_chats=self.MANAGE_VIDEO_CHATS, + can_restrict_members=self.RESTRICT_MEMBERS, + can_promote_members=self.PROMOTE_MEMBERS, + can_change_info=self.CHANGE_INFO, + can_invite_users=self.INVITE_USER, + can_post_stories=self.POST_STORIES, + can_edit_stories=self.EDIT_STORIES, + can_delete_stories=self.DELETE_STORIES, + can_post_messages=self.POST_MESSAGE, + can_edit_messages=self.EDIT_MESSAGES, + can_pin_messages=self.PIN_MESSAGE, + can_manage_topics=self.MANAGE_TOPICS, + ) + + @property + def active_bot_token(self) -> str: + """Активный токен бота в зависимости от режима""" + if not self.BOT_TOKEN: + raise ValueError("Активный токен бота отсутствует") + return self.BOT_TOKEN + + @property + def log_dir_absolute(self) -> Path: + """Абсолютный путь к директории логов""" + return self.LOG_DIR.absolute() + + @property + def super_admin_ids(self) -> set[int]: + """Множество ID суперадминов (для банвордов)""" + return set(self.OWNER_ID) + + +# ✅ Единственный экземпляр настроек +settings = _Settings() + +# ✅ ОПЦИОНАЛЬНО: Простые константы для обратной совместимости (без дублирования) +# Используются только для удобства импорта, но ссылаются на settings +BOT_TOKEN = settings.active_bot_token +ADMIN_CHAT_ID = settings.ADMIN_CHAT_ID +SUPER_ADMIN_IDS = settings.super_admin_ids + +# Экспорт +__all__ = ( + 'settings', + 'BOT_TOKEN', + 'ADMIN_CHAT_ID', + 'SUPER_ADMIN_IDS', +)