улучшенния в коде и исправление ошибок
This commit is contained in:
18
.env_example
18
.env_example
@@ -244,10 +244,16 @@ POSTS_DIR=posts
|
|||||||
# 📌 ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ
|
# 📌 ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
# Добавьте сюда свои кастомные переменные по необходимости
|
# === BACKGROUND TASKS ===
|
||||||
# Например:
|
# Интервал очистки временных банвордов (часы)
|
||||||
|
CLEANUP_INTERVAL=1
|
||||||
|
|
||||||
|
# Интервал backup базы данных (часы)
|
||||||
|
BACKUP_INTERVAL=24
|
||||||
|
|
||||||
|
# Количество хранимых backup'ов
|
||||||
|
KEEP_BACKUPS=7
|
||||||
|
|
||||||
|
# Возраст старой статистики для удаления (дни)
|
||||||
|
STATS_MAX_AGE_DAYS=30
|
||||||
|
|
||||||
# DATABASE_URL=postgresql://user:password@localhost/dbname
|
|
||||||
# REDIS_URL=redis://localhost:6379
|
|
||||||
# MAX_WARNINGS=3
|
|
||||||
# BAN_DURATION=86400
|
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ async def start_cmd(update: Message | CallbackQuery) -> None:
|
|||||||
help_text += (
|
help_text += (
|
||||||
"🔇 <b>Режим тишины:</b>\n"
|
"🔇 <b>Режим тишины:</b>\n"
|
||||||
"/silence <code>минуты</code> — удалять ВСЕ сообщения\n"
|
"/silence <code>минуты</code> — удалять ВСЕ сообщения\n"
|
||||||
"/unsilence — отключить режим тишины\n\n"
|
"/unsilence — отключить режим тишины\n"
|
||||||
|
"/report — отправить репорт\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
help_text += (
|
help_text += (
|
||||||
|
|||||||
4
bot/tasks/__init__.py
Normal file
4
bot/tasks/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
"""
|
||||||
|
Модуль фоновых задач для бота
|
||||||
|
"""
|
||||||
|
from .cleanup import *
|
||||||
263
bot/tasks/cleanup.py
Normal file
263
bot/tasks/cleanup.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
"""
|
||||||
|
Фоновые задачи для автоматической очистки и обслуживания бота
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from database import get_manager
|
||||||
|
from middleware.loggers import logger
|
||||||
|
from configs import settings
|
||||||
|
|
||||||
|
__all__ = ('cleanup_expired_banwords', 'cleanup_spam_stats', 'auto_backup_database', 'start_background_tasks', 'check_system_health')
|
||||||
|
|
||||||
|
|
||||||
|
async def cleanup_expired_banwords() -> None:
|
||||||
|
"""
|
||||||
|
Периодически удаляет истёкшие временные банворды.
|
||||||
|
|
||||||
|
Запускается каждый час и проверяет все временные слова.
|
||||||
|
Удаляет те, у которых истёк срок действия.
|
||||||
|
"""
|
||||||
|
logger.info("🔄 Запущена задача автоочистки временных банвордов", log_type="CLEANUP")
|
||||||
|
|
||||||
|
manager = get_manager()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Ждём 1 час между проверками
|
||||||
|
await asyncio.sleep(3600)
|
||||||
|
|
||||||
|
# Выполняем очистку
|
||||||
|
deleted = await manager.cleanup_expired_temp_words()
|
||||||
|
|
||||||
|
if deleted > 0:
|
||||||
|
logger.info(
|
||||||
|
f"✅ Очищено {deleted} истёкших временных банвордов",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
"✓ Проверка временных банвордов: истёкших не найдено",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка в задаче очистки банвордов: {e}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
# При ошибке ждём 5 минут перед следующей попыткой
|
||||||
|
await asyncio.sleep(300)
|
||||||
|
|
||||||
|
|
||||||
|
async def cleanup_spam_stats(max_age_days: int = 30) -> None:
|
||||||
|
"""
|
||||||
|
Очищает старую статистику антиспама.
|
||||||
|
|
||||||
|
Запускается каждые 24 часа и удаляет записи старше max_age_days дней.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_age_days: Возраст записей для удаления (по умолчанию 30 дней)
|
||||||
|
"""
|
||||||
|
logger.info(
|
||||||
|
f"🔄 Запущена задача очистки статистики (возраст > {max_age_days} дней)",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
from bot.middlewares.spam_mdw import spam_stats
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Ждём 24 часа между проверками
|
||||||
|
await asyncio.sleep(86400)
|
||||||
|
|
||||||
|
# Очищаем старую статистику
|
||||||
|
max_age_seconds = max_age_days * 86400
|
||||||
|
deleted = spam_stats.cleanup(max_age=max_age_seconds)
|
||||||
|
|
||||||
|
if deleted > 0:
|
||||||
|
logger.info(
|
||||||
|
f"✅ Очищена статистика: {deleted} неактивных пользователей",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
"✓ Проверка статистики: старых записей не найдено",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка в задаче очистки статистики: {e}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
await asyncio.sleep(3600) # При ошибке ждём 1 час
|
||||||
|
|
||||||
|
|
||||||
|
async def auto_backup_database(backup_interval_hours: int = 24, keep_backups: int = 7) -> None:
|
||||||
|
"""
|
||||||
|
Автоматически создаёт резервные копии базы данных.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
backup_interval_hours: Интервал между бэкапами (по умолчанию 24 часа)
|
||||||
|
keep_backups: Количество хранимых копий (по умолчанию 7)
|
||||||
|
"""
|
||||||
|
logger.info(
|
||||||
|
f"🔄 Запущена задача автобэкапа (каждые {backup_interval_hours}ч, хранить {keep_backups})",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Ждём указанное время
|
||||||
|
await asyncio.sleep(backup_interval_hours * 3600)
|
||||||
|
|
||||||
|
# Путь к базе данных
|
||||||
|
db_path = Path(settings.DATABASE_PATH)
|
||||||
|
|
||||||
|
if not db_path.exists():
|
||||||
|
logger.warning(
|
||||||
|
f"⚠️ База данных не найдена: {db_path}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Создаём папку для бэкапов
|
||||||
|
backup_dir = Path("backups")
|
||||||
|
backup_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Имя файла бэкапа с датой и временем
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
backup_path = backup_dir / f"banwords_backup_{timestamp}.db"
|
||||||
|
|
||||||
|
# Копируем базу данных
|
||||||
|
shutil.copy2(db_path, backup_path)
|
||||||
|
|
||||||
|
# Получаем размер файла
|
||||||
|
backup_size = backup_path.stat().st_size / 1024 # KB
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"✅ Создан backup: {backup_path.name} ({backup_size:.1f} KB)",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Удаляем старые бэкапы (оставляем только последние N)
|
||||||
|
backups = sorted(backup_dir.glob("banwords_backup_*.db"))
|
||||||
|
|
||||||
|
if len(backups) > keep_backups:
|
||||||
|
old_backups = backups[:-keep_backups]
|
||||||
|
|
||||||
|
for old_backup in old_backups:
|
||||||
|
old_backup.unlink()
|
||||||
|
logger.debug(
|
||||||
|
f"🗑️ Удалён старый backup: {old_backup.name}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"✓ Удалено старых бэкапов: {len(old_backups)}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка создания backup: {e}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
await asyncio.sleep(3600) # При ошибке ждём 1 час
|
||||||
|
|
||||||
|
|
||||||
|
async def check_system_health() -> None:
|
||||||
|
"""
|
||||||
|
Мониторинг здоровья системы.
|
||||||
|
|
||||||
|
Проверяет каждые 5 минут:
|
||||||
|
- Размер базы данных
|
||||||
|
- Количество записей в БД
|
||||||
|
- Использование памяти (статистика антиспама)
|
||||||
|
"""
|
||||||
|
logger.info("🔄 Запущена задача мониторинга системы", log_type="CLEANUP")
|
||||||
|
|
||||||
|
manager = get_manager()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(300) # 5 минут
|
||||||
|
|
||||||
|
# Проверяем размер БД
|
||||||
|
db_path = Path(settings.DATABASE_PATH)
|
||||||
|
if db_path.exists():
|
||||||
|
db_size_mb = db_path.stat().st_size / (1024 * 1024)
|
||||||
|
|
||||||
|
if db_size_mb > 100: # Если больше 100 MB
|
||||||
|
logger.warning(
|
||||||
|
f"⚠️ Большой размер БД: {db_size_mb:.2f} MB",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Проверяем количество временных слов
|
||||||
|
stats = await manager.get_stats()
|
||||||
|
temp_count = stats.get('temp_total', 0)
|
||||||
|
|
||||||
|
if temp_count > 100: # Если больше 100 временных слов
|
||||||
|
logger.warning(
|
||||||
|
f"⚠️ Много временных банвордов: {temp_count}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Проверяем статистику антиспама
|
||||||
|
from bot.middlewares.spam_mdw import spam_stats
|
||||||
|
spam_summary = spam_stats.get_stats_summary()
|
||||||
|
|
||||||
|
active_blocks = spam_summary.get('active_blocks', 0)
|
||||||
|
if active_blocks > 10:
|
||||||
|
logger.warning(
|
||||||
|
f"⚠️ Много активных блокировок: {active_blocks}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка в задаче мониторинга: {e}",
|
||||||
|
log_type="CLEANUP"
|
||||||
|
)
|
||||||
|
await asyncio.sleep(600) # При ошибке ждём 10 минут
|
||||||
|
|
||||||
|
|
||||||
|
def start_background_tasks() -> list[asyncio.Task]:
|
||||||
|
"""
|
||||||
|
Запускает все фоновые задачи.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[asyncio.Task]: Список запущенных задач
|
||||||
|
"""
|
||||||
|
tasks = []
|
||||||
|
|
||||||
|
# 1. Автоочистка временных банвордов (каждый час)
|
||||||
|
task1 = asyncio.create_task(cleanup_expired_banwords())
|
||||||
|
tasks.append(task1)
|
||||||
|
logger.info("✅ Задача 'cleanup_expired_banwords' запущена", log_type="STARTUP")
|
||||||
|
|
||||||
|
# 2. Очистка старой статистики (каждые 24 часа)
|
||||||
|
task2 = asyncio.create_task(cleanup_spam_stats(max_age_days=30))
|
||||||
|
tasks.append(task2)
|
||||||
|
logger.info("✅ Задача 'cleanup_spam_stats' запущена", log_type="STARTUP")
|
||||||
|
|
||||||
|
# 3. Автобэкап базы данных (каждые 24 часа, хранить 7 копий)
|
||||||
|
task3 = asyncio.create_task(auto_backup_database(backup_interval_hours=24, keep_backups=7))
|
||||||
|
tasks.append(task3)
|
||||||
|
logger.info("✅ Задача 'auto_backup_database' запущена", log_type="STARTUP")
|
||||||
|
|
||||||
|
# 4. Мониторинг здоровья системы (каждые 5 минут)
|
||||||
|
task4 = asyncio.create_task(check_system_health())
|
||||||
|
tasks.append(task4)
|
||||||
|
logger.info("✅ Задача 'check_system_health' запущена", log_type="STARTUP")
|
||||||
|
|
||||||
|
logger.success(
|
||||||
|
f"🚀 Все фоновые задачи запущены: {len(tasks)} шт.",
|
||||||
|
log_type="STARTUP"
|
||||||
|
)
|
||||||
|
|
||||||
|
return tasks
|
||||||
@@ -106,7 +106,7 @@ class _Settings(BaseSettings):
|
|||||||
raise ValueError("PREFIX должен содержать хотя бы один символ")
|
raise ValueError("PREFIX должен содержать хотя бы один символ")
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
@field_validator('LOG_DIR', 'LOG_FILE_INFO', 'POSTS_DIR', mode='before')
|
@field_validator('LOG_DIR', 'LOG_FILE_INFO', mode='before')
|
||||||
def validate_paths(cls, v: Any) -> Path:
|
def validate_paths(cls, v: Any) -> Path:
|
||||||
return Path(v) if isinstance(v, str) else v
|
return Path(v) if isinstance(v, str) else v
|
||||||
|
|
||||||
@@ -149,10 +149,6 @@ class _Settings(BaseSettings):
|
|||||||
if self.LOG_FILE:
|
if self.LOG_FILE:
|
||||||
self.LOG_DIR.mkdir(parents=True, exist_ok=True)
|
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
|
return self
|
||||||
|
|
||||||
@model_validator(mode='after')
|
@model_validator(mode='after')
|
||||||
@@ -212,7 +208,6 @@ settings = _Settings()
|
|||||||
BOT_TOKEN = settings.active_bot_token
|
BOT_TOKEN = settings.active_bot_token
|
||||||
ADMIN_CHAT_ID = settings.ADMIN_CHAT_ID
|
ADMIN_CHAT_ID = settings.ADMIN_CHAT_ID
|
||||||
SUPER_ADMIN_IDS = settings.super_admin_ids
|
SUPER_ADMIN_IDS = settings.super_admin_ids
|
||||||
WORDS_FILE = settings.WORDS_FILE
|
|
||||||
|
|
||||||
# Экспорт
|
# Экспорт
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@@ -220,5 +215,4 @@ __all__ = (
|
|||||||
'BOT_TOKEN',
|
'BOT_TOKEN',
|
||||||
'ADMIN_CHAT_ID',
|
'ADMIN_CHAT_ID',
|
||||||
'SUPER_ADMIN_IDS',
|
'SUPER_ADMIN_IDS',
|
||||||
'WORDS_FILE',
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -520,6 +520,65 @@ class BanWordsManager:
|
|||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def cleanup_expired_temp_words(self) -> int:
|
||||||
|
"""
|
||||||
|
Удаляет истёкшие временные банворды.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Количество удалённых слов
|
||||||
|
"""
|
||||||
|
async with self.session_maker() as session:
|
||||||
|
try:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# Ищем истёкшие временные слова
|
||||||
|
query = select(TempBanWord).where(
|
||||||
|
TempBanWord.expires_at < now
|
||||||
|
)
|
||||||
|
result = await session.execute(query)
|
||||||
|
expired_words = result.scalars().all()
|
||||||
|
|
||||||
|
if not expired_words:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Собираем информацию для логирования
|
||||||
|
expired_info = []
|
||||||
|
for word in expired_words:
|
||||||
|
expired_info.append({
|
||||||
|
'word': word.word,
|
||||||
|
'type': word.word_type.value,
|
||||||
|
'expires_at': word.expires_at
|
||||||
|
})
|
||||||
|
await session.delete(word)
|
||||||
|
|
||||||
|
# Сохраняем изменения
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
# Обновляем кеш
|
||||||
|
await self._reload_cache()
|
||||||
|
|
||||||
|
# Логируем подробности
|
||||||
|
logger.info(
|
||||||
|
f"Удалено {len(expired_words)} истёкших временных банвордов",
|
||||||
|
log_type="DATABASE"
|
||||||
|
)
|
||||||
|
|
||||||
|
for info in expired_info:
|
||||||
|
logger.debug(
|
||||||
|
f" └─ {info['type']}: '{info['word']}' (истёк: {info['expires_at']})",
|
||||||
|
log_type="DATABASE"
|
||||||
|
)
|
||||||
|
|
||||||
|
return len(expired_words)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Ошибка удаления истёкших временных слов: {e}",
|
||||||
|
log_type="DATABASE"
|
||||||
|
)
|
||||||
|
await session.rollback()
|
||||||
|
return 0
|
||||||
|
|
||||||
async def get_total_spam_count(self) -> int:
|
async def get_total_spam_count(self) -> int:
|
||||||
"""
|
"""
|
||||||
Получает общее количество удалённых сообщений.
|
Получает общее количество удалённых сообщений.
|
||||||
|
|||||||
126
main.py
126
main.py
@@ -2,6 +2,8 @@
|
|||||||
Точка входа PrimoGuard Bot
|
Точка входа PrimoGuard Bot
|
||||||
"""
|
"""
|
||||||
from asyncio import run
|
from asyncio import run
|
||||||
|
import asyncio
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from configs import settings
|
from configs import settings
|
||||||
from bot import bot, dp, BotInfo, WebhookManager, setup_middlewares, router
|
from bot import bot, dp, BotInfo, WebhookManager, setup_middlewares, router
|
||||||
@@ -11,6 +13,35 @@ from middleware.loggers import logger
|
|||||||
__all__ = ("main",)
|
__all__ = ("main",)
|
||||||
|
|
||||||
|
|
||||||
|
def _start_background_tasks_safe() -> List[asyncio.Task]:
|
||||||
|
"""
|
||||||
|
Безопасный запуск фоновых задач с обработкой ошибок.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[asyncio.Task]: Список запущенных задач (пустой список если модуль не найден)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from bot.tasks import start_background_tasks
|
||||||
|
tasks = start_background_tasks()
|
||||||
|
logger.info(
|
||||||
|
f"🚀 Запущено {len(tasks)} фоновых задач",
|
||||||
|
log_type="STARTUP"
|
||||||
|
)
|
||||||
|
return tasks
|
||||||
|
except ImportError:
|
||||||
|
logger.warning(
|
||||||
|
"⚠️ Модуль 'bot.tasks' не найден, фоновые задачи не запущены",
|
||||||
|
log_type="STARTUP"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка запуска фоновых задач: {e}",
|
||||||
|
log_type="STARTUP"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def setup_services(setup_webhook: bool = True) -> str:
|
async def setup_services(setup_webhook: bool = True) -> str:
|
||||||
"""
|
"""
|
||||||
Инициализация всех сервисов: БД и бот.
|
Инициализация всех сервисов: БД и бот.
|
||||||
@@ -55,7 +86,11 @@ async def on_startup(app) -> None:
|
|||||||
# 1. Инициализируем всё БЕЗ webhook
|
# 1. Инициализируем всё БЕЗ webhook
|
||||||
username = await setup_services(setup_webhook=False)
|
username = await setup_services(setup_webhook=False)
|
||||||
|
|
||||||
# 2. ТЕПЕРЬ устанавливаем webhook (когда всё готово)
|
# 2. Запускаем фоновые задачи
|
||||||
|
background_tasks = _start_background_tasks_safe()
|
||||||
|
app['background_tasks'] = background_tasks
|
||||||
|
|
||||||
|
# 3. ТЕПЕРЬ устанавливаем webhook (когда всё готово)
|
||||||
webhook = WebhookManager(bot, dp)
|
webhook = WebhookManager(bot, dp)
|
||||||
|
|
||||||
if settings.WEBHOOK_URL:
|
if settings.WEBHOOK_URL:
|
||||||
@@ -87,10 +122,40 @@ async def on_shutdown(app) -> None:
|
|||||||
logger.info("👋 Остановка бота...", log_type="SHUTDOWN")
|
logger.info("👋 Остановка бота...", log_type="SHUTDOWN")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Отменяем фоновые задачи
|
||||||
|
if 'background_tasks' in app and app['background_tasks']:
|
||||||
|
tasks = app['background_tasks']
|
||||||
|
logger.info(
|
||||||
|
f"⏸️ Остановка {len(tasks)} фоновых задач...",
|
||||||
|
log_type="SHUTDOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
if not task.done():
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
|
# Ждём завершения с таймаутом 5 секунд
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(
|
||||||
|
asyncio.gather(*tasks, return_exceptions=True),
|
||||||
|
timeout=5.0
|
||||||
|
)
|
||||||
|
logger.info("✅ Фоновые задачи остановлены", log_type="SHUTDOWN")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning(
|
||||||
|
"⚠️ Таймаут остановки фоновых задач (5 сек)",
|
||||||
|
log_type="SHUTDOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Закрываем соединения
|
||||||
|
logger.info("📊 Закрытие базы данных...", log_type="SHUTDOWN")
|
||||||
await get_manager().close()
|
await get_manager().close()
|
||||||
|
|
||||||
|
logger.info("🤖 Закрытие сессии бота...", log_type="SHUTDOWN")
|
||||||
await bot.session.close()
|
await bot.session.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при закрытии: {e}", log_type="SHUTDOWN")
|
logger.error(f"❌ Ошибка при закрытии: {e}", log_type="SHUTDOWN")
|
||||||
|
|
||||||
logger.success("✅ Бот остановлен", log_type="SHUTDOWN")
|
logger.success("✅ Бот остановлен", log_type="SHUTDOWN")
|
||||||
|
|
||||||
@@ -117,10 +182,16 @@ async def start_polling() -> None:
|
|||||||
"""Запуск в режиме Polling (асинхронный)."""
|
"""Запуск в режиме Polling (асинхронный)."""
|
||||||
logger.setup()
|
logger.setup()
|
||||||
|
|
||||||
|
background_tasks: List[asyncio.Task] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 1. Инициализируем сервисы
|
||||||
username = await setup_services(setup_webhook=False)
|
username = await setup_services(setup_webhook=False)
|
||||||
|
|
||||||
# Удаляем webhook для polling режима
|
# 2. Запускаем фоновые задачи
|
||||||
|
background_tasks = _start_background_tasks_safe()
|
||||||
|
|
||||||
|
# 3. Удаляем webhook для polling режима
|
||||||
webhook = WebhookManager(bot, dp)
|
webhook = WebhookManager(bot, dp)
|
||||||
await webhook.delete(drop_pending_updates=True)
|
await webhook.delete(drop_pending_updates=True)
|
||||||
|
|
||||||
@@ -129,9 +200,12 @@ async def start_polling() -> None:
|
|||||||
log_type="STARTUP"
|
log_type="STARTUP"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Запускаем polling
|
# 4. Запускаем polling
|
||||||
await dp.start_polling(bot, drop_pending_updates=True)
|
await dp.start_polling(bot, drop_pending_updates=True)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("⚠️ Получен сигнал остановки (Ctrl+C)", log_type="MAIN")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(
|
logger.critical(
|
||||||
f"🔥 Критическая ошибка: {e}",
|
f"🔥 Критическая ошибка: {e}",
|
||||||
@@ -140,11 +214,47 @@ async def start_polling() -> None:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
logger.info("🧹 Очистка ресурсов...", log_type="SHUTDOWN")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.session.close()
|
# Отменяем фоновые задачи
|
||||||
|
if background_tasks:
|
||||||
|
logger.info(
|
||||||
|
f"⏸️ Остановка {len(background_tasks)} фоновых задач...",
|
||||||
|
log_type="SHUTDOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
for task in background_tasks:
|
||||||
|
if not task.done():
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
|
# Ждём завершения с таймаутом 5 секунд
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(
|
||||||
|
asyncio.gather(*background_tasks, return_exceptions=True),
|
||||||
|
timeout=5.0
|
||||||
|
)
|
||||||
|
logger.info("✅ Фоновые задачи остановлены", log_type="SHUTDOWN")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning(
|
||||||
|
"⚠️ Таймаут остановки фоновых задач (5 сек)",
|
||||||
|
log_type="SHUTDOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Закрываем соединения
|
||||||
|
logger.info("📊 Закрытие базы данных...", log_type="SHUTDOWN")
|
||||||
await get_manager().close()
|
await get_manager().close()
|
||||||
except:
|
|
||||||
pass
|
logger.info("🤖 Закрытие сессии бота...", log_type="SHUTDOWN")
|
||||||
|
await bot.session.close()
|
||||||
|
|
||||||
|
logger.success("✅ Бот остановлен", log_type="SHUTDOWN")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка при очистке ресурсов: {e}",
|
||||||
|
log_type="SHUTDOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -152,9 +262,11 @@ def main() -> None:
|
|||||||
try:
|
try:
|
||||||
if settings.WEBHOOK:
|
if settings.WEBHOOK:
|
||||||
# ========== WEBHOOK РЕЖИМ ==========
|
# ========== WEBHOOK РЕЖИМ ==========
|
||||||
|
logger.info("🔧 Режим: Webhook", log_type="MAIN")
|
||||||
start_webhook()
|
start_webhook()
|
||||||
else:
|
else:
|
||||||
# ========== POLLING РЕЖИМ ==========
|
# ========== POLLING РЕЖИМ ==========
|
||||||
|
logger.info("🔧 Режим: Polling", log_type="MAIN")
|
||||||
run(start_polling())
|
run(start_polling())
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
Reference in New Issue
Block a user