Модуль настроек проекта
This commit is contained in:
285
configs/config.py
Normal file
285
configs/config.py
Normal file
@@ -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="🔍 <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',
|
||||
)
|
||||
Reference in New Issue
Block a user