From a9d1ffc864ad6ef430d28ff9cea3b385c74f7653 Mon Sep 17 00:00:00 2001 From: vrubelroman Date: Mon, 25 May 2026 16:46:35 +0000 Subject: [PATCH] fix: prevent VK downloader from blocking queue --- bot.py | 31 ++++++++++++++++++++++++++++--- vk-downloader/Dockerfile | 7 ++++--- vk-downloader/docker-compose.yml | 7 ++++++- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/bot.py b/bot.py index 81d47ec..25f9e3b 100644 --- a/bot.py +++ b/bot.py @@ -15,9 +15,10 @@ from telegram.request import HTTPXRequest from dataclasses import dataclass from typing import Optional -# Таймаут для HTTP запросов -# Все таймауты убраны - видео может качаться и отправляться очень долго -HTTP_TIMEOUT = httpx.Timeout(connect=None, read=None, write=None, pool=None) +# Таймаут для HTTP запросов к downloader-сервисам. +# Без ограничений один зависший микросервис навсегда блокирует единственный +# queue_worker и вся очередь перестает двигаться. +HTTP_TIMEOUT = httpx.Timeout(connect=10, read=300, write=30, pool=30) # Таймаут для запроса форматов (не такой критичный, но не должен висеть вечно) FORMATS_TIMEOUT = httpx.Timeout(connect=15, read=30, write=15, pool=15) @@ -110,6 +111,7 @@ TEXTS = { 'caption': "Видео скачано с @{bot_username}", 'error': "❌ Произошла ошибка при обработке видео:\n{error}", 'error_unknown_source': "Пардон, не умеем работать с этим источником", + 'error_vk_not_video': "❌ Это ссылка VK, но не на видео. Пришлите ссылку вида vk.com/video... или vk.com/clip...", 'error_file_too_large': "❌ Видео слишком большое ({size_mb:.1f} МБ, max = 50)", 'queue_position': "🕐 Ваше видео #{position} в очереди\nВаш запрос очень важен для нас!", 'queue_first': "⬇️ Скачиваю видео...", @@ -167,6 +169,7 @@ TEXTS = { 'caption': "Video downloaded via @{bot_username}", 'error': "❌ Error processing video:\n{error}", 'error_unknown_source': "Sorry, this source is not supported", + 'error_vk_not_video': "❌ This is a VK link, but not a video. Send a vk.com/video... or vk.com/clip... link.", 'error_file_too_large': "❌ Video is too large ({size_mb:.1f} MB, max = 50)", 'queue_position': "🕐 Your video is #{position} in queue\nYour request is very important to us!", 'queue_first': "⬇️ Downloading video...", @@ -396,6 +399,23 @@ def detect_video_source(url: str) -> str: return 'unknown' +def is_vk_video_url(url: str) -> bool: + """Проверяет, что VK URL ведёт именно на видео/клип, а не на группу/профиль.""" + parsed = urlparse(url) + domain = parsed.netloc.lower() + if not ('vk.com' in domain or 'vk.ru' in domain or 'vkontakte.ru' in domain): + return False + + path = parsed.path.lower().strip('/') + query = parsed.query.lower() + return ( + path.startswith('video') + or path.startswith('clip') + or 'z=video' in query + or 'z=clip' in query + ) + + def extract_urls_from_text(text: str) -> list[str]: """Извлекает все URL из текста сообщения""" url_pattern = r'https?://[^\s<>"{}|\\^`\[\]]+' @@ -1201,6 +1221,11 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): if chat_type == 'private': await update.message.reply_text(get_text(locale, 'unsupported_source')) return + + if source == 'vk' and not is_vk_video_url(url): + if chat_type == 'private': + await update.message.reply_text(get_text(locale, 'error_vk_not_video')) + return # Отправляем сообщение о начале обработки status_message = await update.message.reply_text(get_text(locale, 'processing')) diff --git a/vk-downloader/Dockerfile b/vk-downloader/Dockerfile index d6693ec..84931f8 100644 --- a/vk-downloader/Dockerfile +++ b/vk-downloader/Dockerfile @@ -20,7 +20,8 @@ RUN mkdir -p downloads ENV PYTHONUNBUFFERED=1 -# Gunicorn: 1 worker (последовательная обработка), без таймаута -# Порт берется из переменной окружения PORT (по умолчанию 5000) -CMD sh -c "gunicorn --workers=1 --timeout=0 --bind=0.0.0.0:${PORT:-5000} app:app" +# Gunicorn: несколько worker-ов и конечные таймауты, чтобы один зависший +# клиент или запрос не блокировал весь VK downloader. +# Порт берется из переменной окружения PORT (по умолчанию 5000). +CMD sh -c "gunicorn --workers=${GUNICORN_WORKERS:-2} --threads=${GUNICORN_THREADS:-4} --timeout=${GUNICORN_TIMEOUT:-360} --graceful-timeout=${GUNICORN_GRACEFUL_TIMEOUT:-30} --keep-alive=${GUNICORN_KEEP_ALIVE:-5} --bind=${BIND_HOST:-0.0.0.0}:${PORT:-5000} app:app" diff --git a/vk-downloader/docker-compose.yml b/vk-downloader/docker-compose.yml index c32e6f2..08d368f 100644 --- a/vk-downloader/docker-compose.yml +++ b/vk-downloader/docker-compose.yml @@ -3,8 +3,13 @@ services: build: . container_name: vk_downloader_service restart: unless-stopped - network_mode: host + ports: + - "127.0.0.1:5555:5555" volumes: - ./downloads:/app/downloads environment: - PORT=5555 + - GUNICORN_WORKERS=2 + - GUNICORN_THREADS=4 + - GUNICORN_TIMEOUT=360 + - GUNICORN_KEEP_ALIVE=5