diff --git a/admin_bot.py b/admin_bot.py index 23340fc..e4e6b13 100644 --- a/admin_bot.py +++ b/admin_bot.py @@ -7,6 +7,8 @@ import asyncio import logging import sqlite3 from pathlib import Path + +import httpx from telegram import Bot, Update from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters from telegram.request import HTTPXRequest @@ -31,6 +33,57 @@ DATA_DIR = BASE_DIR / 'data' DB_FILE = DATA_DIR / 'bot.db' ADMIN_CHAT_ID_FILE = DATA_DIR / 'admin_chat_id.txt' +# Адреса загрузчиков — те же дефолты, что и в bot.py (все сервисы в network_mode: host). +# Флаг has_cookies: у YouTube и Instagram есть /cookies/check, у остальных только /health. +DOWNLOADER_SERVICES = [ + ('YouTube', os.getenv('YOUTUBE_DOWNLOADER_URL', 'http://localhost:5557'), True), + ('Instagram', os.getenv('INSTAGRAM_DOWNLOADER_URL', 'http://localhost:5556'), True), + ('VK', os.getenv('VK_DOWNLOADER_URL', 'http://localhost:5555'), False), + ('Yapfiles', os.getenv('YAPFILES_DOWNLOADER_URL', 'http://localhost:5558'), False), + ('TikTok', os.getenv('TIKTOK_DOWNLOADER_URL', 'http://localhost:5559'), False), +] + +# /cookies/check ходит в реальный YouTube/Instagram, это несколько секунд +SERVICE_CHECK_TIMEOUT = httpx.Timeout(connect=5, read=45, write=10, pool=10) + +# Расшифровка status из /cookies/check +COOKIE_STATUS_LABELS = { + 'ok': '✅ работает', + 'cookies_invalid': '🍪 cookies протухли', + 'extraction_failed': '⚠️ не извлекает видео (не cookies)', + 'no_cookies': '➖ без cookies', +} + + +async def check_service_status(name: str, base_url: str, has_cookies: bool) -> str: + """Возвращает строку статуса одного загрузчика для /stat.""" + try: + async with httpx.AsyncClient(timeout=SERVICE_CHECK_TIMEOUT) as client: + if has_cookies: + response = await client.post(f'{base_url}/cookies/check') + if response.status_code != 200: + return f"• {name}: ❌ HTTP {response.status_code}" + status = response.json().get('status', 'unknown') + label = COOKIE_STATUS_LABELS.get(status, f'⚠️ {status}') + return f"• {name}: {label}" + + response = await client.get(f'{base_url}/health') + if response.status_code != 200: + return f"• {name}: ❌ HTTP {response.status_code}" + return f"• {name}: ✅ работает" + except Exception as e: + logger.warning(f"Проверка сервиса {name} не удалась: {e}") + return f"• {name}: ❌ недоступен" + + +async def get_services_status_text() -> str: + """Опрашивает все загрузчики параллельно — последовательно это заняло бы десятки секунд.""" + results = await asyncio.gather(*( + check_service_status(name, url, has_cookies) + for name, url, has_cookies in DOWNLOADER_SERVICES + )) + return "\n".join(results) + def get_total_downloads() -> int: """Возвращает общее количество скачанных видео""" @@ -256,7 +309,22 @@ async def stat_command(update: Update, context: ContextTypes.DEFAULT_TYPE): f"❌ Ошибки по сервисам:\n{error_stats_text}" ) - await update.message.reply_text(stat_message) + # Проверка сервисов ходит в реальные YouTube/Instagram и занимает секунды, + # поэтому сначала отдаём статистику, а статус дозаполняем правкой сообщения + sent = await update.message.reply_text( + f"{stat_message}\n\n🔌 Состояние сервисов:\nпроверяю…" + ) + + try: + services_text = await get_services_status_text() + except Exception as e: + logger.error(f"Не удалось получить состояние сервисов: {e}") + services_text = "❌ не удалось проверить" + + try: + await sent.edit_text(f"{stat_message}\n\n🔌 Состояние сервисов:\n{services_text}") + except Exception as e: + logger.warning(f"Не удалось обновить сообщение статистики: {e}") async def send_messages_command(update: Update, context: ContextTypes.DEFAULT_TYPE):