fix(downloaders): Deno runtime, свежий yt-dlp, честный health-check, смоук-тест
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 2m36s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 2m36s
Возрастные YouTube-видео не скачивались: с cookies yt-dlp отбрасывает клиент
android и остаётся web, которому нужно решить n-challenge, а в образе стоял
Node 20 при требуемом минимуме 22 ("JS runtimes: node-20.19.2 (unsupported)").
Ставим Deno — рекомендованный yt-dlp рантайм и один статический бинарник.
Instagram падал с "empty media response" одинаково с cookies и без них —
дело было не в сессии, а в устаревшем экстракторе: слой pip был закеширован
на yt-dlp 2026.06.09. Поднимаем нижнюю границу до 2026.7.4.
/cookies/check помечал проблемой с cookies ЛЮБОЙ сбой, из-за чего на поломку
JS-рантайма прилетел алерт про протухшие cookies и увёл разбор не туда.
Теперь ответ содержит status: ok | cookies_invalid | extraction_failed |
no_cookies, и админ-бот шлёт разные сообщения. Разбор ответа в bot.py
сохраняет совместимость со старым форматом без поля status.
Добавлен smoke_test.py — гоняет реальные ссылки (включая обе регрессии выше)
через запущенные сервисы и печатает таблицу. Запускать после каждой правки.
Схема получения cookies переведена с крона на разовый ручной экспорт:
cookies-cron/ -> cookies/, удалены скрипты с анти-паттерном
`--cookies-from-browser BROWSER --cookies FILE`, который wiki yt-dlp прямо
запрещает и который сам ломал YouTube-сессию ротацией. Ключевые cookies живут
около года, поэтому обновление по расписанию не нужно — триггером служит алерт
health-check. deliver_cookies.sh только доставляет файлы по scp.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5092d882e5
commit
d597a5e1c5
19 changed files with 373 additions and 733 deletions
43
bot.py
43
bot.py
|
|
@ -617,8 +617,13 @@ def _save_cookie_state(state: dict):
|
|||
logger.error(f"Не удалось сохранить состояние cookie-health: {e}")
|
||||
|
||||
|
||||
async def notify_admin_cookie_alert(service: str, healthy: bool, detail: str):
|
||||
"""Проактивное уведомление о состоянии cookies (health-check), не связано с ошибкой конкретного пользователя"""
|
||||
async def notify_admin_cookie_alert(service: str, status: str, detail: str):
|
||||
"""Проактивное уведомление от health-check, не связано с ошибкой конкретного пользователя.
|
||||
|
||||
status: 'ok' | 'cookies_invalid' | 'extraction_failed'
|
||||
Тексты разные намеренно: сбой извлечения (версия yt-dlp, JS-рантайм) не должен
|
||||
выглядеть как протухшие cookies, иначе разбор уходит не туда.
|
||||
"""
|
||||
if not ADMIN_BOT_TOKEN:
|
||||
return
|
||||
admin_chat_id = get_admin_chat_id()
|
||||
|
|
@ -634,15 +639,24 @@ async def notify_admin_cookie_alert(service: str, healthy: bool, detail: str):
|
|||
)
|
||||
admin_bot = Bot(token=ADMIN_BOT_TOKEN, request=request)
|
||||
|
||||
if healthy:
|
||||
text = f"✅ Cookies восстановлены\n\n🍪 Сервис: {service}\nCookies снова рабочие."
|
||||
if status == 'ok':
|
||||
text = f"✅ Снова работает\n\n🍪 Сервис: {service}\nПроверка проходит успешно."
|
||||
elif status == 'cookies_invalid':
|
||||
text = (
|
||||
f"🍪⚠️ COOKIES ПРОТУХЛИ\n\n🍪 Сервис: {service}\n❗ {detail}\n\n"
|
||||
"Обновить по инструкции в cookies/README.md "
|
||||
"(для YouTube — только через приватное окно)."
|
||||
)
|
||||
else:
|
||||
text = f"🍪⚠️ ПРОАКТИВНАЯ ПРОВЕРКА COOKIES\n\n🍪 Сервис: {service}\n❗ {detail}"
|
||||
text = (
|
||||
f"🛠⚠️ СЕРВИС НЕ МОЖЕТ ИЗВЛЕЧЬ ВИДЕО\n\n🍪 Сервис: {service}\n❗ {detail}\n\n"
|
||||
"Это НЕ cookies — проверить версию yt-dlp и логи сервиса."
|
||||
)
|
||||
if len(text) > 4000:
|
||||
text = text[:4000] + "…"
|
||||
|
||||
await admin_bot.send_message(chat_id=admin_chat_id, text=text)
|
||||
logger.info(f"Уведомление о состоянии cookies ({service}) отправлено админ боту")
|
||||
logger.info(f"Уведомление health-check ({service}, {status}) отправлено админ боту")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке уведомления о cookies админ боту: {e}")
|
||||
|
|
@ -664,24 +678,31 @@ async def check_cookies_health():
|
|||
logger.warning(f"Проверка cookies {service} не удалась (сервис недоступен?): {e}")
|
||||
continue # не меняем состояние при сетевой ошибке — только при чистом ответе cookies_valid:false
|
||||
|
||||
if data.get('cookies_valid') is None:
|
||||
# Обратная совместимость: старый ответ без 'status' различал только cookies_valid
|
||||
status = data.get('status')
|
||||
if status is None:
|
||||
valid = data.get('cookies_valid')
|
||||
status = 'no_cookies' if valid is None else ('ok' if valid else 'cookies_invalid')
|
||||
|
||||
if status == 'no_cookies':
|
||||
continue # куки для этого сервиса не настроены — нечего проверять
|
||||
|
||||
currently_healthy = bool(data.get('cookies_valid'))
|
||||
detail = data.get('detail', '')
|
||||
currently_healthy = status == 'ok'
|
||||
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', ''))
|
||||
await notify_admin_cookie_alert(service, status, 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', ''))
|
||||
await notify_admin_cookie_alert(service, status, detail)
|
||||
elif currently_healthy and not was_healthy:
|
||||
state[service] = 'healthy'
|
||||
await notify_admin_cookie_alert(service, healthy=True, detail='')
|
||||
await notify_admin_cookie_alert(service, 'ok', '')
|
||||
|
||||
_save_cookie_state(state)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue