""" telegram_notify.py ------------------ Notifikace a obousměrná komunikace přes Telegram Bot API (bot ClaudeBot @Vlado_Claude_Bot). Token a výchozí chat_id se načítají z `Medevio/.env`: TELEGRAM_BOT_TOKEN=123456789:AAE... TELEGRAM_CHAT_ID=6639316354 Použití ze skriptu: from Knihovny.telegram_notify import posli_telegram, zeptej_se_telegram posli_telegram("Pipeline 08 hotová, 142 záznamů") odpoved = zeptej_se_telegram("Mám reimportovat i archiv? (ano/ne)") if odpoved and odpoved.strip().lower() == "ano": ... Použití z příkazové řádky: python -m Knihovny.telegram_notify "Hotovo" python -m Knihovny.telegram_notify --ask "Pokracovat? (ano/ne)" POZN.: getUpdates smí v jednu chvíli pollovat jen JEDEN proces. Pokud běží víc skriptů naráz, které čekají na odpověď, kradou si navzájem zprávy — v praxi se ptá vždy jen jeden agent. """ import os import sys import time from pathlib import Path import requests # ========================= # Načtení .env (Medevio/.env) # ========================= def _load_env(): env_path = Path(__file__).resolve().parent.parent / "Medevio" / ".env" if env_path.exists(): for line in env_path.read_text(encoding="utf-8").splitlines(): line = line.strip() if "=" in line and not line.startswith("#"): k, v = line.split("=", 1) os.environ.setdefault(k.strip(), v.strip()) _load_env() API_BASE = "https://api.telegram.org/bot{token}/{method}" def _token() -> str: token = os.environ.get("TELEGRAM_BOT_TOKEN") if not token: raise RuntimeError("Chybí TELEGRAM_BOT_TOKEN v Medevio/.env") return token def _resolve_chat_id(chat_id: str | None) -> str: chat_id = chat_id or os.environ.get("TELEGRAM_CHAT_ID") if not chat_id: raise RuntimeError("Chybí TELEGRAM_CHAT_ID (zadej argumentem nebo v Medevio/.env)") return str(chat_id) def _call(method: str, *, http_timeout: int = 15, **params): """Zavolá Telegram Bot API metodu a vrátí pole `result`.""" url = API_BASE.format(token=_token(), method=method) r = requests.post(url, json=params, timeout=http_timeout) data = r.json() if not data.get("ok"): raise RuntimeError(f"Telegram {method} selhal [{r.status_code}]: {data}") return data["result"] def posli_telegram( text: str, *, chat_id: str | None = None, parse_mode: str | None = None, disable_notification: bool = False, ) -> dict: """ Pošle zprávu přes Telegram bota. :param text: text zprávy (max 4096 znaků) :param chat_id: cílový chat; výchozí z TELEGRAM_CHAT_ID :param parse_mode: None | "Markdown" | "MarkdownV2" | "HTML" :param disable_notification: True = tichá zpráva (bez upozornění) :return: odeslaná zpráva (dict z Telegram API) """ params = { "chat_id": _resolve_chat_id(chat_id), "text": text, "disable_notification": disable_notification, } if parse_mode: params["parse_mode"] = parse_mode return _call("sendMessage", **params) def zeptej_se_telegram( otazka: str, *, chat_id: str | None = None, timeout: int = 300, poll_timeout: int = 30, parse_mode: str | None = None, ) -> str | None: """ Pošle otázku a BLOKUJÍCÍ čeká na textovou odpověď uživatele. Zahodí starší zprávy a bere jen tu, která přijde PO odeslání otázky. :param otazka: text otázky :param chat_id: cílový chat; výchozí z TELEGRAM_CHAT_ID :param timeout: celkové čekání na odpověď v sekundách (pak vrátí None) :param poll_timeout: délka jednoho long-poll cyklu v sekundách :param parse_mode: formátování otázky (None | "HTML" | "Markdown") :return: text odpovědi, nebo None když nikdo neodpoví do timeoutu """ cid = _resolve_chat_id(chat_id) # Zjisti poslední update_id, ať bereme jen NOVÉ zprávy po otázce. existujici = _call("getUpdates", http_timeout=15) offset = (existujici[-1]["update_id"] + 1) if existujici else 0 posli_telegram(otazka, chat_id=cid, parse_mode=parse_mode) deadline = time.monotonic() + timeout while time.monotonic() < deadline: zbyva = int(deadline - time.monotonic()) if zbyva <= 0: break lp = max(1, min(poll_timeout, zbyva)) updates = _call("getUpdates", http_timeout=lp + 10, offset=offset, timeout=lp) for u in updates: offset = u["update_id"] + 1 msg = u.get("message") or {} if str(msg.get("chat", {}).get("id")) != cid: continue # zpráva z jiného chatu — ignoruj text = msg.get("text") if text: return text return None def _safe_print(text: str): """Výpis odolný vůči kódování Windows konzole (cp1252).""" try: print(text) except UnicodeEncodeError: print(text.encode("ascii", "replace").decode("ascii")) if __name__ == "__main__": # Ať projdou i diakritika/emoji na Windows konzoli. try: sys.stdout.reconfigure(encoding="utf-8") except Exception: pass args = sys.argv[1:] if not args: print('Použití:') print(' python -m Knihovny.telegram_notify "text zprávy"') print(' python -m Knihovny.telegram_notify --ask "otázka?"') sys.exit(1) if args[0] == "--ask": otazka = " ".join(args[1:]) or "?" odpoved = zeptej_se_telegram(otazka, timeout=240) if odpoved is None: _safe_print("(bez odpovědi — vypršel timeout)") sys.exit(2) _safe_print(odpoved) else: posli_telegram(" ".join(args)) _safe_print("Odesláno OK")