feat(bot): proactive cookie health-check + simpler user-facing errors
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 51s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 51s
Add /cookies/check to youtube-downloader and instagram-downloader, polled every 30 min from bot.py; alerts the admin bot on healthy->broken transitions (with a 24h re-reminder cap while still broken) and on recovery. Also stop leaking raw exception text to end users - they now see a plain, friendly English message while the admin bot still gets full technical detail via notify_admin_error.
This commit is contained in:
parent
be1acaaf2b
commit
5092d882e5
3 changed files with 166 additions and 4 deletions
109
bot.py
109
bot.py
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
import sqlite3
|
||||
|
|
@ -109,7 +110,7 @@ TEXTS = {
|
|||
'downloading': "⬇️ Скачиваю видео...",
|
||||
'sending': "📤 Отправляю видео...",
|
||||
'caption': "Видео скачано с @{bot_username}",
|
||||
'error': "❌ Произошла ошибка при обработке видео:\n{error}",
|
||||
'error': "❌ Something went wrong while processing your video. Please try again in a bit.",
|
||||
'error_unknown_source': "Пардон, не умеем работать с этим источником",
|
||||
'error_vk_not_video': "❌ Это ссылка VK, но не на видео. Пришлите ссылку вида vk.com/video... или vk.com/clip...",
|
||||
'error_file_too_large': "❌ Видео слишком большое ({size_mb:.1f} МБ, max = 50)",
|
||||
|
|
@ -167,7 +168,7 @@ TEXTS = {
|
|||
'downloading': "⬇️ Downloading video...",
|
||||
'sending': "📤 Sending video...",
|
||||
'caption': "Video downloaded via @{bot_username}",
|
||||
'error': "❌ Error processing video:\n{error}",
|
||||
'error': "❌ Something went wrong while processing your video. Please try again in a bit.",
|
||||
'error_unknown_source': "Sorry, this source is not supported",
|
||||
'error_vk_not_video': "❌ This is a VK link, but not a video. Send a vk.com/video... or vk.com/clip... link.",
|
||||
'error_file_too_large': "❌ Video is too large ({size_mb:.1f} MB, max = 50)",
|
||||
|
|
@ -597,6 +598,94 @@ async def notify_admin_error(url: str, error_text: str, from_user=None):
|
|||
logger.error(f"Ошибка при отправке уведомления об ошибке админ боту: {e}")
|
||||
|
||||
|
||||
COOKIE_STATUS_FILE = DATA_DIR / 'cookie_health_state.json'
|
||||
COOKIE_CHECK_INTERVAL = 30 * 60 # 30 минут — дешёвая read-only проверка
|
||||
COOKIE_REMINDER_INTERVAL = 24 * 3600 # не чаще раза в сутки повторное напоминание, пока всё ещё сломано
|
||||
|
||||
|
||||
def _load_cookie_state() -> dict:
|
||||
try:
|
||||
return json.loads(COOKIE_STATUS_FILE.read_text())
|
||||
except Exception:
|
||||
return {"youtube": "healthy", "instagram": "healthy", "last_reminder_ts": {}}
|
||||
|
||||
|
||||
def _save_cookie_state(state: dict):
|
||||
try:
|
||||
COOKIE_STATUS_FILE.write_text(json.dumps(state))
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось сохранить состояние cookie-health: {e}")
|
||||
|
||||
|
||||
async def notify_admin_cookie_alert(service: str, healthy: bool, detail: str):
|
||||
"""Проактивное уведомление о состоянии cookies (health-check), не связано с ошибкой конкретного пользователя"""
|
||||
if not ADMIN_BOT_TOKEN:
|
||||
return
|
||||
admin_chat_id = get_admin_chat_id()
|
||||
if not admin_chat_id:
|
||||
return
|
||||
|
||||
try:
|
||||
request = HTTPXRequest(
|
||||
read_timeout=30,
|
||||
write_timeout=30,
|
||||
connect_timeout=15,
|
||||
pool_timeout=15
|
||||
)
|
||||
admin_bot = Bot(token=ADMIN_BOT_TOKEN, request=request)
|
||||
|
||||
if healthy:
|
||||
text = f"✅ Cookies восстановлены\n\n🍪 Сервис: {service}\nCookies снова рабочие."
|
||||
else:
|
||||
text = f"🍪⚠️ ПРОАКТИВНАЯ ПРОВЕРКА COOKIES\n\n🍪 Сервис: {service}\n❗ {detail}"
|
||||
if len(text) > 4000:
|
||||
text = text[:4000] + "…"
|
||||
|
||||
await admin_bot.send_message(chat_id=admin_chat_id, text=text)
|
||||
logger.info(f"Уведомление о состоянии cookies ({service}) отправлено админ боту")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления о cookies админ боту: {e}")
|
||||
|
||||
|
||||
async def check_cookies_health():
|
||||
"""Периодическая проверка здоровья cookies YouTube и Instagram"""
|
||||
state = _load_cookie_state()
|
||||
checks = [
|
||||
('youtube', f"{YOUTUBE_DOWNLOADER_URL}/cookies/check"),
|
||||
('instagram', f"{INSTAGRAM_DOWNLOADER_URL}/cookies/check"),
|
||||
]
|
||||
for service, endpoint in checks:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=FORMATS_TIMEOUT) as client:
|
||||
response = await client.post(endpoint)
|
||||
data = response.json()
|
||||
except Exception as e:
|
||||
logger.warning(f"Проверка cookies {service} не удалась (сервис недоступен?): {e}")
|
||||
continue # не меняем состояние при сетевой ошибке — только при чистом ответе cookies_valid:false
|
||||
|
||||
if data.get('cookies_valid') is None:
|
||||
continue # куки для этого сервиса не настроены — нечего проверять
|
||||
|
||||
currently_healthy = bool(data.get('cookies_valid'))
|
||||
was_healthy = state.get(service, 'healthy') == 'healthy'
|
||||
|
||||
if not currently_healthy and was_healthy:
|
||||
state[service] = 'broken'
|
||||
state.setdefault('last_reminder_ts', {})[service] = time.time()
|
||||
await notify_admin_cookie_alert(service, healthy=False, detail=data.get('detail', ''))
|
||||
elif not currently_healthy and not was_healthy:
|
||||
last = state.get('last_reminder_ts', {}).get(service, 0)
|
||||
if time.time() - last >= COOKIE_REMINDER_INTERVAL:
|
||||
state.setdefault('last_reminder_ts', {})[service] = time.time()
|
||||
await notify_admin_cookie_alert(service, healthy=False, detail=data.get('detail', ''))
|
||||
elif currently_healthy and not was_healthy:
|
||||
state[service] = 'healthy'
|
||||
await notify_admin_cookie_alert(service, healthy=True, detail='')
|
||||
|
||||
_save_cookie_state(state)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# СИСТЕМА ОЧЕРЕДЕЙ
|
||||
# ============================================================================
|
||||
|
|
@ -731,7 +820,7 @@ async def process_queue_item(item: QueueItem):
|
|||
|
||||
await notify_admin_error(item.url, str(e), item.original_message.from_user)
|
||||
|
||||
error_msg = get_text(item.locale, 'error', error=str(e))
|
||||
error_msg = get_text(item.locale, 'error')
|
||||
try:
|
||||
await item.status_message.edit_text(error_msg)
|
||||
except:
|
||||
|
|
@ -1447,7 +1536,19 @@ def main():
|
|||
|
||||
asyncio.create_task(periodic_cleanup())
|
||||
logger.info("Фоновая задача периодической очистки файлов запущена")
|
||||
|
||||
|
||||
# Проактивная проверка здоровья cookies (YouTube, Instagram)
|
||||
async def periodic_cookie_check():
|
||||
while True:
|
||||
await asyncio.sleep(COOKIE_CHECK_INTERVAL)
|
||||
try:
|
||||
await check_cookies_health()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при проверке здоровья cookies: {e}")
|
||||
|
||||
asyncio.create_task(periodic_cookie_check())
|
||||
logger.info("Фоновая задача проверки здоровья cookies запущена")
|
||||
|
||||
application.post_init = post_init
|
||||
|
||||
# Запускаем бота
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue