feat(admin_bot): показывать состояние загрузчиков в /stat
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 47s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 47s
Раньше о поломке сервиса админ узнавал только из алерта health-check или из ошибки живого пользователя. Теперь /stat отдаёт состояние всех пяти загрузчиков по запросу. У YouTube и Instagram дёргается /cookies/check, поэтому статусы различают причину: работает / cookies протухли / не извлекает видео (не cookies) / недоступен. Остальные три проверяются через /health. Проверка ходит в реальные YouTube и Instagram и занимает секунды, поэтому статистика отправляется сразу, а блок состояния дозаполняется правкой сообщения. Все сервисы опрашиваются параллельно — последовательно набежало бы под полминуты. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d597a5e1c5
commit
b5f0670754
1 changed files with 69 additions and 1 deletions
70
admin_bot.py
70
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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue