186 lines
5.4 KiB
Python
186 lines
5.4 KiB
Python
"""
|
|
Telegram bot for viral clip processing.
|
|
Receives YouTube URLs, processes via main.py, sends progress updates.
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import shutil
|
|
import tempfile
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from dotenv import load_dotenv
|
|
from telegram import Update
|
|
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
|
|
|
|
load_dotenv()
|
|
|
|
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
|
|
CHAT_ID = int(os.environ["TELEGRAM_CHAT_ID"])
|
|
NEXTCLOUD_BASE = "/home/ren/nextcloud/html/data/renato97/files/viral_clips"
|
|
SCRIPT_DIR = Path(__file__).parent
|
|
|
|
queue = asyncio.Queue()
|
|
current_job: str | None = None
|
|
|
|
|
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
if update.effective_chat.id != CHAT_ID:
|
|
return
|
|
await update.message.reply_text(
|
|
"🎬 Mandame un link de YouTube y lo proceso.\n"
|
|
"Los clips aparecen en Nextcloud automáticamente."
|
|
)
|
|
|
|
|
|
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
if update.effective_chat.id != CHAT_ID:
|
|
return
|
|
qsize = queue.qsize()
|
|
if current_job:
|
|
name = current_job[:80]
|
|
await update.message.reply_text(f"▶️ Procesando: {name}...\n⏳ En cola: {qsize}")
|
|
elif qsize > 0:
|
|
await update.message.reply_text(f"⏳ En cola: {qsize}")
|
|
else:
|
|
await update.message.reply_text("💤 Sin actividad.")
|
|
|
|
|
|
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
if update.effective_chat.id != CHAT_ID:
|
|
return
|
|
text = update.message.text.strip()
|
|
if not re.search(r"(youtube\.com|youtu\.be)", text):
|
|
await update.message.reply_text("❌ Mandame un link de YouTube.")
|
|
return
|
|
qsize = queue.qsize()
|
|
await queue.put(text)
|
|
if qsize == 0 and not current_job:
|
|
await update.message.reply_text("🚀 Iniciando procesamiento...")
|
|
else:
|
|
await update.message.reply_text(f"⏳ Encolado ({qsize + 1}º en cola).")
|
|
|
|
|
|
def get_video_title(url: str) -> str:
|
|
try:
|
|
r = subprocess.run(
|
|
["yt-dlp", "--print", "title", url],
|
|
capture_output=True, text=True, timeout=30,
|
|
)
|
|
return r.stdout.strip()
|
|
except Exception:
|
|
return "video"
|
|
|
|
|
|
async def process_queue(bot):
|
|
global current_job
|
|
while True:
|
|
url = await queue.get()
|
|
current_job = url
|
|
try:
|
|
await process_video(bot, url)
|
|
except Exception as e:
|
|
try:
|
|
await bot.send_message(CHAT_ID, f"❌ Error procesando: {e}")
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
current_job = None
|
|
queue.task_done()
|
|
|
|
|
|
async def process_video(bot, url: str):
|
|
title = get_video_title(url)
|
|
safe = re.sub(r'[<>:"/\\|?*#\s]+', "_", title)[:80]
|
|
folder = f"{safe}_{datetime.now():%Y-%m-%d}"
|
|
target = f"{NEXTCLOUD_BASE}/{folder}"
|
|
os.makedirs(target, exist_ok=True)
|
|
|
|
msg = await bot.send_message(CHAT_ID, "🚀 Descargando...")
|
|
|
|
env = os.environ.copy()
|
|
env["NEXTCLOUD_TARGET"] = target
|
|
temp_dir = tempfile.mkdtemp(prefix="viral_")
|
|
|
|
total_clips = 0
|
|
cur_clip = 0
|
|
last_text = ""
|
|
|
|
try:
|
|
proc = await asyncio.create_subprocess_exec(
|
|
sys.executable,
|
|
"-u",
|
|
str(SCRIPT_DIR / "main.py"),
|
|
"-u",
|
|
url,
|
|
"-o",
|
|
temp_dir,
|
|
env=env,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.STDOUT,
|
|
)
|
|
|
|
async for raw in proc.stdout:
|
|
line = raw.decode("utf-8", errors="replace").strip()
|
|
if not line:
|
|
continue
|
|
|
|
text = None
|
|
if "Transcribing" in line:
|
|
text = "🎙️ Transcribiendo..."
|
|
elif "Calling local LLM" in line:
|
|
text = "🤖 Analizando clips virales..."
|
|
elif m := re.search(r"Found\s+(\d+)\s+viral", line, re.IGNORECASE):
|
|
total_clips = int(m.group(1))
|
|
text = f"🔥 Encontrados {total_clips} clips"
|
|
elif m := re.search(r"\bClip\s+(\d+)\]\s+Starting", line):
|
|
cur_clip = int(m.group(1))
|
|
text = f"🎬 Clip {cur_clip}/?: reencuadrando + subs..."
|
|
elif "Normalizing" in line and cur_clip > 0:
|
|
text = f"📱 Clip {cur_clip}/{max(total_clips, cur_clip)}: normalizando..."
|
|
elif "Copying to Nextcloud" in line and cur_clip > 0:
|
|
text = f"📤 Clip {cur_clip}/{max(total_clips, cur_clip)}: subiendo a Nextcloud..."
|
|
|
|
if text and text != last_text:
|
|
last_text = text
|
|
try:
|
|
await msg.edit_text(text)
|
|
except Exception:
|
|
pass
|
|
|
|
await proc.wait()
|
|
|
|
finally:
|
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
|
|
try:
|
|
await msg.delete()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
async def post_init(app: Application):
|
|
asyncio.create_task(process_queue(app.bot))
|
|
|
|
|
|
def main():
|
|
app = (
|
|
Application.builder()
|
|
.token(BOT_TOKEN)
|
|
.post_init(post_init)
|
|
.build()
|
|
)
|
|
app.add_handler(CommandHandler("start", start))
|
|
app.add_handler(CommandHandler("status", status))
|
|
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
|
print("🤖 Viral Telegram Bot iniciado.")
|
|
app.run_polling(allowed_updates=Update.ALL_TYPES)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|