""" telegram_user.py ---------------- Ovládání PLNOHODNOTNÉHO Telegram účtu (ne bota) přes user API (MTProto / Telethon). Na rozdíl od bota umí napsat komukoli a unese VÍCE souběžných agentů na jednom účtu (jako Telegram otevřený zároveň na PC, tabletu i mobilu). ⚠️ Jedná JMÉNEM přihlášeného účtu. Session soubor = plný přístup k účtu. ⚠️ Nové účty na automatizaci Telegram rychle banuje (zvlášť VoIP čísla — použij reálnou SIM). ──────────────────────────────────────────────────────────────────────── VÍCE AGENTŮ NA JEDNOM ÚČTU ──────────────────────────────────────────────────────────────────────── - api_id/api_hash se SDÍLÍ (identifikují „aplikaci", ne zařízení). - Každý agent musí mít VLASTNÍ session soubor (= vlastní autorizace / „zařízení"). Sdílet jednu session mezi procesy NELZE (database is locked / AUTH_KEY_DUPLICATED). → každý agent se přihlásí zvlášť: `login --jako ` (jeden SMS kód na agenta). - Všechny sessions vidí stejný chat, proto se odpovědi směrují přes Telegram **Reply**: agent pošle označenou otázku a bere jen tu odpověď, která je Reply na *jeho* zprávu (shoda `reply_to_msg_id`). Tím se odpovědi více agentů nepomíchají. Konfigurace v `Medevio/.env` (api_id/api_hash z https://my.telegram.org): TELEGRAM_API_ID=1234567 TELEGRAM_API_HASH=abcdef0123456789abcdef0123456789 TELEGRAM_PHONE=+420... # nepovinné (jinak se zeptá při loginu) Session soubory: `Medevio/agent_telegram_.session` (gitignored). ──────────────────────────────────────────────────────────────────────── CLI ──────────────────────────────────────────────────────────────────────── Jednorázové přihlášení agenta (spusť ve svém terminálu — čeká na kód z SMS): python -m Knihovny.telegram_user login --jako recepty python -m Knihovny.telegram_user login --jako kalendar Poslání zprávy ("me" = Uložené zprávy / Saved Messages): python -m Knihovny.telegram_user send me "Test" --jako recepty Otázka + čekání na Reply odpověď (vypíše odpověď na stdout): python -m Knihovny.telegram_user ask recepty "Mam pokracovat? (ano/ne)" ──────────────────────────────────────────────────────────────────────── ZE SKRIPTU ──────────────────────────────────────────────────────────────────────── from Knihovny.telegram_user import posli_jako_ja, zeptej_se_jako posli_jako_ja("me", "Pipeline 08 hotová", session="recepty") odp = zeptej_se_jako("recepty", "Našel jsem 3 sporné záznamy. Pokračovat?") if odp and odp.strip().lower() == "ano": ... """ import argparse import os import sys import time from pathlib import Path # telethon.sync zpřístupní metody synchronně (bez async/await) from telethon.sync import TelegramClient from telethon.errors import SessionPasswordNeededError, PhoneNumberUnoccupiedError 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() def _api_id() -> int: val = os.environ.get("TELEGRAM_API_ID") if not val: raise RuntimeError("Chybí TELEGRAM_API_ID v Medevio/.env (z https://my.telegram.org)") return int(val) def _api_hash() -> str: val = os.environ.get("TELEGRAM_API_HASH") if not val: raise RuntimeError("Chybí TELEGRAM_API_HASH v Medevio/.env (z https://my.telegram.org)") return val def _session_path(jmeno: str | None) -> Path: base = f"agent_telegram_{jmeno}" if jmeno else "agent_telegram" return Path(__file__).resolve().parent.parent / "Medevio" / base def _new_client(session: str | None = None) -> TelegramClient: return TelegramClient(str(_session_path(session)), _api_id(), _api_hash()) def prihlas(session: str | None = None) -> None: """ Jednorázové přihlášení dané session. Interaktivně se zeptá na kód z SMS a případně na heslo dvoufázového ověření. Vytvoří session soubor. SPOUŠTĚJ V TERMINÁLU (potřebuje input). """ client = _new_client(session) client.start(phone=os.environ.get("TELEGRAM_PHONE") or (lambda: input("Telefon (+420...): "))) me = client.get_me() print(f"Session '{session or 'default'}' přihlášena jako " f"{me.first_name or ''} (@{me.username}) id={me.id}") client.disconnect() def _phone() -> str: val = os.environ.get("TELEGRAM_PHONE") if not val: raise RuntimeError("Chybí TELEGRAM_PHONE v Medevio/.env") return val def login_posli_kod(session: str | None = None) -> None: """ 1. krok přihlášení (řízeného na dálku): vyžádá si od Telegramu kód. Vytiskne `PHONE_CODE_HASH=...`, který je potřeba pro 2. krok. """ client = _new_client(session) client.connect() try: sent = client.send_code_request(_phone()) print("PHONE_CODE_HASH=" + sent.phone_code_hash) finally: client.disconnect() def login_dokonci(code, phone_code_hash: str, session: str | None = None, password: str | None = None) -> None: """ 2. krok přihlášení: dokončí login zadaným kódem (a případně heslem 2FA). Při úspěchu uloží session soubor. """ client = _new_client(session) client.connect() try: try: client.sign_in(phone=_phone(), code=str(code), phone_code_hash=phone_code_hash) except SessionPasswordNeededError: if not password: print("NEED_PASSWORD") return client.sign_in(password=password) except PhoneNumberUnoccupiedError: print("UCET_NEEXISTUJE - nejdriv zaregistruj cislo v aplikaci Telegram") return me = client.get_me() print(f"OK prihlaseno jako {me.first_name or ''} (@{me.username}) id={me.id}") finally: client.disconnect() def posli_jako_ja(komu, text: str, *, session: str | None = None): """ Pošle zprávu jménem přihlášeného účtu z dané session. :param komu: "me" (Saved Messages) | "@username" | telefon | int id :param text: text zprávy :param session: jméno session (které přihlášení použít) :return: odeslaná zpráva (Telethon Message) """ with _new_client(session) as client: if not client.is_user_authorized(): raise RuntimeError( f"Session '{session or 'default'}' není přihlášena — " f"spusť: python -m Knihovny.telegram_user login" + (f" --jako {session}" if session else "") ) return client.send_message(komu, text) def precti_zpravy(komu, limit: int = 10, *, session: str | None = None): """ Vrátí posledních `limit` zpráv z daného chatu. :return: list dictů {"id", "text", "odeslal_ja", "reply_na", "datum"} """ out = [] with _new_client(session) as client: if not client.is_user_authorized(): raise RuntimeError(f"Session '{session or 'default'}' není přihlášena.") for msg in client.iter_messages(komu, limit=limit): out.append({ "id": msg.id, "text": msg.text or "", "odeslal_ja": bool(msg.out), "reply_na": msg.reply_to_msg_id, "datum": msg.date, }) return out def zeptej_se_jako( agent: str, otazka: str, *, komu="me", session: str | None = None, timeout: int = 300, poll_interval: int = 3, vyzaduj_reply: bool = True, ) -> str | None: """ Pošle označenou otázku ("[agent] otázka") a BLOKUJÍCÍ čeká na odpověď. Při více agentech naráz se odpovědi rozlišují přes Telegram **Reply**: bere jen tu příchozí zprávu, která je Reply na právě odeslanou otázku. :param agent: jméno agenta (objeví se v textu otázky jako štítek) :param otazka: text otázky :param komu: kam poslat ("me" = Saved Messages | "@username" | id) :param session: jméno session; výchozí = `agent` (každý agent svůj soubor) :param timeout: celkové čekání v sekundách (pak vrátí None) :param poll_interval: jak často kontrolovat nové zprávy (s) :param vyzaduj_reply: True = bere jen Reply na svou otázku (bezpečné pro víc agentů); False = vezme první příchozí zprávu (jen pro 1 agenta) :return: text odpovědi, nebo None při timeoutu """ session = session or agent with _new_client(session) as client: if not client.is_user_authorized(): raise RuntimeError( f"Session '{session}' není přihlášena — " f"spusť: python -m Knihovny.telegram_user login --jako {session}" ) sent = client.send_message(komu, f"[{agent}] {otazka}") qid = sent.id deadline = time.monotonic() + timeout while time.monotonic() < deadline: # jen zprávy novější než naše otázka, od nejstarší for msg in client.iter_messages(komu, min_id=qid, reverse=True): if msg.out: continue # naše vlastní zpráva if vyzaduj_reply: if msg.reply_to_msg_id == qid: return msg.text else: return msg.text zbyva = deadline - time.monotonic() if zbyva <= 0: break time.sleep(min(poll_interval, max(1, zbyva))) return None def _safe_print(text: str): try: print(text) except UnicodeEncodeError: print(text.encode("ascii", "replace").decode("ascii")) def _main(): try: sys.stdout.reconfigure(encoding="utf-8") except Exception: pass parser = argparse.ArgumentParser(prog="telegram_user", description="Telegram user účet (Telethon)") sub = parser.add_subparsers(dest="cmd", required=True) p_login = sub.add_parser("login", help="jednorázové přihlášení session") p_login.add_argument("--jako", dest="jako", default=None, help="jméno session/agenta") p_send = sub.add_parser("send", help="poslat zprávu") p_send.add_argument("komu", help='"me" | "@username" | telefon | id') p_send.add_argument("text", help="text zprávy") p_send.add_argument("--jako", dest="jako", default=None, help="jméno session") p_ask = sub.add_parser("ask", help="poslat otázku a počkat na Reply odpověď") p_ask.add_argument("agent", help="jméno agenta (štítek + výchozí session)") p_ask.add_argument("text", help="text otázky") p_ask.add_argument("--komu", dest="komu", default="me", help='kam (výchozí "me")') p_ask.add_argument("--timeout", dest="timeout", type=int, default=240, help="čekání v s") args = parser.parse_args() if args.cmd == "login": prihlas(args.jako) elif args.cmd == "send": posli_jako_ja(args.komu, args.text, session=args.jako) _safe_print("Odesláno OK") elif args.cmd == "ask": odp = zeptej_se_jako(args.agent, args.text, komu=args.komu, timeout=args.timeout) if odp is None: _safe_print("(bez odpovědi — vypršel timeout)") sys.exit(2) _safe_print(odp) if __name__ == "__main__": _main()