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
|
|
@ -182,7 +182,7 @@ CREATE TABLE stats (
|
|||
- (YouTube) `./youtube_cookies.txt:/app/youtube_cookies.txt`
|
||||
- (Instagram) `./instagram_cookies.txt:/app/instagram_cookies.txt`
|
||||
|
||||
Cookies монтируются с хоста (не read-only — внешний крон на отдельной машине обновляет файл по `scp`), а не запекаются в образ — это позволяет обновлять их без пересборки/передеплоя сервиса.
|
||||
Cookies монтируются с хоста (не read-only — файл обновляется вручную по `scp` с машины, где есть браузер), а не запекаются в образ — это позволяет обновлять их без пересборки/передеплоя сервиса. Процедура — в `cookies/README.md`.
|
||||
|
||||
## Потоки данных
|
||||
|
||||
|
|
@ -235,7 +235,7 @@ YAPFILES_DOWNLOADER_URL=http://<host2_ip>:5558
|
|||
## Безопасность
|
||||
|
||||
- Токен бота в `.env` (не коммитится)
|
||||
- Cookies YouTube/Instagram закоммичены в репозиторий как fallback внутри образа; актуальные версии приходят на хост кроном и монтируются поверх (см. `README.md#cookies-на-проде`)
|
||||
- Cookies YouTube/Instagram закоммичены в репозиторий как fallback внутри образа; актуальные версии кладутся на хост вручную и монтируются поверх (см. `README.md#cookies-на-проде` и `cookies/README.md`)
|
||||
- Сервисы доступны только по указанным URL
|
||||
- Можно добавить API key между сервисами
|
||||
|
||||
|
|
|
|||
23
README.md
23
README.md
|
|
@ -76,14 +76,9 @@ TIKTOK_DOWNLOADER_URL=http://localhost:5559
|
|||
|
||||
Если планируете скачивать видео с Instagram:
|
||||
|
||||
1. Экспортируйте cookies из браузера (см. `instagram-downloader/INSTAGRAM_COOKIES_INSTRUCTIONS.md`)
|
||||
1. Экспортируйте cookies из браузера расширением для `cookies.txt` — процедура в [`cookies/README.md`](cookies/README.md)
|
||||
2. Сохраните файл как `instagram_cookies.txt` в папке `instagram-downloader/`
|
||||
|
||||
```bash
|
||||
cd instagram-downloader
|
||||
./get_instagram_cookies.sh
|
||||
```
|
||||
|
||||
### 4. Запуск сервисов
|
||||
|
||||
Все 7 сервисов (бот, admin-бот, 5 загрузчиков) собираются и запускаются одной командой из корня проекта — единый `docker-compose.yml` уже содержит build-контексты для всех подпапок:
|
||||
|
|
@ -174,7 +169,21 @@ Content-Type: application/json
|
|||
|
||||
В проде `youtube_cookies.txt`/`instagram_cookies.txt` монтируются в контейнеры с хоста (`docker-compose.prod.yml`), а не запекаются в образ — это позволяет обновлять их без пересборки/передеплоя. `yt-dlp`/`app.py` читают файл с диска при каждом запросе — рестарт контейнера не требуется.
|
||||
|
||||
Куки получает по крону (раз в 20 минут) отдельная машина с графическим окружением и реальным залогиненным браузером (на самом проде это сделать нельзя — там нет GUI/браузера), и разливает результат по `scp` сразу на прод и на тестовый стенд. Скрипты для этого, требования к cron-машине и инструкция по развёртыванию на новой машине — в [`cookies-cron/`](cookies-cron/README.md). Это копия того, что реально крутится на cron-машине; `youtube-downloader/get_youtube_cookies.sh` и `instagram-downloader/get_instagram_cookies.sh` в подпапках сервисов — более старые версии тех же скриптов, с тех пор разошедшиеся.
|
||||
Куки обновляются **вручную и редко** — по алерту `🍪⚠️ COOKIES ПРОТУХЛИ` из админ-бота, а не по расписанию. Ключевые куки живут около года, поэтому регулярный экспорт не нужен; за состоянием следит фоновая проверка в `bot.py` (`check_cookies_health`, раз в 30 минут дёргает `POST /cookies/check` у youtube- и instagram-downloader).
|
||||
|
||||
Экспорт делается с машины с браузером (на проде нет GUI) и требует точной процедуры — для YouTube только через приватное окно, иначе сессия ротируется и куки протухают. Пошаговая инструкция и скрипт доставки — в [`cookies/`](cookies/README.md).
|
||||
|
||||
## Смоук-тест
|
||||
|
||||
После любой правки, до коммита, на поднятом тестовом контуре:
|
||||
|
||||
```bash
|
||||
python3 smoke_test.py
|
||||
```
|
||||
|
||||
Гоняет реальные ссылки через запущенные сервисы и печатает таблицу: `/cookies/check` обоих загрузчиков, `/formats` на возрастном YouTube-видео (ловит отсутствие рабочего JS-рантайма) и реальные скачивания YouTube и Instagram. Код возврата `0` — всё прошло, `1` — есть падения. Адреса переопределяются через `YOUTUBE_DOWNLOADER_URL`/`INSTAGRAM_DOWNLOADER_URL`.
|
||||
|
||||
Если сервисы пересобирались — поднимать их через `docker compose up -d --force-recreate <service>`: без флага `up -d` просто стартует старый контейнер, и правка в проверку не попадёт.
|
||||
|
||||
## Обновление
|
||||
|
||||
|
|
|
|||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
# cookies-cron
|
||||
|
||||
Скрипты для регулярного обновления `youtube_cookies.txt`/`instagram_cookies.txt` из реального авторизованного браузера и доставки их по `scp` на прод и тестовые стенды.
|
||||
|
||||
Это **точная копия** скриптов, которые сейчас реально работают по крону на отдельной машине (не на проде — на проде GUI/браузера нет в принципе). Здесь они лежат для справки и на случай, если cron-машину придётся поднимать заново. Ничего в этом репозитории/CI их не запускает и не использует автоматически.
|
||||
|
||||
## Где это должно стоять и работать
|
||||
|
||||
- Машина с **графическим окружением и реальным браузером** (используется Firefox, см. `BROWSER="firefox"` в обоих скриптах) — обычный десктоп/ноутбук, не сервер.
|
||||
- В этом браузере должны быть **активные авторизованные сессии** на youtube.com и на instagram.com — скрипты не логинятся сами, они просто экспортируют куки из уже залогиненного браузера через `yt-dlp --cookies-from-browser`. Если сессии нет (вышел из аккаунта/куки протухли) — экспортируется пустой/неполный набор куков, и видео с этими доменами перестают скачиваться (в Instagram это проявляется как `login required` без всякой ошибки в самом cron-скрипте).
|
||||
- На машине должен быть установлен **`yt-dlp`** и доступен в `PATH` для cron-окружения (не только для интерактивного шелла — у cron urezанный `PATH`). Скрипты исторически не проверяют, что `yt-dlp` реально нашёлся и отработал — при отсутствии `yt-dlp` они тихо «успешно завершаются», просто переиспользуя старый файл cookies. Если куки на проде не обновляются неделями без видимых ошибок — первым делом проверьте `which yt-dlp` на cron-машине.
|
||||
|
||||
## Файлы
|
||||
|
||||
- **`get_instagram_cookies.sh`** / **`get_youtube_cookies.sh`** — извлекают куки из Firefox в локальные `instagram_cookies.txt`/`youtube_cookies.txt` рядом со скриптом.
|
||||
- **`update_cookies.sh`** — оркестратор: гоняет оба скрипта выше, затем разливает результат по `scp` на:
|
||||
- прод (`REMOTE_HOST`/`REMOTE_INSTAGRAM_PATH`/`REMOTE_YOUTUBE_PATH`) — обязательный шаг, при ошибке скрипт падает с `error_exit`;
|
||||
- тестовый стенд (`DEV_HOST`/`DEV_INSTAGRAM_PATH`/`DEV_YOUTUBE_PATH`) — необязательный, при недоступности стенда просто пишет warning и продолжает.
|
||||
|
||||
Значения этих переменных сейчас захардкожены под текущую инфраструктуру (SSH-алиасы `germany`/`hermesDev` из `~/.ssh/config` той машины, где крон запущен) — при разворачивании на новой cron-машине их нужно поправить под актуальные хосты/пути.
|
||||
|
||||
## Установка на новую cron-машину
|
||||
|
||||
```bash
|
||||
# 1. yt-dlp должен быть в PATH (включая cron-окружение)
|
||||
sudo dnf install -y yt-dlp # или: pip install --user yt-dlp / pipx install yt-dlp
|
||||
|
||||
# 2. Залогиниться в Firefox на youtube.com и instagram.com
|
||||
|
||||
# 3. Прописать актуальные REMOTE_HOST/DEV_HOST и пути в update_cookies.sh
|
||||
|
||||
# 4. Добавить в crontab
|
||||
*/20 * * * * /bin/bash -c '/path/to/cookies-cron/update_cookies.sh' >> /path/to/cookies-cron/cron_error.log 2>&1
|
||||
```
|
||||
|
||||
На принимающей стороне (прод и тестовый стенд) cookies монтируются в контейнеры с хоста — см. `docker-compose.prod.yml`/`docker-compose.yml` и раздел «Cookies на проде» в корневом `README.md`. Перезапуск контейнеров после доставки не требуется — `yt-dlp` читает файл с диска при каждом запросе.
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Скрипт для получения cookies Instagram через yt-dlp из Firefox
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COOKIES_FILE="$SCRIPT_DIR/instagram_cookies.txt"
|
||||
BROWSER="firefox"
|
||||
|
||||
# Если запущено с sudo, используем HOME реального пользователя
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
export HOME="$REAL_HOME"
|
||||
echo "⚠️ Обнаружен sudo, использую домашнюю директорию пользователя: $HOME"
|
||||
fi
|
||||
|
||||
echo "Получение cookies Instagram из Firefox..."
|
||||
echo "Файл cookies будет сохранен в: $COOKIES_FILE"
|
||||
echo "Получаю cookies..."
|
||||
|
||||
# Извлекаем cookies из браузера напрямую из базы данных (быстро)
|
||||
# --cookies-from-browser извлекает все cookies для домена
|
||||
# Используем один запрос - все cookies для instagram.com будут извлечены
|
||||
echo "Получаю cookies из браузера..."
|
||||
timeout 10 yt-dlp \
|
||||
--cookies-from-browser "$BROWSER" \
|
||||
--cookies "$COOKIES_FILE" \
|
||||
--no-download \
|
||||
--quiet \
|
||||
--socket-timeout 5 \
|
||||
"https://www.instagram.com" 2>&1 | head -20 || true
|
||||
|
||||
# Проверяем, был ли создан файл cookies (не проверяем EXIT_CODE, т.к. команды могут завершиться с ошибками, но cookies могут быть получены)
|
||||
|
||||
if [ -f "$COOKIES_FILE" ]; then
|
||||
# Проверяем, что файл содержит данные (не только заголовки)
|
||||
COOKIE_LINES=$(grep -v '^#' "$COOKIES_FILE" | grep -v '^$' | wc -l)
|
||||
|
||||
if [ "$COOKIE_LINES" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "✅ Cookies успешно сохранены в $COOKIES_FILE"
|
||||
echo " Найдено строк с cookies: $COOKIE_LINES"
|
||||
echo ""
|
||||
echo "Теперь перезапустите instagram-downloader:"
|
||||
echo " docker compose -f instagram-downloader/docker-compose.yml restart"
|
||||
echo ""
|
||||
echo "Или перезапустите все сервисы:"
|
||||
echo " ./stop_all.sh && ./start_all.sh"
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies создан, но не содержит данных"
|
||||
rm -f "$COOKIES_FILE"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies не был создан"
|
||||
echo ""
|
||||
echo "Возможные причины:"
|
||||
echo "1. Firefox не найден или недоступен"
|
||||
echo "2. Проблемы с правами доступа к файлу cookies браузера"
|
||||
echo "3. Cookies не найдены в браузере"
|
||||
echo ""
|
||||
echo "Проверка:"
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
FIREFOX_DIR="$REAL_HOME/.mozilla/firefox"
|
||||
else
|
||||
FIREFOX_DIR="$HOME/.mozilla/firefox"
|
||||
fi
|
||||
if [ -d "$FIREFOX_DIR" ]; then
|
||||
echo " ✓ Директория Firefox найдена: $FIREFOX_DIR"
|
||||
else
|
||||
echo " ✗ Директория Firefox не найдена: $FIREFOX_DIR"
|
||||
echo " Попробуйте запустить скрипт БЕЗ sudo: ./get_instagram_cookies.sh"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Скрипт для получения cookies YouTube через yt-dlp из Firefox
|
||||
# Предназначен для запуска на Ubuntu 24.04 с авторизованным Firefox
|
||||
# Результат копируется через cron на серверы со службами скачивания
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COOKIES_FILE="$SCRIPT_DIR/youtube_cookies.txt"
|
||||
BROWSER="firefox"
|
||||
TEMP_FILE="${COOKIES_FILE}.tmp"
|
||||
|
||||
# Определяем HOME пользователя (работает для sudo и обычного запуска)
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
export HOME="$REAL_HOME"
|
||||
elif [ -z "$HOME" ] || [ "$HOME" = "/root" ]; then
|
||||
# Если HOME не установлен или это root, пытаемся найти реального пользователя
|
||||
REAL_USER=$(whoami)
|
||||
if [ "$REAL_USER" != "root" ]; then
|
||||
export HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Проверка для cron: если запущен без терминала, меньше вывода
|
||||
if [ -t 0 ]; then
|
||||
VERBOSE=1
|
||||
else
|
||||
VERBOSE=0
|
||||
fi
|
||||
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo "Получение cookies YouTube из Firefox..."
|
||||
echo "Файл cookies будет сохранен в: $COOKIES_FILE"
|
||||
fi
|
||||
|
||||
# Проверяем наличие yt-dlp
|
||||
if ! command -v yt-dlp &> /dev/null; then
|
||||
echo "ERROR: yt-dlp не найден в системе" >&2
|
||||
echo "Установите: sudo apt install yt-dlp (Ubuntu) или pip install yt-dlp" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo "Получаю cookies..."
|
||||
fi
|
||||
|
||||
# Удаляем старый временный файл
|
||||
rm -f "$TEMP_FILE"
|
||||
|
||||
# Извлекаем cookies из браузера напрямую из базы данных
|
||||
# --cookies-from-browser извлекает cookies для всех доменов, связанных с URL
|
||||
# Используем один запрос с основным URL - cookies для всех поддоменов будут извлечены
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo "Получаю cookies из браузера..."
|
||||
timeout 10 yt-dlp \
|
||||
--cookies-from-browser "$BROWSER" \
|
||||
--cookies "$TEMP_FILE" \
|
||||
--no-download \
|
||||
--quiet \
|
||||
--socket-timeout 5 \
|
||||
"https://www.youtube.com" > /dev/null 2>&1 || true
|
||||
else
|
||||
timeout 10 yt-dlp \
|
||||
--cookies-from-browser "$BROWSER" \
|
||||
--cookies "$TEMP_FILE" \
|
||||
--no-download \
|
||||
--quiet \
|
||||
--socket-timeout 5 \
|
||||
"https://www.youtube.com" > /dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Функция проверки валидности куков
|
||||
check_cookies() {
|
||||
local file="$1"
|
||||
local current_time=$(date +%s)
|
||||
|
||||
# Проверяем существование файла
|
||||
if [ ! -f "$file" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Проверяем, что файл содержит данные (не только заголовки)
|
||||
local cookie_lines=$(grep -v '^#' "$file" | grep -v '^$' | wc -l)
|
||||
if [ "$cookie_lines" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Проверяем наличие YouTube-куков
|
||||
local youtube_cookies=$(grep -E '\.youtube\.com|youtube\.com' "$file" | grep -v '^#' | wc -l)
|
||||
if [ "$youtube_cookies" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Проверяем срок действия куков (должен быть хотя бы один не просроченный)
|
||||
local valid_cookies=0
|
||||
local expired_cookies=0
|
||||
|
||||
# Используем awk для более надежного парсинга
|
||||
while IFS=$'\t' read -r domain flag path secure expiration name value; do
|
||||
# Пропускаем комментарии и пустые строки
|
||||
[[ "$domain" =~ ^#.*$ ]] && continue
|
||||
[[ -z "$domain" ]] && continue
|
||||
|
||||
# Проверяем только YouTube-куки
|
||||
if [[ "$domain" =~ youtube\.com ]]; then
|
||||
if [ "$expiration" = "0" ] || [ -z "$expiration" ]; then
|
||||
# Сессионные куки (expiration=0) считаем валидными
|
||||
valid_cookies=$((valid_cookies + 1))
|
||||
else
|
||||
# Проверяем срок действия
|
||||
if [ "$expiration" -gt "$current_time" ] 2>/dev/null; then
|
||||
valid_cookies=$((valid_cookies + 1))
|
||||
else
|
||||
expired_cookies=$((expired_cookies + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
# Должен быть хотя бы один валидный YouTube-куки
|
||||
if [ "$valid_cookies" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Если есть просроченные куки, выводим предупреждение, но не считаем критичным
|
||||
if [ "$expired_cookies" -gt 0 ] && [ "$VERBOSE" = "1" ]; then
|
||||
echo "⚠️ Предупреждение: найдено $expired_cookies просроченных YouTube-куков" >&2
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Проверяем результат
|
||||
if [ -f "$TEMP_FILE" ] && check_cookies "$TEMP_FILE"; then
|
||||
# Куки валидны, переносим во временный файл в финальный
|
||||
mv "$TEMP_FILE" "$COOKIES_FILE"
|
||||
|
||||
# Собираем статистику для вывода
|
||||
COOKIE_LINES=$(grep -v '^#' "$COOKIES_FILE" | grep -v '^$' | wc -l)
|
||||
YOUTUBE_COOKIES=$(grep -E '\.youtube\.com|youtube\.com' "$COOKIES_FILE" | grep -v '^#' | wc -l)
|
||||
IMPORTANT_COOKIES=$(grep -E 'VISITOR_INFO1_LIVE|__Secure-3PSID|PREF|__Secure-YNID' "$COOKIES_FILE" | grep -v '^#' | wc -l)
|
||||
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo ""
|
||||
echo "✅ Cookies успешно сохранены в $COOKIES_FILE"
|
||||
echo " Всего строк с cookies: $COOKIE_LINES"
|
||||
echo " YouTube-куков: $YOUTUBE_COOKIES"
|
||||
echo " Важных куков: $IMPORTANT_COOKIES"
|
||||
echo ""
|
||||
echo "Файл готов к копированию на серверы через cron"
|
||||
else
|
||||
# Минимальный вывод для cron (можно логировать в файл)
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - YouTube cookies updated: $YOUTUBE_COOKIES cookies, $IMPORTANT_COOKIES important"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
else
|
||||
# Очищаем временный файл при ошибке
|
||||
rm -f "$TEMP_FILE"
|
||||
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo ""
|
||||
echo "❌ Ошибка: не удалось получить валидные YouTube-куки"
|
||||
echo ""
|
||||
echo "Возможные причины:"
|
||||
echo "1. В Firefox нет куков для YouTube"
|
||||
echo "2. Откройте YouTube в Firefox и войдите в аккаунт"
|
||||
echo "3. Все куки просрочены - обновите их в браузере"
|
||||
echo ""
|
||||
else
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Failed to get valid YouTube cookies" >&2
|
||||
fi
|
||||
|
||||
if [ ! -f "$COOKIES_FILE" ]; then
|
||||
# Если файла не было, это критичная ошибка
|
||||
exit 1
|
||||
else
|
||||
# Если файл был, просто оставляем старый (лучше работать со старыми куками чем без них)
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo "⚠️ Оставляю существующий файл cookies без изменений"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "$TEMP_FILE" ] && [ ! -f "$COOKIES_FILE" ]; then
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies не был создан"
|
||||
echo ""
|
||||
echo "Возможные причины:"
|
||||
echo "1. Firefox не найден или недоступен"
|
||||
echo "2. Проблемы с правами доступа к файлу cookies браузера"
|
||||
echo "3. Cookies не найдены в браузере"
|
||||
echo ""
|
||||
echo "Проверка:"
|
||||
FIREFOX_DIR="$HOME/.mozilla/firefox"
|
||||
if [ -d "$FIREFOX_DIR" ]; then
|
||||
echo " ✓ Директория Firefox найдена: $FIREFOX_DIR"
|
||||
else
|
||||
echo " ✗ Директория Firefox не найдена: $FIREFOX_DIR"
|
||||
echo " Убедитесь что Firefox установлен и вы авторизованы на YouTube"
|
||||
fi
|
||||
else
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Cookies file was not created" >&2
|
||||
fi
|
||||
|
||||
# Если был старый файл, оставляем его
|
||||
if [ -f "$COOKIES_FILE" ]; then
|
||||
if [ "$VERBOSE" = "1" ]; then
|
||||
echo "⚠️ Оставляю существующий файл cookies без изменений"
|
||||
fi
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
74
cookies/README.md
Normal file
74
cookies/README.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# cookies
|
||||
|
||||
Как обновлять `youtube_cookies.txt`/`instagram_cookies.txt` для прода и тестового стенда.
|
||||
|
||||
Куки обновляются **вручную и редко** — по алерту от проактивной проверки, а не по расписанию. Раньше здесь жил крон, который каждые 20–30 минут экспортировал куки из постоянно запущенного браузера; эта схема убрана, ниже объяснено почему.
|
||||
|
||||
## Почему больше нет крона и постоянно открытого браузера
|
||||
|
||||
1. **Экспорт через `--cookies-from-browser` ломает YouTube-сессию.** Wiki yt-dlp прямо запрещает связку `--cookies COOKIEFILE --cookies-from-browser BROWSER` — именно она даёт ошибку `The provided YouTube account cookies are no longer valid. They have likely been rotated in the browser as a security measure`. Пока браузер держит живую сессию, YouTube её ротирует, и любой снятый снимок куков протухает.
|
||||
|
||||
2. **Обновлять раз в 30 минут нечего.** Ключевые куки живут около года: Instagram `sessionid` и YouTube `SID`/`__Secure-1PSID` на момент разбора имели срок ≈344–376 дней. Крон сотни раз в сутки копировал одни и те же значения.
|
||||
|
||||
3. **Открытый браузер сам провоцировал блокировки.** Вкладка Instagram, висящая сутками, непрерывно ходит в сеть (лента, сторис, автоплей, телеметрия) — тысячи запросов, которые повышают шанс, что Instagram пометит сессию. Это ровно тот симптом, который мы ловили: `Main webpage is locked behind the login page` при формально валидных по сроку куках.
|
||||
|
||||
## Когда обновлять
|
||||
|
||||
Когда админ-бот пришлёт `🍪⚠️ ПРОАКТИВНАЯ ПРОВЕРКА COOKIES` по нужному сервису. Проверка живёт в `bot.py` (`check_cookies_health`, раз в 30 минут дёргает `POST /cookies/check` у youtube- и instagram-downloader) и шлёт алерт при переходе healthy→broken, потом не чаще раза в сутки, а при восстановлении — `✅ Cookies восстановлены`.
|
||||
|
||||
Проверить состояние вручную в любой момент:
|
||||
|
||||
```bash
|
||||
ssh germany "curl -s -X POST localhost:5557/cookies/check" # YouTube
|
||||
ssh germany "curl -s -X POST localhost:5556/cookies/check" # Instagram
|
||||
```
|
||||
|
||||
## Экспорт cookies
|
||||
|
||||
Нужен браузер с графическим окружением и расширение для экспорта в формате `cookies.txt` (Netscape). На проде это сделать нельзя — там нет GUI.
|
||||
|
||||
### YouTube — обязательно через приватное окно
|
||||
|
||||
Официальный способ из wiki yt-dlp, именно он снимает ротацию:
|
||||
|
||||
1. Открыть **приватное окно** и залогиниться на YouTube.
|
||||
2. В той же вкладке перейти на `https://www.youtube.com/robots.txt`. Это должна быть **единственная** открытая приватная вкладка.
|
||||
3. Экспортировать куки `youtube.com` расширением в `youtube_cookies.txt`.
|
||||
4. **Сразу закрыть приватное окно, не разлогиниваясь.** Сессия больше никогда не открывается — значит, YouTube её не ротирует, и куки живут месяцами.
|
||||
|
||||
Не использовать `--cookies-from-browser`: он заберёт обычные куки профиля, а не куки приватной сессии.
|
||||
|
||||
### Instagram
|
||||
|
||||
Экспортировать куки `instagram.com` расширением в `instagram_cookies.txt`, после чего вкладку закрыть и сессию в браузере не трогать. Формальной гарантии, как у YouTube, документация не даёт, но принцип тот же: сессию, которой браузер не пользуется, не ротируют и не помечают за подозрительную активность.
|
||||
|
||||
В файле должен быть `sessionid` — без него Instagram отдаёт `login required`:
|
||||
|
||||
```bash
|
||||
grep -c sessionid instagram_cookies.txt # должно быть >= 1
|
||||
```
|
||||
|
||||
### После экспорта
|
||||
|
||||
Браузер не оставлять запущенным. Профиль на диске для этой схемы больше не нужен — куки уже лежат в файлах.
|
||||
|
||||
## Доставка
|
||||
|
||||
Положить оба файла рядом с `deliver_cookies.sh` и запустить:
|
||||
|
||||
```bash
|
||||
./deliver_cookies.sh
|
||||
```
|
||||
|
||||
Скрипт проверит, что файлы непустые, и разольёт их по `scp`:
|
||||
|
||||
- на прод (`REMOTE_HOST=germany`) — обязательный шаг, при ошибке падает;
|
||||
- на тестовый стенд (`DEV_HOST=hermesDev`) — необязательный, при недоступности пишет warning и продолжает.
|
||||
|
||||
Хосты и пути захардкожены под текущую инфраструктуру (SSH-алиасы из `~/.ssh/config` машины, где запускаете) — при переезде поправить переменные в начале скрипта.
|
||||
|
||||
Перезапускать контейнеры после доставки не нужно: `yt-dlp` читает файл с диска при каждом запросе. Монтирование в контейнеры — см. `docker-compose.prod.yml`/`docker-compose.yml`.
|
||||
|
||||
## Проверка результата
|
||||
|
||||
После доставки дёрнуть `/cookies/check` (команды выше) — ожидаем `"cookies_valid": true` у обоих сервисов. На следующем цикле проверки в админ-бот придёт `✅ Cookies восстановлены`.
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
#!/bin/bash
|
||||
# Скрипт для обновления cookies Instagram и YouTube через cron
|
||||
# Запускает скрипты получения cookies и копирует их на удаленный хост
|
||||
# Доставка cookies на прод и тестовый стенд.
|
||||
#
|
||||
# Запускается ВРУЧНУЮ после разового экспорта cookies из браузера — не по крону.
|
||||
# Как правильно экспортировать cookies, см. README.md рядом с этим скриптом.
|
||||
# Автоматический экспорт через `yt-dlp --cookies-from-browser` убран намеренно:
|
||||
# он ломает YouTube-сессию ("cookies have likely been rotated in the browser").
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -9,8 +13,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Настройки
|
||||
INSTAGRAM_SCRIPT="$SCRIPT_DIR/get_instagram_cookies.sh"
|
||||
YOUTUBE_SCRIPT="$SCRIPT_DIR/get_youtube_cookies.sh"
|
||||
INSTAGRAM_COOKIES="$SCRIPT_DIR/instagram_cookies.txt"
|
||||
YOUTUBE_COOKIES="$SCRIPT_DIR/youtube_cookies.txt"
|
||||
REMOTE_HOST="germany"
|
||||
|
|
@ -19,7 +21,7 @@ REMOTE_YOUTUBE_PATH="/root/services/videoDownloadTGbot/youtube_cookies.txt"
|
|||
DEV_HOST="hermesDev"
|
||||
DEV_INSTAGRAM_PATH="services/videoDownloadTGbot/instagram-downloader/instagram_cookies.txt"
|
||||
DEV_YOUTUBE_PATH="services/videoDownloadTGbot/youtube-downloader/youtube_cookies.txt"
|
||||
LOG_FILE="$SCRIPT_DIR/cron.log"
|
||||
LOG_FILE="$SCRIPT_DIR/deliver.log"
|
||||
|
||||
# Функция логирования
|
||||
log() {
|
||||
|
|
@ -32,48 +34,28 @@ error_exit() {
|
|||
exit 1
|
||||
}
|
||||
|
||||
log "=== Начало обновления cookies ==="
|
||||
# Проверяет, что файл существует и содержит хотя бы одну строку с cookie
|
||||
check_cookies_file() {
|
||||
local file="$1"
|
||||
local name="$2"
|
||||
|
||||
# Проверяем наличие скриптов
|
||||
if [ ! -f "$INSTAGRAM_SCRIPT" ]; then
|
||||
error_exit "Скрипт не найден: $INSTAGRAM_SCRIPT"
|
||||
if [ ! -f "$file" ]; then
|
||||
error_exit "Файл не найден: $file. Сначала экспортируйте cookies $name — см. README.md"
|
||||
fi
|
||||
|
||||
if [ ! -f "$YOUTUBE_SCRIPT" ]; then
|
||||
error_exit "Скрипт не найден: $YOUTUBE_SCRIPT"
|
||||
local lines
|
||||
lines=$(grep -cv -e '^#' -e '^$' "$file" || true)
|
||||
if [ "$lines" -eq 0 ]; then
|
||||
error_exit "Файл $file пустой (0 строк с cookies). Экспорт $name не удался — см. README.md"
|
||||
fi
|
||||
|
||||
# Проверяем, что скрипты исполняемые
|
||||
chmod +x "$INSTAGRAM_SCRIPT" "$YOUTUBE_SCRIPT"
|
||||
log "✅ $name: $file ($lines строк с cookies)"
|
||||
}
|
||||
|
||||
# Получаем cookies Instagram
|
||||
log "Получение cookies Instagram..."
|
||||
if bash "$INSTAGRAM_SCRIPT" >> "$LOG_FILE" 2>&1; then
|
||||
if [ -f "$INSTAGRAM_COOKIES" ]; then
|
||||
log "✅ Cookies Instagram успешно получены"
|
||||
else
|
||||
error_exit "Файл cookies Instagram не был создан"
|
||||
fi
|
||||
else
|
||||
error_exit "Ошибка при получении cookies Instagram (код возврата: $?)"
|
||||
fi
|
||||
log "=== Начало доставки cookies ==="
|
||||
|
||||
# Получаем cookies YouTube
|
||||
log "Получение cookies YouTube..."
|
||||
if bash "$YOUTUBE_SCRIPT" >> "$LOG_FILE" 2>&1; then
|
||||
if [ -f "$YOUTUBE_COOKIES" ]; then
|
||||
log "✅ Cookies YouTube успешно получены"
|
||||
else
|
||||
error_exit "Файл cookies YouTube не был создан"
|
||||
fi
|
||||
else
|
||||
# YouTube скрипт может завершиться с кодом 0, даже если новый файл не создан (использует старый)
|
||||
if [ -f "$YOUTUBE_COOKIES" ]; then
|
||||
log "✅ Cookies YouTube (возможно использован старый файл)"
|
||||
else
|
||||
error_exit "Файл cookies YouTube не найден"
|
||||
fi
|
||||
fi
|
||||
check_cookies_file "$INSTAGRAM_COOKIES" "Instagram"
|
||||
check_cookies_file "$YOUTUBE_COOKIES" "YouTube"
|
||||
|
||||
# Копируем cookies на удаленный хост (прод)
|
||||
log "Копирование cookies на удаленный хост $REMOTE_HOST..."
|
||||
|
|
@ -107,5 +89,6 @@ else
|
|||
log "⚠️ Не удалось скопировать YouTube cookies на $DEV_HOST (продолжаем)"
|
||||
fi
|
||||
|
||||
log "=== Обновление cookies завершено успешно ==="
|
||||
log "=== Доставка cookies завершена успешно ==="
|
||||
log "Проверить результат: ssh ${REMOTE_HOST} \"curl -s -X POST localhost:5557/cookies/check; curl -s -X POST localhost:5556/cookies/check\""
|
||||
exit 0
|
||||
|
|
@ -1,51 +1,7 @@
|
|||
# Инструкция по получению cookies для Instagram
|
||||
|
||||
## Вариант 1: Использование расширения браузера (рекомендуется)
|
||||
Актуальная инструкция живёт в одном месте — [`cookies/README.md`](../cookies/README.md).
|
||||
|
||||
1. Установите расширение для экспорта cookies:
|
||||
- Chrome: [Get cookies.txt LOCALLY](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)
|
||||
- Firefox: [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/)
|
||||
|
||||
2. Откройте Instagram и войдите в свой аккаунт: https://www.instagram.com
|
||||
|
||||
3. Кликните на расширение и выберите "Export cookies.txt"
|
||||
|
||||
4. Сохраните файл как `instagram_cookies.txt` в корень проекта (там же, где docker-compose.yml)
|
||||
|
||||
## Вариант 2: Ручной экспорт через DevTools
|
||||
|
||||
1. Откройте Instagram в браузере и войдите: https://www.instagram.com
|
||||
|
||||
2. Откройте DevTools (F12) → вкладка Application/Storage → Cookies → https://www.instagram.com
|
||||
|
||||
3. Скопируйте нужные cookies в формате Netscape:
|
||||
```
|
||||
# Netscape HTTP Cookie File
|
||||
.instagram.com TRUE / FALSE 1735689600 sessionid YOUR_SESSION_ID
|
||||
.instagram.com TRUE / FALSE 1735689600 csrftoken YOUR_CSRF_TOKEN
|
||||
```
|
||||
|
||||
4. Сохраните в файл `instagram_cookies.txt`
|
||||
|
||||
## Вариант 3: Использование yt-dlp для экспорта
|
||||
|
||||
```bash
|
||||
# Экспорт cookies из браузера Chrome
|
||||
yt-dlp --cookies-from-browser chrome --cookies instagram_cookies.txt https://www.instagram.com
|
||||
|
||||
# Или из Firefox
|
||||
yt-dlp --cookies-from-browser firefox --cookies instagram_cookies.txt https://www.instagram.com
|
||||
```
|
||||
|
||||
## Важно!
|
||||
|
||||
- Файл должен называться `instagram_cookies.txt`
|
||||
- Разместите его в корне проекта (рядом с docker-compose.yml)
|
||||
- Cookies имеют срок действия - возможно, потребуется обновлять их периодически
|
||||
- Не коммитьте файл в git (он уже добавлен в .gitignore)
|
||||
|
||||
После добавления файла перезапустите контейнер:
|
||||
```bash
|
||||
docker compose restart bot
|
||||
```
|
||||
Там описано: когда обновлять (по алерту `🍪⚠️ ПРОАКТИВНАЯ ПРОВЕРКА COOKIES` из админ-бота, а не по расписанию), как правильно экспортировать куки расширением для `cookies.txt` и как доставить их на прод через `deliver_cookies.sh`.
|
||||
|
||||
**Не используйте `yt-dlp --cookies-from-browser ... --cookies file.txt`** — этот способ раньше был описан здесь, но wiki yt-dlp его прямо запрещает: он ломает сессию, и куки протухают («cookies have likely been rotated in the browser as a security measure»).
|
||||
|
|
|
|||
|
|
@ -11,27 +11,9 @@
|
|||
|
||||
### 1. Настройка cookies
|
||||
|
||||
Перед запуском сервиса необходимо получить cookies Instagram. Есть несколько способов:
|
||||
Перед запуском сервиса нужно положить `instagram_cookies.txt` в папку `instagram-downloader/`.
|
||||
|
||||
#### Способ 1: Через скрипт (рекомендуется)
|
||||
|
||||
```bash
|
||||
cd instagram-downloader
|
||||
./get_instagram_cookies.sh
|
||||
```
|
||||
|
||||
Скрипт попросит выбрать браузер и автоматически извлечет cookies.
|
||||
|
||||
#### Способ 2: Обновление существующих cookies
|
||||
|
||||
```bash
|
||||
cd instagram-downloader
|
||||
./update_instagram_cookies.sh
|
||||
```
|
||||
|
||||
#### Способ 3: Вручную
|
||||
|
||||
См. подробные инструкции в `INSTAGRAM_COOKIES_INSTRUCTIONS.md`
|
||||
Куки экспортируются вручную из браузера расширением для `cookies.txt` — пошаговая процедура в [`cookies/README.md`](../cookies/README.md). Скрипты автоматического экспорта через `yt-dlp --cookies-from-browser` убраны: этот способ ломает сессию, куки после него быстро протухают.
|
||||
|
||||
### 2. Запуск сервиса
|
||||
|
||||
|
|
@ -71,6 +53,21 @@ curl http://localhost:5556/health
|
|||
}
|
||||
```
|
||||
|
||||
### POST /cookies/check
|
||||
|
||||
Проверка, работают ли cookies прямо сейчас: делает лёгкий `extract_info` (без скачивания) по публичному тестовому ролику. Используется фоновым мониторингом в `bot.py`.
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"cookies_present": true,
|
||||
"cookies_valid": false,
|
||||
"detail": "текст ошибки от yt-dlp"
|
||||
}
|
||||
```
|
||||
|
||||
`cookies_valid: null` означает, что файл с куками не найден. Тестовый URL переопределяется переменной `INSTAGRAM_COOKIE_TEST_URL`.
|
||||
|
||||
### POST /download/stream
|
||||
|
||||
Скачивание видео с Instagram.
|
||||
|
|
@ -93,40 +90,22 @@ curl http://localhost:5556/health
|
|||
|
||||
## Обновление cookies
|
||||
|
||||
Cookies Instagram имеют ограниченный срок действия. Рекомендуется обновлять их раз в несколько недель.
|
||||
Ключевые куки Instagram живут около года, поэтому обновлять их по расписанию не нужно. Триггер на обновление — алерт `🍪⚠️ ПРОАКТИВНАЯ ПРОВЕРКА COOKIES` из админ-бота (за состоянием следит фоновая проверка в `bot.py`, которая раз в 30 минут дёргает `POST /cookies/check`).
|
||||
|
||||
Для обновления:
|
||||
|
||||
```bash
|
||||
cd instagram-downloader
|
||||
./update_instagram_cookies.sh
|
||||
```
|
||||
|
||||
После обновления перезапустите сервис:
|
||||
|
||||
```bash
|
||||
docker compose restart instagram-downloader
|
||||
```
|
||||
Процедура экспорта и доставки — в [`cookies/README.md`](../cookies/README.md). Перезапускать сервис после обновления не нужно: `yt-dlp` читает файл с диска при каждом запросе.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Сервис не может скачать видео
|
||||
|
||||
1. Проверьте наличие файла `instagram_cookies.txt` в папке `instagram-downloader/`
|
||||
2. Проверьте срок действия cookies (они могут истечь)
|
||||
3. Обновите cookies через скрипт `update_instagram_cookies.sh`
|
||||
1. Проверьте состояние куков: `curl -s -X POST localhost:5556/cookies/check`
|
||||
2. Проверьте наличие файла `instagram_cookies.txt` в папке `instagram-downloader/` и что в нём есть `sessionid`
|
||||
3. Если `cookies_valid: false` — обновите куки по инструкции в [`cookies/README.md`](../cookies/README.md)
|
||||
4. Проверьте логи: `docker compose logs instagram-downloader`
|
||||
|
||||
### Cookies истекли
|
||||
### Cookies есть и не истекли по сроку, но всё равно `login required`
|
||||
|
||||
Если видите ошибку "Instagram cookies истекли", выполните:
|
||||
|
||||
```bash
|
||||
cd instagram-downloader
|
||||
./update_instagram_cookies.sh
|
||||
```
|
||||
|
||||
Затем перезапустите сервис.
|
||||
Instagram умеет блокировать сессию на своей стороне — тогда в логах видно `Main webpage is locked behind the login page` при формально валидном `sessionid`. Лечится только повторным экспортом куков. Провоцирует такую блокировку активная автоматическая работа с аккаунтом (например, постоянно открытая вкладка Instagram, непрерывно ходящая в сеть), поэтому после экспорта браузер лучше закрывать.
|
||||
|
||||
## Структура файлов
|
||||
|
||||
|
|
@ -136,9 +115,7 @@ instagram-downloader/
|
|||
├── Dockerfile # Образ Docker
|
||||
├── docker-compose.yml # Конфигурация для отдельного запуска
|
||||
├── requirements.txt # Python зависимости
|
||||
├── get_instagram_cookies.sh # Скрипт для получения cookies
|
||||
├── update_instagram_cookies.sh # Скрипт для обновления cookies
|
||||
├── INSTAGRAM_COOKIES_INSTRUCTIONS.md # Подробные инструкции по cookies
|
||||
├── INSTAGRAM_COOKIES_INSTRUCTIONS.md # Отсылка к cookies/README.md
|
||||
└── README.md # Этот файл
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -206,12 +206,24 @@ INSTAGRAM_COOKIE_TEST_URL = os.getenv(
|
|||
)
|
||||
|
||||
|
||||
# Маркеры, по которым сбой однозначно относится к cookies/авторизации.
|
||||
# Всё остальное считаем поломкой извлечения (версия yt-dlp, изменения на стороне
|
||||
# Instagram) и про cookies ничего не утверждаем — иначе мониторинг уводит
|
||||
# диагностику не туда: например, "empty media response" воспроизводится и без cookies.
|
||||
INSTAGRAM_COOKIE_INVALID_MARKERS = (
|
||||
'login required',
|
||||
'locked behind the login page',
|
||||
'rate-limit reached',
|
||||
)
|
||||
|
||||
|
||||
@app.route('/cookies/check', methods=['POST'])
|
||||
def cookies_check():
|
||||
"""Проверяет, рабочие ли сейчас Instagram cookies (для проактивного мониторинга)."""
|
||||
cookies_file = Path(os.getenv('INSTAGRAM_COOKIES_FILE', 'instagram_cookies.txt'))
|
||||
if not cookies_file.exists():
|
||||
return jsonify({'cookies_present': False, 'cookies_valid': None,
|
||||
'status': 'no_cookies',
|
||||
'detail': 'cookies file missing'}), 200
|
||||
|
||||
ydl_opts = {
|
||||
|
|
@ -224,10 +236,16 @@ def cookies_check():
|
|||
try:
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.extract_info(INSTAGRAM_COOKIE_TEST_URL, download=False)
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': True, 'detail': ''}), 200
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': True,
|
||||
'status': 'ok', 'detail': ''}), 200
|
||||
except Exception as e:
|
||||
err = str(e)
|
||||
detail = err[-500:]
|
||||
if any(marker in err.lower() for marker in INSTAGRAM_COOKIE_INVALID_MARKERS):
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': False,
|
||||
'detail': str(e)[-500:]}), 200
|
||||
'status': 'cookies_invalid', 'detail': detail}), 200
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': None,
|
||||
'status': 'extraction_failed', 'detail': detail}), 200
|
||||
|
||||
|
||||
@app.route('/download/stream', methods=['POST'])
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Скрипт для получения cookies Instagram через yt-dlp из Firefox
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COOKIES_FILE="$SCRIPT_DIR/instagram_cookies.txt"
|
||||
BROWSER="firefox"
|
||||
|
||||
# Если запущено с sudo, используем HOME реального пользователя
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
export HOME="$REAL_HOME"
|
||||
echo "⚠️ Обнаружен sudo, использую домашнюю директорию пользователя: $HOME"
|
||||
fi
|
||||
|
||||
echo "Получение cookies Instagram из Firefox..."
|
||||
echo "Файл cookies будет сохранен в: $COOKIES_FILE"
|
||||
echo "Получаю cookies..."
|
||||
|
||||
# Используем таймаут для предотвращения зависания
|
||||
timeout 15 yt-dlp \
|
||||
--cookies-from-browser "$BROWSER" \
|
||||
--cookies "$COOKIES_FILE" \
|
||||
--no-download \
|
||||
--quiet \
|
||||
"https://www.instagram.com" 2>&1 | head -20
|
||||
|
||||
EXIT_CODE=$?
|
||||
|
||||
if [ $EXIT_CODE -eq 124 ]; then
|
||||
echo ""
|
||||
echo "⚠️ Процесс получения cookies превысил таймаут (15 сек)"
|
||||
echo " Проверяю, был ли создан файл cookies..."
|
||||
elif [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 124 ]; then
|
||||
echo ""
|
||||
echo "⚠️ Процесс завершился с кодом $EXIT_CODE"
|
||||
echo " Проверяю, был ли создан файл cookies..."
|
||||
fi
|
||||
|
||||
if [ -f "$COOKIES_FILE" ]; then
|
||||
# Проверяем, что файл содержит данные (не только заголовки)
|
||||
COOKIE_LINES=$(grep -v '^#' "$COOKIES_FILE" | grep -v '^$' | wc -l)
|
||||
|
||||
if [ "$COOKIE_LINES" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "✅ Cookies успешно сохранены в $COOKIES_FILE"
|
||||
echo " Найдено строк с cookies: $COOKIE_LINES"
|
||||
echo ""
|
||||
echo "Если cookies смонтированы с хоста (см. docker-compose.yml/docker-compose.prod.yml),"
|
||||
echo "перезапуск не нужен — yt-dlp читает файл заново при каждом запросе."
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies создан, но не содержит данных"
|
||||
rm -f "$COOKIES_FILE"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies не был создан"
|
||||
echo ""
|
||||
echo "Возможные причины:"
|
||||
echo "1. Firefox не найден или недоступен"
|
||||
echo "2. Проблемы с правами доступа к файлу cookies браузера"
|
||||
echo "3. Cookies не найдены в браузере"
|
||||
echo ""
|
||||
echo "Проверка:"
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
FIREFOX_DIR="$REAL_HOME/.mozilla/firefox"
|
||||
else
|
||||
FIREFOX_DIR="$HOME/.mozilla/firefox"
|
||||
fi
|
||||
if [ -d "$FIREFOX_DIR" ]; then
|
||||
echo " ✓ Директория Firefox найдена: $FIREFOX_DIR"
|
||||
else
|
||||
echo " ✗ Директория Firefox не найдена: $FIREFOX_DIR"
|
||||
echo " Попробуйте запустить скрипт БЕЗ sudo: ./get_instagram_cookies.sh"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
Flask==3.0.0
|
||||
flask-cors==4.0.0
|
||||
yt-dlp>=2024.12.13
|
||||
yt-dlp>=2026.7.4
|
||||
|
||||
gunicorn==21.2.0
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Обновление cookies Instagram через браузер
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COOKIES_FILE="$SCRIPT_DIR/instagram_cookies.txt"
|
||||
|
||||
echo "Обновление cookies Instagram..."
|
||||
echo ""
|
||||
echo "Выберите браузер:"
|
||||
echo "1) Chrome"
|
||||
echo "2) Firefox"
|
||||
echo "3) Edge"
|
||||
echo "4) Opera"
|
||||
read -p "Введите номер (1-4): " browser
|
||||
|
||||
case "$browser" in
|
||||
1) BROWSER="chrome" ;;
|
||||
2) BROWSER="firefox" ;;
|
||||
3) BROWSER="edge" ;;
|
||||
4) BROWSER="opera" ;;
|
||||
*)
|
||||
echo "Неверный выбор"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "ВАЖНО: Перед обновлением cookies убедитесь, что вы авторизованы в Instagram в выбранном браузере!"
|
||||
echo ""
|
||||
read -p "Нажмите Enter для продолжения или Ctrl+C для отмены..."
|
||||
|
||||
echo "Обновляю cookies из $BROWSER..."
|
||||
echo "Файл cookies будет сохранен в: $COOKIES_FILE"
|
||||
|
||||
yt-dlp --cookies-from-browser "$BROWSER" --cookies "$COOKIES_FILE" --no-download https://www.instagram.com 2>&1 | head -10
|
||||
|
||||
if [ -f "$COOKIES_FILE" ]; then
|
||||
echo ""
|
||||
echo "✅ Cookies успешно обновлены в $COOKIES_FILE"
|
||||
echo ""
|
||||
echo "Для применения изменений перезапустите instagram-downloader сервис:"
|
||||
echo " cd $(cd "$SCRIPT_DIR/.." && pwd) && docker compose restart instagram-downloader"
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies не был создан"
|
||||
exit 1
|
||||
fi
|
||||
128
smoke_test.py
Executable file
128
smoke_test.py
Executable file
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Смоук-тест загрузчиков: гоняет реальные ссылки через запущенные сервисы.
|
||||
|
||||
Запускать после КАЖДОЙ правки, до коммита:
|
||||
|
||||
python3 smoke_test.py
|
||||
|
||||
Ссылки подобраны не случайно — это реальные регрессии, которые уже ломались:
|
||||
* возрастное YouTube-видео ловит отсутствие рабочего JS-рантайма (n-challenge);
|
||||
* Instagram-ролик ловит устаревший экстрактор yt-dlp;
|
||||
* обычное видео проверяет, что базовый путь скачивания цел.
|
||||
|
||||
Зависимостей нет — только стандартная библиотека, чтобы запускалось на любом хосте.
|
||||
Код возврата: 0 — все проверки прошли, 1 — есть падения.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
YOUTUBE_URL = os.getenv('YOUTUBE_DOWNLOADER_URL', 'http://localhost:5557')
|
||||
INSTAGRAM_URL = os.getenv('INSTAGRAM_DOWNLOADER_URL', 'http://localhost:5556')
|
||||
|
||||
YT_SMALL = 'https://www.youtube.com/watch?v=jNQXAC9IVRw' # «Me at the zoo», 19 секунд
|
||||
YT_AGE_RESTRICTED = 'https://youtu.be/UMyoCr2MnpM' # требует cookies + решения n-challenge
|
||||
IG_REEL = 'https://www.instagram.com/reel/Dak0fDTMv_i/' # обычный публичный reel
|
||||
|
||||
MIN_VIDEO_BYTES = 100 * 1024 # меньше — значит пришло не видео, а заглушка/ошибка
|
||||
|
||||
|
||||
def _post(url, payload=None, timeout=120):
|
||||
"""POST с JSON-телом. Возвращает (http_code, body_bytes)."""
|
||||
data = json.dumps(payload or {}).encode()
|
||||
req = urllib.request.Request(
|
||||
url, data=data, headers={'Content-Type': 'application/json'}, method='POST'
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return resp.status, resp.read()
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read()
|
||||
except Exception as e:
|
||||
return None, str(e).encode()
|
||||
|
||||
|
||||
def check_cookies(service, base_url):
|
||||
"""/cookies/check должен вернуть status=ok."""
|
||||
code, body = _post(f'{base_url}/cookies/check', timeout=180)
|
||||
if code != 200:
|
||||
return False, f'HTTP {code}'
|
||||
try:
|
||||
data = json.loads(body)
|
||||
except ValueError:
|
||||
return False, 'ответ не JSON'
|
||||
status = data.get('status', '?')
|
||||
if status == 'ok':
|
||||
return True, 'status=ok'
|
||||
return False, f"status={status}: {(data.get('detail') or '')[:120]}"
|
||||
|
||||
|
||||
def check_formats(url):
|
||||
"""/formats у YouTube — лёгкая проверка: cookies + JS-рантайм без скачивания."""
|
||||
code, body = _post(f'{YOUTUBE_URL}/formats', {'url': url}, timeout=180)
|
||||
if code != 200:
|
||||
detail = body.decode('utf-8', 'replace')[:150]
|
||||
return False, f'HTTP {code}: {detail}'
|
||||
try:
|
||||
formats = json.loads(body).get('formats', [])
|
||||
except ValueError:
|
||||
return False, 'ответ не JSON'
|
||||
if not formats:
|
||||
return False, 'форматы не найдены'
|
||||
return True, f'форматов: {len(formats)}'
|
||||
|
||||
|
||||
def check_download(base_url, url):
|
||||
"""/download/stream — полный путь до реального файла."""
|
||||
code, body = _post(f'{base_url}/download/stream', {'url': url}, timeout=600)
|
||||
if code != 200:
|
||||
detail = body.decode('utf-8', 'replace')[:150]
|
||||
return False, f'HTTP {code}: {detail}'
|
||||
size = len(body)
|
||||
if size < MIN_VIDEO_BYTES:
|
||||
return False, f'подозрительно мало: {size} байт'
|
||||
return True, f'{size / 1024:.0f} КБ'
|
||||
|
||||
|
||||
CHECKS = [
|
||||
('cookies YouTube', lambda: check_cookies('youtube', YOUTUBE_URL)),
|
||||
('cookies Instagram', lambda: check_cookies('instagram', INSTAGRAM_URL)),
|
||||
('YouTube возрастное', lambda: check_formats(YT_AGE_RESTRICTED)),
|
||||
('YouTube скачивание', lambda: check_download(YOUTUBE_URL, YT_SMALL)),
|
||||
('Instagram скачивание', lambda: check_download(INSTAGRAM_URL, IG_REEL)),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
print(f'Смоук-тест: {YOUTUBE_URL} / {INSTAGRAM_URL}\n')
|
||||
|
||||
results = []
|
||||
for name, fn in CHECKS:
|
||||
started = time.monotonic()
|
||||
try:
|
||||
ok, detail = fn()
|
||||
except Exception as e: # проверка не должна ронять весь прогон
|
||||
ok, detail = False, f'исключение: {e}'
|
||||
results.append((name, ok, detail, time.monotonic() - started))
|
||||
|
||||
width = max(len(name) for name, *_ in results)
|
||||
print(f'| {"Проверка".ljust(width)} | Итог | Время | Детали')
|
||||
print(f'|{"-" * (width + 2)}|------|-------|--------')
|
||||
for name, ok, detail, elapsed in results:
|
||||
mark = ' OK ' if ok else 'FAIL'
|
||||
print(f'| {name.ljust(width)} | {mark} | {elapsed:5.1f}с | {detail}')
|
||||
|
||||
failed = [name for name, ok, *_ in results if not ok]
|
||||
print()
|
||||
if failed:
|
||||
print(f'ИТОГ: провалено {len(failed)} из {len(results)} — {", ".join(failed)}')
|
||||
return 1
|
||||
print(f'ИТОГ: все проверки прошли ({len(results)} из {len(results)})')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -1,5 +1,20 @@
|
|||
FROM python:3.11-slim
|
||||
RUN apt-get update && apt-get install -y ffmpeg wget curl aria2 nodejs npm && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y ffmpeg wget curl aria2 unzip && rm -rf /var/lib/apt/lists/*
|
||||
# Deno — рекомендованный yt-dlp рантайм для решения n-challenge YouTube.
|
||||
# Node из Debian bookworm — версия 20, а yt-dlp требует минимум 22 (иначе
|
||||
# "JS runtimes: node-20.x (unsupported)" и "No video formats found" на путях с cookies).
|
||||
RUN set -eux; \
|
||||
case "$(uname -m)" in \
|
||||
x86_64) DENO_ARCH=x86_64-unknown-linux-gnu ;; \
|
||||
aarch64) DENO_ARCH=aarch64-unknown-linux-gnu ;; \
|
||||
*) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
curl -fsSL --retry 5 --retry-all-errors -o /tmp/deno.zip \
|
||||
"https://github.com/denoland/deno/releases/latest/download/deno-${DENO_ARCH}.zip"; \
|
||||
unzip -q /tmp/deno.zip -d /usr/local/bin; \
|
||||
rm /tmp/deno.zip; \
|
||||
chmod +x /usr/local/bin/deno; \
|
||||
deno --version
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ def _build_ytdlp_base_cmd() -> list:
|
|||
YTDLP_CMD,
|
||||
'--socket-timeout', '15',
|
||||
'--extractor-args', EXTRACTOR_ARGS,
|
||||
'--js-runtimes', 'node',
|
||||
'--js-runtimes', 'deno',
|
||||
'--remote-components', 'ejs:github',
|
||||
'--no-playlist',
|
||||
]
|
||||
|
|
@ -490,7 +490,14 @@ YOUTUBE_COOKIE_TEST_URL = os.getenv(
|
|||
'YOUTUBE_COOKIE_TEST_URL',
|
||||
'https://www.youtube.com/watch?v=jNQXAC9IVRw' # "Me at the zoo" — первое видео на YouTube, публичное, без возрастных ограничений
|
||||
)
|
||||
YOUTUBE_COOKIE_INVALID_MARKERS = ('no longer valid', 'have likely been rotated')
|
||||
# Маркеры, по которым сбой однозначно относится к cookies/авторизации.
|
||||
# Всё остальное считаем поломкой извлечения (версия yt-dlp, JS-рантайм, сеть) и
|
||||
# про cookies ничего не утверждаем — иначе мониторинг уводит диагностику не туда.
|
||||
YOUTUBE_COOKIE_INVALID_MARKERS = (
|
||||
'no longer valid',
|
||||
'have likely been rotated',
|
||||
'sign in to confirm',
|
||||
)
|
||||
|
||||
|
||||
@app.route('/cookies/check', methods=['POST'])
|
||||
|
|
@ -499,6 +506,7 @@ def cookies_check():
|
|||
cookies_file = Path(os.getenv('YOUTUBE_COOKIES_FILE', '/app/youtube_cookies.txt'))
|
||||
if not (cookies_file.exists() and cookies_file.stat().st_size > 0):
|
||||
return jsonify({'cookies_present': False, 'cookies_valid': None,
|
||||
'status': 'no_cookies',
|
||||
'detail': 'cookies file missing or empty'}), 200
|
||||
|
||||
# Без --quiet/--no-warnings: сигнал ротации кук — это подавляемый WARNING, а не ошибка с ненулевым returncode
|
||||
|
|
@ -506,15 +514,23 @@ def cookies_check():
|
|||
try:
|
||||
result = _run_ytdlp(cmd, timeout=INFO_TIMEOUT)
|
||||
except Exception as e:
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': False,
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': None,
|
||||
'status': 'extraction_failed',
|
||||
'detail': f'yt-dlp invocation failed: {e}'}), 200
|
||||
|
||||
stderr = result.stderr
|
||||
if any(marker in stderr for marker in YOUTUBE_COOKIE_INVALID_MARKERS) or result.returncode != 0:
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': False,
|
||||
'detail': stderr.strip()[-500:]}), 200
|
||||
detail = stderr.strip()[-500:]
|
||||
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': True, 'detail': ''}), 200
|
||||
if any(marker in stderr.lower() for marker in YOUTUBE_COOKIE_INVALID_MARKERS):
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': False,
|
||||
'status': 'cookies_invalid', 'detail': detail}), 200
|
||||
|
||||
if result.returncode != 0:
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': None,
|
||||
'status': 'extraction_failed', 'detail': detail}), 200
|
||||
|
||||
return jsonify({'cookies_present': True, 'cookies_valid': True,
|
||||
'status': 'ok', 'detail': ''}), 200
|
||||
|
||||
|
||||
@app.route('/download/stream', methods=['POST'])
|
||||
|
|
@ -570,9 +586,10 @@ def download_stream():
|
|||
error_msg = (
|
||||
f"{error_str}\n\n"
|
||||
"💡 Совет: Cookies устарели или недействительны. "
|
||||
"Обновите cookies через скрипт:\n"
|
||||
" ./youtube-downloader/get_youtube_cookies.sh\n"
|
||||
"Затем перезапустите сервис."
|
||||
"Обновите их вручную по инструкции в cookies/README.md "
|
||||
"(для YouTube — только через приватное окно, иначе сессия ротируется) "
|
||||
"и доставьте через cookies/deliver_cookies.sh. "
|
||||
"Перезапуск сервиса не требуется."
|
||||
)
|
||||
else:
|
||||
error_msg = error_str
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Скрипт для получения cookies YouTube через yt-dlp из Firefox
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COOKIES_FILE="$SCRIPT_DIR/youtube_cookies.txt"
|
||||
BROWSER="firefox"
|
||||
|
||||
# Если запущено с sudo, используем HOME реального пользователя
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
export HOME="$REAL_HOME"
|
||||
echo "⚠️ Обнаружен sudo, использую домашнюю директорию пользователя: $HOME"
|
||||
fi
|
||||
|
||||
echo "Получение cookies YouTube из Firefox..."
|
||||
echo "Файл cookies будет сохранен в: $COOKIES_FILE"
|
||||
|
||||
# Проверяем наличие yt-dlp
|
||||
if ! command -v yt-dlp &> /dev/null; then
|
||||
echo ""
|
||||
echo "❌ yt-dlp не найден в системе"
|
||||
echo ""
|
||||
echo "Установите yt-dlp одним из способов:"
|
||||
echo ""
|
||||
echo "1. Через pacman (Arch Linux):"
|
||||
echo " sudo pacman -S yt-dlp"
|
||||
echo ""
|
||||
echo "2. Через pip:"
|
||||
echo " pip install yt-dlp"
|
||||
echo " или"
|
||||
echo " pip3 install yt-dlp"
|
||||
echo ""
|
||||
echo "3. Через pipx (рекомендуется):"
|
||||
echo " pipx install yt-dlp"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Получаю cookies..."
|
||||
|
||||
# Используем простой способ: извлекаем cookies из браузера и сохраняем в файл
|
||||
# Используем конкретное короткое видео (не плейлист, не главную страницу)
|
||||
# Таймаут 10 секунд
|
||||
# --no-download - не скачивать видео
|
||||
# --skip-download - пропустить скачивание
|
||||
# --flat-playlist - не извлекать информацию о видео в плейлистах
|
||||
# Перенаправляем весь вывод, чтобы не видеть процесс скачивания
|
||||
timeout 10 yt-dlp \
|
||||
--cookies-from-browser "$BROWSER" \
|
||||
--cookies "$COOKIES_FILE" \
|
||||
--no-download \
|
||||
--skip-download \
|
||||
--flat-playlist \
|
||||
--quiet \
|
||||
"https://www.youtube.com/watch?v=jNQXAC9IVRw" > /dev/null 2>&1 || true
|
||||
|
||||
EXIT_CODE=$?
|
||||
|
||||
# Не важно, какой код возврата - главное проверить, создался ли файл cookies
|
||||
# yt-dlp может вернуть ошибку, но cookies все равно сохранить
|
||||
|
||||
if [ -f "$COOKIES_FILE" ]; then
|
||||
# Проверяем, что файл содержит данные (не только заголовки)
|
||||
COOKIE_LINES=$(grep -v '^#' "$COOKIES_FILE" | grep -v '^$' | wc -l)
|
||||
|
||||
if [ "$COOKIE_LINES" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "✅ Cookies успешно сохранены в $COOKIES_FILE"
|
||||
echo " Найдено строк с cookies: $COOKIE_LINES"
|
||||
echo ""
|
||||
echo "Если cookies смонтированы с хоста (см. docker-compose.yml/docker-compose.prod.yml),"
|
||||
echo "перезапуск не нужен — yt-dlp читает файл заново при каждом запросе."
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies создан, но не содержит данных"
|
||||
rm -f "$COOKIES_FILE"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Ошибка: файл cookies не был создан"
|
||||
echo ""
|
||||
echo "Возможные причины:"
|
||||
echo "1. Firefox не найден или недоступен"
|
||||
echo "2. Проблемы с правами доступа к файлу cookies браузера"
|
||||
echo "3. Cookies не найдены в браузере"
|
||||
echo ""
|
||||
echo "Проверка:"
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||
FIREFOX_DIR="$REAL_HOME/.mozilla/firefox"
|
||||
else
|
||||
FIREFOX_DIR="$HOME/.mozilla/firefox"
|
||||
fi
|
||||
if [ -d "$FIREFOX_DIR" ]; then
|
||||
echo " ✓ Директория Firefox найдена: $FIREFOX_DIR"
|
||||
else
|
||||
echo " ✗ Директория Firefox не найдена: $FIREFOX_DIR"
|
||||
echo " Попробуйте запустить скрипт БЕЗ sudo: ./get_youtube_cookies.sh"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
Flask==3.0.0
|
||||
flask-cors==4.0.0
|
||||
yt-dlp>=2025.12.01
|
||||
yt-dlp>=2026.7.4
|
||||
gunicorn==21.2.0
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue