From b67d3f3a82f6959d12f05ed2a48f5a17c225375f Mon Sep 17 00:00:00 2001 From: Renato Date: Fri, 3 Jul 2026 17:11:58 +0200 Subject: [PATCH] feat: implement downloading and processing of Kick and Twitch URLs --- openshorts/main.py | 8 +++---- openshorts/telegram_bot.py | 47 ++++++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/openshorts/main.py b/openshorts/main.py index aa15fe1..0631dd1 100644 --- a/openshorts/main.py +++ b/openshorts/main.py @@ -473,7 +473,7 @@ def download_youtube_video(url, output_dir="."): import time import sys - print("📥 Downloading video from YouTube using custom solver...") + print("📥 Downloading video using custom solver...") step_start_time = time.time() # Ensure output_dir is absolute path @@ -492,14 +492,14 @@ def download_youtube_video(url, output_dir="."): result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if result.returncode != 0: - print("🚨 YOUTUBE DOWNLOAD ERROR 🚨", file=sys.stderr) + print("🚨 DOWNLOAD ERROR 🚨", file=sys.stderr) print(result.stderr, file=sys.stderr) - raise Exception(f"YouTube download failed: {result.stderr}") + raise Exception(f"Video download failed: {result.stderr}") try: output_data = json.loads(result.stdout.strip()) downloaded_file = output_data['filepath'] - video_title = output_data.get('title', 'youtube_video') + video_title = output_data.get('title', 'video') sanitized_title = sanitize_filename(video_title) except Exception as e: print(f"⚠️ JSON parsing failed, trying fallback detection. Error: {e}") diff --git a/openshorts/telegram_bot.py b/openshorts/telegram_bot.py index 736e9d2..26f1416 100644 --- a/openshorts/telegram_bot.py +++ b/openshorts/telegram_bot.py @@ -113,7 +113,7 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE): elif qsize > 0: await update.message.reply_text(f"⏳ {qsize} videos en cola, iniciando pronto...") else: - await update.message.reply_text("💤 Sin actividad. Mandame un link de YouTube.") + await update.message.reply_text("💤 Sin actividad. Mandame un link de YouTube, Twitch o Kick.") async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -125,9 +125,9 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): log.warning(f" Ignoring unauthorized chat_id={chat_id}") return - if not re.search(r"(youtube\.com|youtu\.be)", text): - log.info(" Not a YouTube URL, ignoring.") - await update.message.reply_text("❌ Mandame un link de YouTube (youtube.com o youtu.be).") + if not re.search(r"(youtube\.com|youtu\.be|twitch\.tv|kick\.com)", text): + log.info(" Not a YouTube, Twitch or Kick URL, ignoring.") + await update.message.reply_text("❌ Mandame un link de YouTube, Twitch o Kick.") return qsize = queue.qsize() @@ -206,10 +206,37 @@ async def process_queue(bot): log.info(f"[queue] Job done. Queue size now: {queue.qsize()}") -def get_yt_id(url: str) -> str: - """Extract YouTube video ID from URL.""" - match = re.search(r"(?:v=|\/shorts\/|\/embed\/|\/v\/|youtu\.be\/)([a-zA-Z0-9_-]{11})", url) - return match.group(1) if match else "unknown" +def get_video_id(url: str) -> str: + """Extract unique video ID from YouTube, Twitch, or Kick URL.""" + # 1. YouTube + yt_match = re.search(r"(?:v=|\/shorts\/|\/embed\/|\/v\/|youtu\.be\/)([a-zA-Z0-9_-]{11})", url) + if yt_match: + return yt_match.group(1) + + # 2. Twitch VODs + twitch_vod_match = re.search(r"twitch\.tv\/videos\/(\d+)", url) + if twitch_vod_match: + return f"twitch_vod_{twitch_vod_match.group(1)}" + + # 3. Twitch Clips + twitch_clip_match = re.search(r"(?:clips\.twitch\.tv\/|twitch\.tv\/\w+\/clip\/)([a-zA-Z0-9_-]+)", url) + if twitch_clip_match: + return f"twitch_clip_{twitch_clip_match.group(1)}" + + # 4. Kick VODs (contains UUID) + kick_vod_match = re.search(r"kick\.com\/(?:video\/|(?:\w+\/videos\/))([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})", url) + if kick_vod_match: + return f"kick_vod_{kick_vod_match.group(1)}" + + # 5. Kick Clips + kick_clip_match = re.search(r"kick\.com\/\w+\/clips\/([a-zA-Z0-9_-]+)", url) + if kick_clip_match: + return f"kick_clip_{kick_clip_match.group(1)}" + + # Fallback to md5 hash of URL to ensure uniqueness + import hashlib + url_hash = hashlib.md5(url.encode('utf-8')).hexdigest()[:10] + return f"video_{url_hash}" # ─── Main video processor ───────────────────────────────────────────────────── @@ -218,7 +245,7 @@ async def process_video(bot, url: str): log.info(f"[process] URL: {url}") # 1. Extract video ID and Get title - yt_id = get_yt_id(url) + yt_id = get_video_id(url) title = await get_video_title(url) log.info(f"[process] Video ID: {yt_id}, Title: {title!r}") @@ -298,7 +325,7 @@ async def process_video(bot, url: str): log.debug(f"[main.py] {line[:200]}") # Parse progress markers from main.py output - if "Downloading video from YouTube" in line: + if "Downloading video" in line: await edit_msg( f"🎬 *{title[:50] or 'Video'}*\n\n" f"📥 Descargando..."