05-sep: [infra] configurar Raspberry Pi y documentar flota

This commit is contained in:
juanjo
2026-09-05 15:46:47 +02:00
parent 1e0ed24663
commit 770b9f7ee2
6 changed files with 311 additions and 8 deletions

View File

@@ -5,6 +5,7 @@ import sys
import threading
import time
import urllib.parse
import json
import requests
@@ -13,6 +14,7 @@ ultima_titulos = {} # chat_id -> {'query':..., 'results':[...]}
ultimos_torrents = {} # chat_id -> {'titulo':..., 'torrents':[...]}
flujo_genero = {} # chat_id -> 'esperando_tipo' | 'esperando_genero' | None
flujo_genero_tipo = {} # chat_id -> 'movie' | 'series' (tipo ya elegido)
panel_descargas = {} # chat_id -> estado del panel editable
BASE = os.path.dirname(os.path.abspath(__file__))
LOG = os.path.join(BASE, "bot.log")
@@ -100,6 +102,148 @@ def enviar(texto, chat_id):
return ultimo_id
def teclado_descargas():
return {
"inline_keyboard": [
[
{"text": "🔄 Actualizar", "callback_data": "dl:refresh"},
{"text": "▶️ Activas", "callback_data": "dl:active"},
],
[
{"text": "📁 Todas", "callback_data": "dl:all"},
{"text": "✖️ Cerrar", "callback_data": "dl:close"},
],
]
}
def enviar_panel_descargas(texto, chat_id):
try:
r = requests.post(
f"https://api.telegram.org/bot{TOKEN}/sendMessage",
data={
"chat_id": chat_id,
"text": texto,
"parse_mode": "HTML",
"reply_markup": json.dumps(teclado_descargas()),
},
timeout=10,
)
d = r.json()
if d.get("ok"):
return d["result"].get("message_id")
log(f"Telegram panel send error: {d.get('description', d)}")
except Exception as e:
log(f"Telegram panel send exception: {e}")
return None
def editar_panel_descargas(chat_id, message_id, texto):
try:
r = requests.post(
f"https://api.telegram.org/bot{TOKEN}/editMessageText",
data={
"chat_id": chat_id,
"message_id": message_id,
"text": texto,
"parse_mode": "HTML",
"reply_markup": json.dumps(teclado_descargas()),
},
timeout=10,
)
d = r.json()
if not d.get("ok") and "not modified" not in d.get("description", "").lower():
log(f"Telegram panel edit error: {d.get('description', d)}")
return d.get("ok", False)
except Exception as e:
log(f"Telegram panel edit exception: {e}")
return False
def responder_callback(callback_id):
try:
requests.post(
f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
data={"callback_query_id": callback_id},
timeout=10,
)
except Exception as e:
log(f"Telegram callback error: {e}")
def texto_panel_descargas(modo="active"):
import nas_dl
lineas, activas = nas_dl.listar_descargas(
hablar_solo_activas=(modo == "active")
)
encabezado = "📥 <b>DESCARGAS DEL NAS</b>"
if modo == "active":
encabezado += " · ACTIVAS"
texto = encabezado + "\n\n" + "\n".join(lineas)
texto += f"\n\n🕒 Actualizado: <code>{time.strftime('%H:%M:%S')}</code>"
return texto, activas
def abrir_panel_descargas(chat_id):
anterior = panel_descargas.pop(chat_id, None)
if anterior:
anterior["stop"].set()
try:
texto, _activas = texto_panel_descargas()
message_id = enviar_panel_descargas(texto, chat_id)
if not message_id:
enviar("❌ No se pudo abrir el panel de descargas.", chat_id)
return
estado_panel = {
"message_id": message_id,
"modo": "active",
"stop": threading.Event(),
}
panel_descargas[chat_id] = estado_panel
threading.Thread(
target=monitor_panel_descargas,
args=(chat_id, estado_panel),
daemon=True,
).start()
except Exception as e:
log(f"panel descargas error: {e}")
enviar(f"❌ Error en el panel de descargas: {e}", chat_id)
def monitor_panel_descargas(chat_id, estado_panel):
while not estado_panel["stop"].wait(20):
try:
texto, activas = texto_panel_descargas(estado_panel["modo"])
editar_panel_descargas(chat_id, estado_panel["message_id"], texto)
if not activas:
break
except Exception as e:
log(f"monitor panel descargas error: {e}")
def procesar_callback(callback):
callback_id = callback.get("id")
mensaje = callback.get("message") or {}
chat_id = (mensaje.get("chat") or {}).get("id")
accion = (callback.get("data") or "").split(":", 1)[-1]
responder_callback(callback_id)
estado_panel = panel_descargas.get(chat_id)
if not estado_panel:
return
if accion == "close":
estado_panel["stop"].set()
panel_descargas.pop(chat_id, None)
borrar(chat_id, estado_panel["message_id"])
return
if accion in ("active", "all"):
estado_panel["modo"] = "active" if accion == "active" else "all"
try:
texto, _activas = texto_panel_descargas(estado_panel["modo"])
editar_panel_descargas(chat_id, estado_panel["message_id"], texto)
except Exception as e:
log(f"callback panel descargas error: {e}")
def borrar(chat_id, message_id):
try:
requests.post(
@@ -213,6 +357,8 @@ def procesar(texto, chat_id):
else:
ok = mandar_youtube(url)
enviar("🎬 YouTube lanzado en la TV ✅" if ok else "❌ No se pudo lanzar YouTube", chat_id)
elif t in ("descargas", "/descargas") or (comando == "descargar" and not argumento):
abrir_panel_descargas(chat_id)
elif comando == "descargar":
try:
import nas_dl
@@ -220,11 +366,6 @@ def procesar(texto, chat_id):
ok = nas_dl.add_torrent(argumento)
enviar("📥 Añadido a Download Station del NAS ✅" if ok
else "❌ No se pudo añadir al NAS. Revisa <code>bot.log</code>.", chat_id)
else:
# Incluye también pendientes, pausadas y terminadas para que
# el usuario pueda ver el estado real de cada torrent.
lineas, _act = nas_dl.listar_descargas(hablar_solo_activas=False)
enviar("\n".join(lineas), chat_id)
except Exception as e:
log(f"descargar error: {e}")
enviar(f"❌ Error en Download Station: {e}", chat_id)
@@ -269,8 +410,8 @@ def procesar(texto, chat_id):
"<b>7.</b> <code>novedades</code> — últimas novedades del catálogo\n"
" ✏️ Ej: <code>/novedades</code>\n\n"
"━━━ 🖥️ NAS / DESCARGAS ━━━\n"
"<b>8.</b> <code>descargar</code> — torrents descargándose y su progreso\n"
" ✏️ Ej: <code>/descargar</code>\n"
"<b>8.</b> <code>descargas</code> — panel con progreso y botones\n"
" ✏️ Ej: <code>/descargas</code> o <code>/descargar</code>\n"
"<b>9.</b> <code>descargar &lt;enlace&gt;</code> — añade a Download Station\n"
" ✏️ Ej: <code>/descargar magnet:?xt=urn:btih:...</code>\n\n"
"━━━ 📺 TV ━━━\n"
@@ -646,6 +787,9 @@ def main():
offset = upd["update_id"] + 1
guardar_offset(offset)
log(f"UPDATE {upd.get('update_id')}")
if upd.get("callback_query"):
procesar_callback(upd["callback_query"])
continue
msg = upd.get("message") or upd.get("channel_post")
if not msg:
continue