reels-app/app/telegram.py
Sebastjan Artič 91cc03658d Multi-upload batch queue + Telegram notifications
Changes:

1. Frontend multi-upload:
   - File input now has 'multiple' attribute, drag-drop accepts multiple
   - File queue list with per-file artist/title preview + remove button
   - 'Pošlji vse' uploads sequentially (one at a time to avoid network saturation)
   - Each file gets same batch_id for Telegram batch summary
   - After upload, queue clears, jobs appear in right sidebar

2. Backend queue worker:
   - New _queue_worker() background thread processes 'queued' jobs sequentially
   - Only 1 job at a time to keep openclaw stable (avoid CPU/RAM thrash)
   - FIFO order by created_at
   - Auto-starts on app startup after job resume

3. Job submission flow change:
   - /api/process and /api/youtube no longer call background.add_task directly
   - Just mark status='queued', queue worker picks up
   - This means upload completes fast, processing happens in background
   - User can close browser, jobs continue

4. Telegram notifications (FOLX Alerts bot):
   - Per-job: 'Reel pripravljen: Lady Gaga - Abracadabra (29s, 30 MB)'
   - Per-job failed: 'Reel ni uspel: <name> + error message'
   - Batch summary: 'Batch končan: 10/10 reels pripravljeni' (only if >1 in batch)
   - Uses existing TELEGRAM_TOKEN + TELEGRAM_CHAT_ID env vars
   - app/telegram.py module with notify_job_done(), notify_job_failed(),
     notify_batch_complete()

5. batch_id field:
   - Added to Job model + StartJobIn pydantic
   - Saved during upload + process
   - Used to count batch progress and trigger summary notification

User experience:
- Drag 20 videos at once
- Click 'Pošlji'
- Close browser, go grab coffee
- Telegram sends 'Reel pripravljen' for each
- After all done: 'Batch končan: 20/20 reels pripravljeni' summary
- Open app to download all
2026-04-29 15:12:38 +00:00

120 lines
3.6 KiB
Python

"""
telegram.py — Telegram bot helper za reels-app.
Pošilja obvestila o končanih jobih, batch summary, napakah.
Credentials se preberejo iz env vars (TELEGRAM_TOKEN, TELEGRAM_CHAT_ID).
"""
import os
import urllib.request
import urllib.parse
import json
def send_message(text, parse_mode="Markdown", disable_notification=False):
"""Pošlji sporočilo na Telegram. Vrne True ob uspehu, False ob napaki.
text: ne sme biti daljši od 4096 znakov.
parse_mode: 'Markdown' ali 'HTML' ali None.
disable_notification: True za tiho obvestilo (brez zvonca).
"""
token = os.environ.get("TELEGRAM_TOKEN")
chat_id = os.environ.get("TELEGRAM_CHAT_ID")
if not token or not chat_id:
return False
# Telegram limit: 4096 chars
if len(text) > 4090:
text = text[:4087] + "..."
data_dict = {
"chat_id": chat_id,
"text": text,
}
if parse_mode:
data_dict["parse_mode"] = parse_mode
if disable_notification:
data_dict["disable_notification"] = "true"
data = urllib.parse.urlencode(data_dict).encode()
req = urllib.request.Request(
f"https://api.telegram.org/bot{token}/sendMessage",
data=data,
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
result = json.loads(resp.read().decode())
return result.get("ok", False)
except Exception as e:
print(f"⚠️ Telegram send failed: {e}", flush=True)
return False
def notify_job_done(job, base_url="https://reels.biba.live"):
"""Obvesti o končanem reelu."""
artist = job.get("parsed_artist", "")
title = job.get("parsed_title", "")
job_id = job.get("id", "")
if artist and title:
name = f"*{artist}{title}*"
elif title:
name = f"*{title}*"
else:
name = f"`{job_id}`"
duration = job.get("duration", 0)
output_size_mb = job.get("output_size_mb") or round((job.get("output_size", 0) / 1024 / 1024), 1)
text = (
f"✅ Reel pripravljen\n\n"
f"{name}\n"
f"{duration:.0f}s · 📦 {output_size_mb} MB\n\n"
f"[Predogled & Download]({base_url})"
)
return send_message(text)
def notify_job_failed(job, error_msg=""):
"""Obvesti o neuspehu joba."""
artist = job.get("parsed_artist", "")
title = job.get("parsed_title", "")
fname = job.get("filename", "")
if artist and title:
name = f"*{artist}{title}*"
elif fname:
name = f"`{fname}`"
else:
name = f"`{job.get('id', '?')}`"
err_short = (error_msg or "neznana napaka")[:200]
text = f"⚠️ Reel ni uspel\n\n{name}\n\n```\n{err_short}\n```"
return send_message(text)
def notify_batch_complete(batch_id, total, succeeded, failed, base_url="https://reels.biba.live"):
"""Obvesti o končanem batch-u (več jobov hkrati)."""
if total <= 1:
return False # ne pošiljaj batch summary za en sam job
if failed == 0:
emoji = "🎉"
head = f"Batch končan: vsi {total} reelov pripravljeni"
else:
emoji = ""
head = f"Batch končan: {succeeded}/{total} uspelih"
if failed > 0:
head += f", {failed} neuspelih"
text = f"{emoji} {head}\n\n[Vsi reels →]({base_url})"
return send_message(text)
def notify_batch_started(batch_id, total):
"""Obvesti o začetku batch-a (samo če > 1 job)."""
if total <= 1:
return False
text = f"🎬 Batch za {total} datotek dodan v vrsto…"
return send_message(text, disable_notification=True)