Files
PrimoGuardBot/configs/config.py

286 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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="🔍 <b>Нужна помощь?</b>\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',
)