videoDownloadTGbot/instagram-downloader/app.py

312 lines
15 KiB
Python
Raw Normal View History

"""
Instagram Video Downloader Service
Отдельный микросервис для скачивания видео с Instagram
"""
import os
import logging
import time
from pathlib import Path
from flask import Flask, request, jsonify
from flask_cors import CORS
import yt_dlp
import uuid
import re
# Настройка логирования
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app) # Разрешаем CORS для взаимодействия с основным ботом
# Директория для временных файлов
DOWNLOADS_DIR = Path('downloads')
DOWNLOADS_DIR.mkdir(exist_ok=True)
def check_instagram_cookies_expiry() -> tuple[bool, int]:
"""
Проверяет срок действия Instagram cookies
Returns: (is_valid, days_until_expiry)
"""
cookies_file = os.getenv('INSTAGRAM_COOKIES_FILE', 'instagram_cookies.txt')
cookies_file_path = Path(cookies_file)
if not cookies_file_path.exists():
return False, 0
try:
current_time = time.time()
valid_expiries = []
2025-12-20 22:17:20 +03:00
important_cookies_found = {'sessionid': False, 'csrftoken': False, 'ds_user_id': False}
# Важные cookies для Instagram (проверяем их в первую очередь)
important_cookies = ['sessionid', 'csrftoken', 'ds_user_id']
with open(cookies_file_path, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split('\t')
if len(parts) >= 7:
domain = parts[0]
if 'instagram' in domain.lower():
try:
expiry = int(parts[4]) # Unix timestamp
cookie_name = parts[5] if len(parts) > 5 else ''
2025-12-20 22:17:20 +03:00
# Отмечаем найденные важные cookies
if cookie_name in important_cookies:
important_cookies_found[cookie_name] = True
# Игнорируем невалидные expiry (0, отрицательные, или слишком старые)
# Session cookies (expiry = 0) также игнорируем для проверки срока
if expiry > 0 and expiry > 946684800: # Фильтр: после 2000-01-01 (избегаем epoch 0)
# Для важных cookies проверяем строже
if cookie_name in important_cookies:
if expiry > current_time:
valid_expiries.append(expiry)
else:
valid_expiries.append(expiry)
except (ValueError, IndexError):
continue
2025-12-20 22:17:20 +03:00
# Если найдены важные cookies, но их expiry истекли - все равно считаем валидными
# (Instagram может принимать истекшие cookies, если они еще работают на сервере)
if any(important_cookies_found.values()):
if not valid_expiries:
# Важные cookies найдены, но expiry истекли - считаем валидными на 30 дней
logger.info("Важные Instagram cookies найдены, но expiry истекли. Пробуем использовать их.")
return True, 30
if not valid_expiries:
logger.warning("Не найдено валидных Instagram cookies с нормальным сроком действия")
# Если нет валидных expiry, но есть cookies - считаем их действительными
# (возможно, это session cookies)
return True, 30 # Возвращаем разумное значение по умолчанию
# Берем минимальный валидный expiry
min_expiry = min(valid_expiries)
days_until_expiry = (min_expiry - current_time) / 86400
is_valid = min_expiry > current_time
return is_valid, int(days_until_expiry)
except Exception as e:
logger.error(f"Ошибка при проверке срока действия cookies: {e}")
# В случае ошибки считаем cookies действительными (не блокируем работу)
return True, 30
def download_instagram_video(url: str, max_retries: int = 3) -> Path:
"""Скачивает видео с Instagram - используем cookies с правильными заголовками"""
cookies_file = os.getenv('INSTAGRAM_COOKIES_FILE', 'instagram_cookies.txt')
cookies_file_path = Path(cookies_file)
# Проверяем срок действия cookies перед использованием
if cookies_file_path.exists():
is_valid, days_left = check_instagram_cookies_expiry()
if not is_valid:
2025-12-20 22:17:20 +03:00
logger.warning("Instagram cookies истекли по сроку, но пробуем использовать их (возможно, они еще работают на сервере)")
elif days_left < 7:
logger.warning(f"Instagram cookies истекают через {days_left} дней. Рекомендуется обновить.")
# Парсим cookies для получения csrf token (формат Netscape)
csrf_token = None
sessionid = None
if cookies_file_path.exists():
try:
with open(cookies_file_path, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split('\t')
if len(parts) >= 7:
domain = parts[0]
# Ищем только cookies от instagram.com
if 'instagram' in domain.lower():
cookie_name = parts[5] # Имя cookie
cookie_value = parts[6] # Значение cookie
if cookie_name == 'csrftoken':
csrf_token = cookie_value
elif cookie_name == 'sessionid':
sessionid = cookie_value
# Если нашли оба - можно выходить
if csrf_token and sessionid:
break
except Exception as e:
logger.warning(f"Не удалось прочитать cookies: {e}")
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
def _build_opts(use_cookies: bool) -> dict:
ydl_opts = {
'format': 'best',
'outtmpl': str(DOWNLOADS_DIR / f'{uuid.uuid4()}_%(title)s.%(ext)s'),
'quiet': False,
'no_warnings': False,
'socket_timeout': 30,
}
if use_cookies:
ydl_opts['cookiefile'] = str(cookies_file_path.absolute())
headers = {
'Referer': 'https://www.instagram.com/',
'X-Requested-With': 'XMLHttpRequest',
}
if csrf_token:
headers['X-CSRFToken'] = csrf_token
ydl_opts['http_headers'] = headers
return ydl_opts
# Протухшая сессия заставляет yt-dlp ходить в API, который отдаёт 404, тогда как
# анонимный путь через веб-страницу для публичных постов работает. Поэтому после
# неудачи с cookies пробуем без них — как это давно делает YouTube-загрузчик.
cookie_modes = [True, False] if cookies_file_path.exists() else [False]
last_error = None
for attempt in range(max_retries):
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
for use_cookies in cookie_modes:
mode = "с cookies" if use_cookies else "без cookies"
try:
logger.info(f"Instagram: скачивание {mode} (попытка {attempt + 1}/{max_retries})")
with yt_dlp.YoutubeDL(_build_opts(use_cookies)) as ydl:
ydl.download([url])
# Находим скачанный файл
downloaded_files = list(DOWNLOADS_DIR.glob('*'))
if downloaded_files:
downloaded_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)
return downloaded_files[0]
raise Exception("Файл не был найден после скачивания")
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
except Exception as e:
last_error = e
logger.warning(f"Instagram: {mode}, попытка {attempt + 1}/{max_retries} не удалась: {e}")
if attempt < max_retries - 1:
time.sleep((attempt + 1) * 2)
raise last_error or Exception("Неизвестная ошибка при скачивании с Instagram. Возможно, нужно обновить cookies.")
@app.route('/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({'status': 'ok', 'service': 'instagram-downloader'}), 200
INSTAGRAM_COOKIE_TEST_URL = os.getenv(
'INSTAGRAM_COOKIE_TEST_URL',
'https://www.instagram.com/reel/Cvuh19eNWv_/' # стабильный публичный ролик (NASA)
)
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
def _probe_extract(url: str, cookies_file: Path | None) -> tuple[bool, str]:
"""Пробует достать метаданные (без скачивания). Возвращает (получилось, текст ошибки)."""
ydl_opts = {'quiet': True, 'no_warnings': True, 'socket_timeout': 20}
if cookies_file is not None:
ydl_opts['cookiefile'] = str(cookies_file.absolute())
ydl_opts['http_headers'] = {'Referer': 'https://www.instagram.com/'}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.extract_info(url, download=False)
return True, ''
except Exception as e:
return False, str(e)
fix(downloaders): Deno runtime, свежий yt-dlp, честный health-check, смоук-тест Возрастные 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>
2026-07-26 00:31:16 +00:00
@app.route('/cookies/check', methods=['POST'])
def cookies_check():
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
"""Проверяет, способен ли сервис прямо сейчас скачать публичное видео.
Намеренно повторяет эффективный путь download_instagram_video: сначала с cookies,
при неудаче без них. Известный баг yt-dlp (эндпоинт media/{id}/info/ отдаёт 404
вместо редиректа на логин даже с валидной sessionid, из-за чего внутренний фоллбэк
yt-dlp не срабатывает) означает, что путь с cookies может не проходить всегда
но раз наш собственный фоллбэк без cookies его закрывает, для пользователя это не
авария и не повод слать алерт "куки протухли" (обновление 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,
fix(downloaders): Deno runtime, свежий yt-dlp, честный health-check, смоук-тест Возрастные 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>
2026-07-26 00:31:16 +00:00
'status': 'no_cookies',
'detail': 'cookies file missing'}), 200
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
ok_with, err_with = _probe_extract(INSTAGRAM_COOKIE_TEST_URL, cookies_file)
if ok_with:
fix(downloaders): Deno runtime, свежий yt-dlp, честный health-check, смоук-тест Возрастные 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>
2026-07-26 00:31:16 +00:00
return jsonify({'cookies_present': True, 'cookies_valid': True,
'status': 'ok', 'detail': ''}), 200
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
ok_without, err_without = _probe_extract(INSTAGRAM_COOKIE_TEST_URL, None)
if ok_without:
# Путь с cookies сломан, но эффективное скачивание всё равно работает через
# фоллбэк — так же, как реальный download_instagram_video. Не поднимаем тревогу.
detail = f"cookies-путь сломан, но fallback без cookies работает: {err_with[-400:]}"
fix(downloaders): Deno runtime, свежий yt-dlp, честный health-check, смоук-тест Возрастные 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>
2026-07-26 00:31:16 +00:00
return jsonify({'cookies_present': True, 'cookies_valid': None,
fix(instagram): смягчить /cookies/check, честное сообщение для гейтованного контента /cookies/check раньше тестировал только путь с cookies и при неудаче писал cookies_invalid — даже когда реальное скачивание прекрасно работает через app-level fallback без cookies. Теперь проверка повторяет этот же эффективный путь: cookies path сломан известным багом yt-dlp (media/info всегда 404 вместо редиректа на логин), но раз fallback его закрывает — это не авария и не повод слать ложный алерт "куки протухли", который обновление cookies всё равно не лечит. Если не работает и анонимный путь (который cookies не использует) — это extraction_failed, а не cookies_invalid. Отдельно: когда Instagram сам отказывает показывать контент анонимным/ несовместимым аккаунтам ("can't be seen by certain audiences") — это легитимный отказ, а не наша поломка, и повторная попытка не поможет. Пользователь теперь видит это прямо, а не общее "Something went wrong". Админ по-прежнему получает полный технический текст без изменений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 10:47:41 +00:00
'status': 'ok', 'detail': detail}), 200
# Не работает и анонимный путь, который cookies вообще не использует —
# значит дело не в cookies, а в более общей поломке (сайт/экстрактор/сеть).
return jsonify({'cookies_present': True, 'cookies_valid': None,
'status': 'extraction_failed', 'detail': err_without[-500:]}), 200
@app.route('/download/stream', methods=['POST'])
def download_stream():
"""Скачивает видео с Instagram и возвращает бинарные данные"""
try:
data = request.get_json()
if not data or 'url' not in data:
return jsonify({'error': 'URL is required'}), 400
url = data['url']
logger.info(f"Получен запрос на скачивание (stream): {url}")
# Проверяем, что это Instagram URL
if 'instagram.com' not in url:
return jsonify({'error': 'Only Instagram URLs are supported'}), 400
# Скачиваем видео
video_path = download_instagram_video(url)
logger.info(f"Видео скачано: {video_path}")
# Читаем файл и отправляем
with open(video_path, 'rb') as f:
video_data = f.read()
# Безопасное имя файла без кириллицы для заголовка
safe_filename = video_path.name.encode('ascii', 'ignore').decode('ascii') or 'instagram_video.mp4'
if not safe_filename.endswith(('.mp4', '.webm', '.mkv')):
safe_filename = 'instagram_video.mp4'
# Определяем content-type
content_type = 'video/mp4'
if video_path.suffix == '.webm':
content_type = 'video/webm'
elif video_path.suffix == '.mkv':
content_type = 'video/x-matroska'
# Удаляем временный файл
video_path.unlink()
return video_data, 200, {
'Content-Type': content_type,
'Content-Disposition': f'attachment; filename="{safe_filename}"'
}
except Exception as e:
logger.error(f"Ошибка при скачивании: {e}")
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
port = int(os.getenv('PORT', 5000)) # Внутренний порт контейнера
host = os.getenv('HOST', '0.0.0.0')
logger.info(f"Запуск Instagram Downloader сервиса на {host}:{port}")
app.run(host=host, port=port, debug=False)