Первый коммит

This commit is contained in:
2026-02-17 11:24:55 +07:00
commit a06448ca4b
109 changed files with 21165 additions and 0 deletions

224
configs/config.py Normal file
View File

@@ -0,0 +1,224 @@
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
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
# Параметры сообщений
PARSE_MODE: str = "HTML"
PREFIX: str = "/!.&?"
# Разрешения и логирование
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] = []
ADMIN_CHAT_ID: int = 0
# Настройки бота
BOT_NAME: str = "Бот"
BOT_DESCRIPTION: Optional[str] = None
BOT_SHORT_DESCRIPTION: Optional[str] = None
# Права администратора
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', 'POSTS_DIR', 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)
# ✅ Создание директории для постов
if not self.POSTS_DIR.exists():
self.POSTS_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 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
WORDS_FILE = settings.WORDS_FILE
# Экспорт
__all__ = (
'settings',
'BOT_TOKEN',
'ADMIN_CHAT_ID',
'SUPER_ADMIN_IDS',
'WORDS_FILE',
)