From 1c6bd113fdc3f8a33738c7e6e5ace8893de82c41 Mon Sep 17 00:00:00 2001 From: juanjo Date: Sat, 5 Sep 2026 22:39:57 +0200 Subject: [PATCH] 05-sep: [feat] salir de sesion (/salir), repo por defecto (/defecto) y limpiar pantalla (/limpiar) en ambos bots --- vigilancia/menu_idd.py | 2 + vigilancia/pi_bot.py | 46 +++++++++++++--- vps_opencode_bot/opencode_bot.py | 95 ++++++++++++++++++++++++++++---- 3 files changed, 124 insertions(+), 19 deletions(-) diff --git a/vigilancia/menu_idd.py b/vigilancia/menu_idd.py index 39a2b5d..d3b986f 100644 --- a/vigilancia/menu_idd.py +++ b/vigilancia/menu_idd.py @@ -154,6 +154,7 @@ def teclado_sistema(): _btn("📸 Panorama", PREFIJO + "acc:panorama"), _btn("🧹 Limpieza", PREFIJO + "acc:limpieza"), ], + [_btn("🗑️ Limpiar pantalla", PREFIJO + "acc:limpiar")], FILA_VOLVER, ]) @@ -399,6 +400,7 @@ def _ejecutar_accion(chat_id, message_id, partes): "temperatura": "temperatura", "panorama": "panorama", "limpieza": "limpieza", + "limpiar": "limpiar", "aimesh": "aiMesh estado", "qos": "qos estado", "limite": "limite listar", diff --git a/vigilancia/pi_bot.py b/vigilancia/pi_bot.py index cae17b5..f80429a 100644 --- a/vigilancia/pi_bot.py +++ b/vigilancia/pi_bot.py @@ -18,6 +18,7 @@ panel_descargas = {} # chat_id -> estado del panel editable temporizadores_parental = {} # alias normalizado -> Timer de desbloqueo menu_pendiente = {} # chat_id -> accion de texto libre esperada (menu botones) menu_pausa_nombre = {} # chat_id -> nombre elegido en flujo de pausa +historial_chat = {} # chat_id -> [message_id...] enviados por el bot (para /limpiar) BASE = os.path.dirname(os.path.abspath(__file__)) LOG = os.path.join(BASE, "bot.log") @@ -98,6 +99,10 @@ def enviar(texto, chat_id): d = r.json() if d.get("ok"): ultimo_id = d["result"].get("message_id") + lista = historial_chat.setdefault(chat_id, []) + lista.append(ultimo_id) + if len(lista) > 300: + historial_chat[chat_id] = lista[-300:] else: log(f"Telegram sendMessage error: {d.get('description', d)}") except Exception as e: @@ -267,6 +272,26 @@ def borrar(chat_id, message_id): print("Error borrando:", e) +def limpiar_pantalla(chat_id): + """Borra los mensajes que el bot ha enviado en este chat (/limpiar).""" + lista = historial_chat.pop(chat_id, None) or [] + borrados = 0 + for mid in reversed(lista): + try: + r = requests.post( + f"https://api.telegram.org/bot{TOKEN}/deleteMessage", + data={"chat_id": chat_id, "message_id": mid}, + timeout=8, + ) + if r.json().get("ok"): + borrados += 1 + except Exception: + pass + aviso = enviar(f"🧹 Pantalla limpia ({borrados} mensajes borrados).", chat_id) + if aviso: + threading.Timer(6.0, borrar, args=(chat_id, aviso)).start() + + def mandar_tv(mensaje, simple): try: url = f"{TV_API}?texto={urllib.parse.quote(mensaje)}" @@ -374,15 +399,18 @@ def procesar(texto, chat_id): log(f"panorama: {r.stdout.strip()[-200:]}") except Exception as e: log(f"panorama error: {e}") - elif t in ("limpieza", "/limpieza", "limpiar", "/limpiar"): - enviar("🧹 Ejecutando limpieza...", chat_id) - try: - from limpiar import limpiar - resultado = limpiar() - enviar(f"
{resultado}
", chat_id) - except Exception as e: - log(f"limpieza error: {e}") - enviar(f"❌ Error: {e}", chat_id) + elif t in ("limpieza", "/limpieza", "limpiar", "/limpiar", "clear", "/clear"): + if t in ("limpieza", "/limpieza"): + enviar("🧹 Ejecutando limpieza de procesos...", chat_id) + try: + from limpiar import limpiar + resultado = limpiar() + enviar(f"
{resultado}
", chat_id) + except Exception as e: + log(f"limpieza error: {e}") + enviar(f"❌ Error: {e}", chat_id) + else: + limpiar_pantalla(chat_id) elif t.startswith("youtube ") or t.startswith("/youtube "): url = texto.split(" ", 1)[1].strip() if not url: diff --git a/vps_opencode_bot/opencode_bot.py b/vps_opencode_bot/opencode_bot.py index f1844f7..c3db909 100644 --- a/vps_opencode_bot/opencode_bot.py +++ b/vps_opencode_bot/opencode_bot.py @@ -52,6 +52,7 @@ AUTH = (OC_USER, OC_PASS) bloqueos = {} # chat_id -> True (procesando mensaje) sesiones = {} # chat_id -> {'repo': slug, 'session': id, 'model': obj|None} repos_cache = [] # lista de {'slug','dir'} +sentidos = {} # chat_id -> [message_id...] enviados por el bot (para /limpiar) def log(linea): @@ -101,10 +102,24 @@ def tg(method, data=None, files=None): return {"ok": False} +def _registrar(chat_id, respuesta): + try: + if respuesta and respuesta.get("ok"): + mid = (respuesta.get("result") or {}).get("message_id") + if mid: + lista = sentidos.setdefault(str(chat_id), []) + lista.append(mid) + if len(lista) > 300: + sentidos[str(chat_id)] = lista[-300:] + except Exception: + pass + + def enviar(chat_id, texto, parse="HTML"): for i in range(0, len(texto), 3900): - tg("sendMessage", {"chat_id": chat_id, "text": texto[i:i + 3900], - "parse_mode": parse}) + _registrar(chat_id, tg("sendMessage", + {"chat_id": chat_id, "text": texto[i:i + 3900], + "parse_mode": parse})) time.sleep(0.3) @@ -113,8 +128,9 @@ def enviar_doc(chat_id, ruta, caption=None): return try: with open(ruta, "rb") as f: - tg("sendDocument", {"chat_id": chat_id, "caption": caption}, - {"document": (os.path.basename(ruta), f)}) + _registrar(chat_id, tg("sendDocument", + {"chat_id": chat_id, "caption": caption}, + {"document": (os.path.basename(ruta), f)})) except Exception as e: log(f"doc error {ruta}: {e}") @@ -124,8 +140,10 @@ def teclado(filas): def tg_con_teclado(chat_id, texto, filas): - tg("sendMessage", {"chat_id": chat_id, "text": texto, "parse_mode": "HTML", - "reply_markup": json.dumps(teclado(filas))}) + _registrar(chat_id, tg("sendMessage", + {"chat_id": chat_id, "text": texto, + "parse_mode": "HTML", + "reply_markup": json.dumps(teclado(filas))})) def editar(chat_id, mid, texto, filas=None): @@ -425,9 +443,11 @@ def cmd_ayuda(chat): "🤖 OpenCode por Telegram\n" "Escribe cualquier cosa y lo hará el agente en el repo activo.\n\n" "Comandos:\n" - "/nueva [texto] — nueva sesión (borra contexto)\n" - "/proyecto — elegir repo de trabajo\n" + "/nueva [texto] — sesión nueva (olvida el contexto)\n" + "/salir — cerrar la sesión actual (la próxima arranca limpia)\n" + "/proyecto / /defecto — elegir repo (queda por defecto)\n" "/modelo — elegir modelo\n" + "/limpiar — limpiar la pantalla (borra mensajes del bot)\n" "/estado — repo y modelo actuales\n" "/ayuda — esta ayuda\n\n" "Los ficheros que cree te llegarán como documentos." @@ -463,6 +483,56 @@ def cmd_nueva(chat, resto): enviar(chat, "🧠 Sesión nueva creada. Dime qué hago.") +def cmd_salir(chat): + """Sale de la sesion actual: la cierra y avisa que la proxima sera nueva.""" + st = sesiones.get(str(chat)) + if st and st.get("session"): + try: + oc_post(f"/session/{st['session']}/abort", {}) + except Exception: + pass + sid, _ = crear_sesion() + slug, _ = repo_activo() + st["session"] = sid + sesiones[str(chat)] = st + guardar_estado() + enviar(chat, "🚪 Sesión anterior cerrada. La próxima petición arranca " + "en una sesión nueva sin contexto.") + else: + enviar(chat, "No había ninguna sesión abierta.") + + +def cmd_limpiar(chat): + """Borra los mensajes que el bot ha enviado en este chat.""" + lista = sentidos.get(str(chat)) or [] + borrados = 0 + for mid in reversed(lista): + try: + r = requests.post(f"{API}/deleteMessage", + data={"chat_id": chat, "message_id": mid}, + timeout=15) + if r.json().get("ok"): + borrados += 1 + except Exception: + pass + sentidos[str(chat)] = [] + enviar(chat, f"🧹 Pantalla limpia ({borrados} mensajes borrados).") + # borra tambien este aviso al cabo de unos segundos + threading.Timer(6.0, _autolimpia, args=(chat,)).start() + + +def _autolimpia(chat): + lista = sentidos.get(str(chat)) or [] + for mid in reversed(lista[:1]): + try: + requests.post(f"{API}/deleteMessage", + data={"chat_id": chat, "message_id": mid}, + timeout=10) + except Exception: + pass + sentidos[str(chat)] = [m for m in lista if m != mid] + + def cmd_proyecto(chat): lista = listar_repos() if not lista: @@ -474,7 +544,8 @@ def cmd_proyecto(chat): marca = " ✅" if r["slug"] == slug_act else "" filas.append([{"text": r["slug"] + marca, "callback_data": f"cb:repo:{i}"}]) - tg_con_teclado(chat, "📂 Elige repo de trabajo:", filas) + tg_con_teclado(chat, "📂 Elige repo de trabajo. Quedará fijado como " + "repo por defecto del bot (reinicia el serve):", filas) def cmd_modelo(chat): @@ -556,7 +627,11 @@ def procesar(texto, chat): cmd_ayuda(chat) elif comando in ("/nueva", "/nuevo"): cmd_nueva(chat, resto) - elif comando in ("/proyecto", "/repo"): + elif comando in ("/salir", "/fin", "/exit"): + cmd_salir(chat) + elif comando in ("/limpiar", "/clear"): + cmd_limpiar(chat) + elif comando in ("/proyecto", "/repo", "/defecto"): cmd_proyecto(chat) elif comando == "/modelo": cmd_modelo(chat)