videoDownloadTGbot/yapfiles-downloader/app.py

238 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Yapfiles Video Downloader Service
Отдельный микросервис для скачивания видео с yapfiles.ru
"""
import os
import logging
import re
import uuid
from pathlib import Path
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
from bs4 import BeautifulSoup
# Настройка логирования
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app)
# Директория для временных файлов
DOWNLOADS_DIR = Path('downloads')
DOWNLOADS_DIR.mkdir(exist_ok=True)
# User-Agent для запросов
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
def extract_download_url(page_url: str) -> tuple[str, str]:
"""
Извлекает прямую ссылку на скачивание видео со страницы yapfiles.ru
Возвращает tuple (download_url, filename)
"""
headers = {
'User-Agent': USER_AGENT,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
'Referer': 'https://www.yapfiles.ru/',
}
logger.info(f"Загружаю страницу: {page_url}")
response = requests.get(page_url, headers=headers, timeout=30)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Ищем ссылку на скачивание в разных местах
download_url = None
filename = 'yapfiles_video.mp4'
# Способ 1: Ищем прямую ссылку в source тегах видеоплеера
video_tag = soup.find('video')
if video_tag:
source = video_tag.find('source')
if source and source.get('src'):
download_url = source['src']
logger.info(f"Найдена ссылка в video/source: {download_url}")
# Способ 2: Ищем ссылку "Скачать файл" или подобную
if not download_url:
download_links = soup.find_all('a', href=True)
for link in download_links:
href = link.get('href', '')
# Ищем ссылки на /files/ с token
if '/files/' in href and 'token=' in href:
download_url = href
if not download_url.startswith('http'):
download_url = 'https://www.yapfiles.ru' + download_url
logger.info(f"Найдена ссылка на скачивание: {download_url}")
break
# Способ 3: Ищем в JavaScript коде
if not download_url:
scripts = soup.find_all('script')
for script in scripts:
script_text = script.string or ''
# Ищем паттерны вида /files/...mp4?token=...
matches = re.findall(r'(https?://[^\s"\'<>]+/files/[^\s"\'<>]+\.mp4\?token=[^\s"\'<>]+)', script_text)
if matches:
download_url = matches[0]
logger.info(f"Найдена ссылка в скрипте: {download_url}")
break
# Или относительные пути
matches = re.findall(r'["\']?(/files/[^\s"\'<>]+\.mp4\?token=[^\s"\'<>]+)["\']?', script_text)
if matches:
download_url = 'https://www.yapfiles.ru' + matches[0]
logger.info(f"Найдена относительная ссылка в скрипте: {download_url}")
break
# Способ 4: Формируем ссылку на основе URL страницы
# URL страницы: https://www.yapfiles.ru/show/3532099/30faa897f5a34bb58c018f909a6f1fae.mp4.html
# URL файла: https://www.yapfiles.ru/files/3532099/30faa897f5a34bb58c018f909a6f1fae.mp4
if not download_url:
match = re.search(r'/show/(\d+)/([^/]+)\.html', page_url)
if match:
file_id = match.group(1)
file_name = match.group(2)
# Пробуем без токена - иногда работает для публичных файлов
download_url = f'https://www.yapfiles.ru/files/{file_id}/{file_name}'
logger.info(f"Сформирована ссылка на основе URL: {download_url}")
if not download_url:
raise Exception("Не удалось найти ссылку на скачивание видео")
# Извлекаем имя файла из URL
url_path = download_url.split('?')[0]
if '/' in url_path:
filename = url_path.split('/')[-1]
if not filename.endswith(('.mp4', '.webm', '.avi', '.mov')):
filename = 'yapfiles_video.mp4'
return download_url, filename
def download_yapfiles_video(url: str, max_retries: int = 3) -> Path:
"""Скачивает видео с yapfiles.ru"""
last_error = None
for attempt in range(max_retries):
try:
# Получаем прямую ссылку на скачивание
download_url, original_filename = extract_download_url(url)
headers = {
'User-Agent': USER_AGENT,
'Accept': '*/*',
'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
'Referer': url,
}
logger.info(f"Yapfiles: скачивание (попытка {attempt + 1}/{max_retries}): {download_url}")
response = requests.get(download_url, headers=headers, stream=True, timeout=120)
response.raise_for_status()
# Проверяем что это действительно видео
content_type = response.headers.get('Content-Type', '')
if 'text/html' in content_type:
raise Exception("Получен HTML вместо видео. Возможно, требуется авторизация или ссылка устарела.")
# Определяем расширение
ext = '.mp4'
if 'video/webm' in content_type:
ext = '.webm'
elif 'video/x-matroska' in content_type:
ext = '.mkv'
# Создаем имя файла
safe_filename = re.sub(r'[<>:"/\\|?*]', '', original_filename)[:100]
if not safe_filename.endswith(ext):
safe_filename = safe_filename.rsplit('.', 1)[0] + ext
output_file = DOWNLOADS_DIR / f'{uuid.uuid4()}_{safe_filename}'
# Сохраняем файл
with open(output_file, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
logger.info(f"Yapfiles: видео скачано: {output_file}")
return output_file
except Exception as e:
last_error = e
logger.warning(f"Yapfiles: попытка {attempt + 1}/{max_retries} не удалась: {e}")
if attempt < max_retries - 1:
import time
time.sleep((attempt + 1) * 2)
raise last_error or Exception("Неизвестная ошибка при скачивании с Yapfiles")
@app.route('/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({'status': 'ok', 'service': 'yapfiles-downloader'}), 200
@app.route('/download/stream', methods=['POST'])
def download_stream():
"""Скачивает видео с Yapfiles и возвращает бинарные данные"""
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}")
# Проверяем, что это Yapfiles URL
if 'yapfiles.ru' not in url:
return jsonify({'error': 'Only Yapfiles URLs are supported'}), 400
# Скачиваем видео
video_path = download_yapfiles_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 'yapfiles_video.mp4'
if not safe_filename.endswith(('.mp4', '.webm', '.mkv')):
safe_filename = 'yapfiles_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"Запуск Yapfiles Downloader сервиса на {host}:{port}")
app.run(host=host, port=port, debug=False)