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

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

398
bot/core/bots.py Normal file
View File

@@ -0,0 +1,398 @@
"""
Ядро PrimoGuard Bot: Инициализация, Управление и Информация
"""
from datetime import datetime
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import User, ChatAdministratorRights, BotDescription, BotShortDescription
from aiogram.utils.i18n import I18n, SimpleI18nMiddleware
from pymorphy3 import MorphAnalyzer
from configs import settings
from middleware.loggers import logger
__all__ = ('bot', 'dp', 'storage', 'i18n', 'morph', 'BotInfo')
# ================= STORAGE И DISPATCHER =================
storage = MemoryStorage()
dp = Dispatcher(storage=storage)
dp["is_active"] = True
# ================= ИНТЕРНАЦИОНАЛИЗАЦИЯ =================
i18n = I18n(path="locales", default_locale="ru", domain="bot")
i18n_middleware = SimpleI18nMiddleware(i18n=i18n)
i18n_middleware.setup(dp)
# ================= БОТ =================
bot = Bot(
token=settings.active_bot_token,
default=DefaultBotProperties(
parse_mode=settings.PARSE_MODE,
disable_notification=settings.DISABLE_NOTIFICATION,
protect_content=settings.PROTECT_CONTENT,
allow_sending_without_reply=settings.ALLOW_SENDING_WITHOUT_REPLY,
link_preview_is_disabled=settings.LINK_PREVIEW_IS_DISABLED,
link_preview_prefer_small_media=settings.LINK_PREVIEW_PREFER_SMALL_MEDIA,
link_preview_prefer_large_media=settings.LINK_PREVIEW_PREFER_LARGE_MEDIA,
link_preview_show_above_text=settings.LINK_PREVIEW_SHOW_ABOVE_TEXT,
show_caption_above_media=settings.SHOW_CAPTION_ABOVE_MEDIA
)
)
# ================= МОРФОАНАЛИЗАТОР =================
morph = MorphAnalyzer()
# ================= КЛАСС УПРАВЛЕНИЯ БОТОМ =================
class BotInfo:
"""Класс для хранения данных и управления ботом"""
# Основные данные бота
id: int = None
url: str = None
first_name: str = None
last_name: str = None
username: str = None
description: str = None
short_description: str = None
is_premium: bool = False
# Возможности бота
can_join_groups: bool = False
can_read_all_group_messages: bool = False
supports_inline_queries: bool = False
can_connect_to_business: bool = False
has_main_web_app: bool = False
added_to_attachment_menu: bool = False
# Данные из конфига
prefix: str = settings.PREFIX
started_at: datetime = None
@classmethod
def mention(cls) -> str:
"""Упоминание бота"""
return f'@{cls.username}' if cls.username else f'id{cls.id}'
@classmethod
async def webhook(cls, bots: Bot = bot) -> None:
"""
Настраивает webhook для бота.
Args:
bots: Объект бота для управления
"""
# Только если включен режим webhook
if not settings.WEBHOOK:
logger.debug("Режим Webhook отключен (WEBHOOK=False)", log_type='WEBHOOK')
return
# Проверяем наличие URL
if not settings.WEBHOOK_URL:
logger.warning(
"⚠️ WEBHOOK_URL не указан в настройках",
log_type='WEBHOOK'
)
return
try:
logger.info("Настройка вебхука бота", log_type='BOT')
# Проверяем текущий webhook
current_info = await bots.get_webhook_info()
# Если уже установлен нужный URL, пропускаем
if current_info.url == settings.WEBHOOK_URL:
logger.info(
f"✓ Вебхук уже установлен: {settings.WEBHOOK_URL}",
log_type='BOT'
)
return
# Устанавливаем webhook
await bots.set_webhook(
url=settings.WEBHOOK_URL,
secret_token=settings.SECRET_TOKEN,
drop_pending_updates=True
)
logger.success(
f"✓ Вебхук установлен: {settings.WEBHOOK_URL}",
log_type='BOT'
)
except Exception as e:
logger.error(
f"❌ Ошибка установки вебхука: {e}",
log_type='BOT'
)
@classmethod
async def info(cls, bots: Bot = bot) -> dict:
"""
Получает и сохраняет информацию о боте.
:param bots: Объект бота для управления
:return: Словарь с данными о боте
"""
logger.info("Получение информации о боте", log_type='BOT')
bot_info: User = await bots.get_me()
cls.id = bot_info.id
cls.url = f'tg://user?id={cls.id}'
cls.first_name = bot_info.first_name
cls.last_name = bot_info.last_name
cls.username = bot_info.username
cls.can_join_groups = getattr(bot_info, 'can_join_groups', False)
cls.can_read_all_group_messages = getattr(bot_info, 'can_read_all_group_messages', False)
cls.supports_inline_queries = bot_info.supports_inline_queries or False
cls.can_connect_to_business = bot_info.can_connect_to_business or False
cls.has_main_web_app = bot_info.has_main_web_app or False
cls.added_to_attachment_menu = bot_info.added_to_attachment_menu or False
cls.started_at = datetime.now()
logger.success(f"Информация о боте @{cls.username} получена", log_type='BOT')
return {
'id': cls.id,
'url': cls.url,
'first_name': cls.first_name,
'last_name': cls.last_name,
'username': cls.username,
'prefix': cls.prefix,
'is_premium': cls.is_premium,
'can_join_groups': cls.can_join_groups,
'can_read_all_group_messages': cls.can_read_all_group_messages,
'supports_inline_queries': cls.supports_inline_queries,
'can_connect_to_business': cls.can_connect_to_business,
'has_main_web_app': cls.has_main_web_app,
'added_to_attachment_menu': cls.added_to_attachment_menu,
}
@staticmethod
async def set_name(bots: Bot = bot, new_name: str = None) -> bool:
"""Устанавливает имя бота"""
new_name = new_name or settings.BOT_NAME
if not (1 <= len(new_name) <= 64):
logger.error(f"Имя бота должно быть от 1 до 64 символов (текущее: {len(new_name)})", log_type='BOT_SETUP')
return False
try:
current_name = (await bots.get_me()).first_name
if current_name == new_name:
logger.debug(f"Имя бота уже установлено: '{current_name}'", log_type='BOT_SETUP')
return False
await bots.set_my_name(new_name)
logger.success(f"Имя бота изменено: '{current_name}''{new_name}'", log_type='BOT_SETUP')
return True
except Exception as e:
logger.error(f"Ошибка установки имени бота: {e}", log_type='BOT_SETUP')
return False
@staticmethod
async def set_description(bots: Bot = bot, new_description: str = None) -> bool:
"""Устанавливает полное описание бота"""
new_description = new_description or settings.BOT_DESCRIPTION
if not (0 < len(new_description) <= 512):
logger.error(f"Описание должно быть от 1 до 512 символов (текущее: {len(new_description)})", log_type='BOT_SETUP')
return False
try:
current_description: BotDescription = await bots.get_my_description()
current_text = current_description.description if current_description else ""
if current_text == new_description:
logger.debug("Описание бота уже установлено", log_type='BOT_SETUP')
return False
await bots.set_my_description(description=new_description)
logger.success("Описание бота обновлено", log_type='BOT_SETUP')
return True
except Exception as e:
logger.error(f"Ошибка установки описания бота: {e}", log_type='BOT_SETUP')
return False
@staticmethod
async def set_short_description(bots: Bot = bot, new_short: str = None) -> bool:
"""Устанавливает короткое описание бота"""
new_short = new_short or settings.BOT_SHORT_DESCRIPTION
if not (0 < len(new_short) <= 120):
logger.error(f"Короткое описание должно быть от 1 до 120 символов (текущее: {len(new_short)})", log_type='BOT_SETUP')
return False
try:
current_short: BotShortDescription = await bots.get_my_short_description()
current_text = current_short.short_description if current_short else ""
if current_text == new_short:
logger.debug("Короткое описание бота уже установлено", log_type='BOT_SETUP')
return False
await bots.set_my_short_description(short_description=new_short)
logger.success("Короткое описание бота обновлено", log_type='BOT_SETUP')
return True
except Exception as e:
logger.error(f"Ошибка установки короткого описания: {e}", log_type='BOT_SETUP')
return False
@staticmethod
async def set_administrator_rights(bots: Bot = bot, rights: ChatAdministratorRights = None) -> bool:
"""Устанавливает права администратора по умолчанию"""
rights = rights or settings.rights
try:
current_rights = await bots.get_my_default_administrator_rights()
if current_rights == rights:
logger.debug("Права администратора уже установлены", log_type='BOT_SETUP')
return False
await bots.set_my_default_administrator_rights(rights)
logger.success("Права администратора обновлены", log_type='BOT_SETUP')
return True
except Exception as e:
logger.error(f"Ошибка установки прав администратора: {e}", log_type='BOT_SETUP')
return False
@classmethod
def print(cls, to_console: bool = True, to_file: bool = True) -> str:
"""
Красиво форматирует и выводит информацию о боте.
:param to_console: Вывести в консоль
:param to_file: Записать в файлы
:return: Отформатированная строка
"""
# Формирование блоков информации
header = f"╔═══════════════════════════════════════════════════════════╗"
title = f"║ 🤖 PRIMOGUARD BOT - ИНФОРМАЦИЯ О ЗАПУСКЕ ║"
separator = f"╠═══════════════════════════════════════════════════════════╣"
footer = f"╚═══════════════════════════════════════════════════════════╝"
lines = [
header,
title,
separator,
f"║ ⏰ Время запуска: {cls.started_at.strftime('%d.%m.%Y %H:%M:%S')}",
f"",
f"║ 📋 ОСНОВНАЯ ИНФОРМАЦИЯ:",
f"║ • Имя: {cls.first_name} {cls.last_name or ''}".ljust(60) + "",
f"║ • Username: @{cls.username}".ljust(60) + "",
f"║ • ID: {cls.id}".ljust(60) + "",
f"",
f"║ ⚙️ ВОЗМОЖНОСТИ БОТА:",
f"║ • Вступать в группы: {'' if cls.can_join_groups else ''}".ljust(60) + "",
f"║ • Читать все сообщения: {'' if cls.can_read_all_group_messages else ''}".ljust(60) + "",
f"║ • Инлайн-запросы: {'' if cls.supports_inline_queries else ''}".ljust(60) + "",
f"║ • Бизнес-аккаунты: {'' if cls.can_connect_to_business else ''}".ljust(60) + "",
f"║ • Веб-приложение: {'' if cls.has_main_web_app else ''}".ljust(60) + "",
f"║ • Меню вложений: {'' if cls.added_to_attachment_menu else ''}".ljust(60) + "",
f"",
f"║ 🔧 НАСТРОЙКИ:",
f"║ • Префикс команд: {cls.prefix}".ljust(60) + "",
f"║ • Режим: {'Webhook' if settings.WEBHOOK else 'Polling'}".ljust(60) + "",
footer
]
output = '\n'.join(lines)
# Вывод в консоль с цветом
if to_console and settings.START_INFO_CONSOLE:
colored_output = f"\033[96m{output}\033[0m" # Cyan цвет
print(colored_output)
# Запись в файлы
if to_file and settings.START_INFO_TO_FILE:
try:
settings.LOG_DIR.mkdir(parents=True, exist_ok=True)
# Полная информация в bot_info.log
info_file = settings.LOG_DIR / 'bot_info.log'
with open(info_file, 'w', encoding='utf-8') as f:
f.write(output)
# Краткая запись в историю запусков
start_file = settings.LOG_DIR / 'bot_starts.log'
with open(start_file, 'a', encoding='utf-8') as f:
start_entry = f"{cls.started_at.strftime('%d.%m.%Y %H:%M:%S')} | @{cls.username} | Mode: {'Webhook' if settings.WEBHOOK else 'Polling'}\n"
f.write(start_entry)
logger.debug(f"Информация о боте записана в {info_file}", log_type='BOT_INFO')
except Exception as e:
logger.error(f"Ошибка записи информации в файл: {e}", log_type='BOT_INFO')
return output
@classmethod
async def setup(
cls,
bots: Bot = bot,
perm: bool = None,
setup_webhook: bool = True
) -> None:
"""
Выполняет полную настройку бота.
Args:
bots: Объект бота для управления
perm: Разрешение на изменения (если None, берется из настроек)
setup_webhook: Устанавливать ли webhook (по умолчанию True)
"""
perm = perm if perm is not None else settings.BOT_EDIT
logger.info("🚀 Процесс запуска бота!", log_type='START')
# Настройка вебхука (только если разрешено)
if setup_webhook:
await cls.webhook(bots=bots)
# Получение информации
await cls.info(bots=bots)
# Обновление профиля (если разрешено)
if perm:
logger.info("Начало настройки профиля бота...", log_type='BOT_SETUP')
results = {
'name': await cls.set_name(bots=bots),
'description': await cls.set_description(bots=bots),
'short_description': await cls.set_short_description(bots=bots),
'admin_rights': await cls.set_administrator_rights(bots=bots)
}
changed_count = sum(results.values())
logger.info(
f"Настройка завершена. Изменено параметров: {changed_count}/4",
log_type='BOT_SETUP'
)
else:
logger.warning(
"⚠️ Изменение настроек бота отключено (BOT_EDIT=False)",
log_type='BOT_SETUP'
)
# Вывод красивой информации
cls.print()