notebookvb
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
"""
|
||||
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 <jmeno>` (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_<jmeno>.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()
|
||||
Reference in New Issue
Block a user