comenzi telegram
This commit is contained in:
10
README.md
10
README.md
@@ -427,6 +427,16 @@ Trimiți în chat-ul bot-ului:
|
|||||||
|
|
||||||
Doar `allowed_chat_ids` sunt acceptate. După 3 `401` consecutive, poller-ul intră în mod degradat.
|
Doar `allowed_chat_ids` sunt acceptate. După 3 `401` consecutive, poller-ul intră în mod degradat.
|
||||||
|
|
||||||
|
**Meniul slash (autocomplete pe `/` în client).** Se înregistrează **automat** la fiecare `atm run` (deci și prin `atm.bat`) — nu trebuie să faci nimic. Pentru (re)set/list/clear manual fără să pornești aplicația:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\.venv\Scripts\python.exe scripts\register_telegram_commands.py # înregistrează
|
||||||
|
.\.venv\Scripts\python.exe scripts\register_telegram_commands.py --list # ce e setat acum
|
||||||
|
.\.venv\Scripts\python.exe scripts\register_telegram_commands.py --clear # șterge
|
||||||
|
```
|
||||||
|
|
||||||
|
Discord este **webhook outbound** în acest proiect (doar trimite alerte) — nu primește comenzi și nu poate avea slash commands fără un bot application separat.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## După sesiune
|
## După sesiune
|
||||||
|
|||||||
70
scripts/register_telegram_commands.py
Normal file
70
scripts/register_telegram_commands.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
r"""Înregistrează comenzile slash ale bot-ului Telegram (meniul "/" din client).
|
||||||
|
|
||||||
|
`atm run` înregistrează deja comenzile automat la pornirea poller-ului
|
||||||
|
(`TelegramPoller._register_commands`). Acest script e pentru cazurile când vrei
|
||||||
|
să le (re)setezi fără să pornești aplicația, să le listezi sau să le ștergi.
|
||||||
|
|
||||||
|
Apelează Bot API `setMyCommands` cu aceeași listă (`atm.commands.TELEGRAM_COMMANDS`).
|
||||||
|
După rulare, în clientul Telegram apare meniul de comenzi cu autocomplete + descriere.
|
||||||
|
|
||||||
|
Notă: Telegram NU suportă parametri tipați pe slash command. Comenzile cu
|
||||||
|
argument (`/interval`, `/window`, `/rebase`) inserează doar prefixul; formatul
|
||||||
|
argumentului e documentat în descriere.
|
||||||
|
|
||||||
|
Discord nu e acoperit aici: în acest proiect Discord e webhook *outbound* —
|
||||||
|
nu există bot application / interactions endpoint, deci nu poate avea slash commands.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
.\.venv\Scripts\python.exe scripts\register_telegram_commands.py
|
||||||
|
.\.venv\Scripts\python.exe scripts\register_telegram_commands.py --list # doar afișează ce e setat acum
|
||||||
|
.\.venv\Scripts\python.exe scripts\register_telegram_commands.py --clear # șterge toate comenzile
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from atm.commands import TELEGRAM_COMMANDS
|
||||||
|
from atm.config import _ensure_env_loaded, _require_env
|
||||||
|
|
||||||
|
_BASE = "https://api.telegram.org/bot{token}/{method}"
|
||||||
|
|
||||||
|
|
||||||
|
def _call(token: str, method: str, payload: dict | None = None) -> dict:
|
||||||
|
resp = requests.post(_BASE.format(token=token, method=method), json=payload or {}, timeout=10)
|
||||||
|
body = resp.json()
|
||||||
|
if not body.get("ok"):
|
||||||
|
raise SystemExit(f"Telegram {method} eșuat: {body}")
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str]) -> int:
|
||||||
|
_ensure_env_loaded() # citește .env din rădăcină dacă există
|
||||||
|
token = _require_env("ATM_TG_TOKEN")
|
||||||
|
|
||||||
|
if "--list" in argv:
|
||||||
|
body = _call(token, "getMyCommands")
|
||||||
|
cmds = body.get("result", [])
|
||||||
|
if not cmds:
|
||||||
|
print("(niciun command setat)")
|
||||||
|
for c in cmds:
|
||||||
|
print(f"/{c['command']:<10} {c['description']}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if "--clear" in argv:
|
||||||
|
_call(token, "deleteMyCommands")
|
||||||
|
print("✓ Comenzi șterse.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
commands = [{"command": c, "description": d} for c, d in TELEGRAM_COMMANDS]
|
||||||
|
_call(token, "setMyCommands", {"commands": commands})
|
||||||
|
print(f"✓ {len(commands)} comenzi înregistrate la bot-ul Telegram:")
|
||||||
|
for c, d in TELEGRAM_COMMANDS:
|
||||||
|
print(f" /{c:<10} {d}")
|
||||||
|
print("\nDeschide chat-ul bot-ului și apasă butonul de meniu (/) ca să le vezi.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
||||||
@@ -23,6 +23,24 @@ CommandAction = Literal[
|
|||||||
|
|
||||||
_BASE = "https://api.telegram.org/bot{token}/{method}"
|
_BASE = "https://api.telegram.org/bot{token}/{method}"
|
||||||
|
|
||||||
|
# Slash-command menu shown in the Telegram client (autocomplete on "/").
|
||||||
|
# Single source of truth: the poller registers these at startup via
|
||||||
|
# setMyCommands, and scripts/register_telegram_commands.py reuses the list.
|
||||||
|
# Keep in sync with the `/help` handler in main.py (same commands, same order).
|
||||||
|
# command must match [a-z0-9_]{1,32}; description is 1-256 chars. Telegram has
|
||||||
|
# no typed args, so arg formats live in the description text.
|
||||||
|
TELEGRAM_COMMANDS: list[tuple[str, str]] = [
|
||||||
|
("status", "Stare FSM, uptime, ultima detecție, fereastră open/closed"),
|
||||||
|
("ss", "Screenshot acum (top-3 buline din ROI)"),
|
||||||
|
("pause", "Suspendă detecția (heartbeat-urile continuă)"),
|
||||||
|
("resume", "Reia detecția (șterge user-pause + drift-pause)"),
|
||||||
|
("rebase", "Propune phash nou pentru canary — aplici cu: /rebase confirm"),
|
||||||
|
("interval", "Auto-screenshot la N minute, ex: /interval 3"),
|
||||||
|
("stop", "Oprește scheduler-ul de auto-screenshot"),
|
||||||
|
("window", "Fereastră monitorizare locală, ex: /window 19:40-21:45 (off = dezactivează)"),
|
||||||
|
("help", "Listă scurtă a tuturor comenzilor"),
|
||||||
|
]
|
||||||
|
|
||||||
_HHMM = __import__("re").compile(r"^([01]?\d|2[0-3]):[0-5]\d$")
|
_HHMM = __import__("re").compile(r"^([01]?\d|2[0-3]):[0-5]\d$")
|
||||||
|
|
||||||
|
|
||||||
@@ -73,6 +91,7 @@ class TelegramPoller:
|
|||||||
async def run(self) -> None:
|
async def run(self) -> None:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
await self._drain(client)
|
await self._drain(client)
|
||||||
|
await self._register_commands(client)
|
||||||
while True:
|
while True:
|
||||||
if self._degraded:
|
if self._degraded:
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
@@ -88,6 +107,31 @@ class TelegramPoller:
|
|||||||
self._audit.log({"event": "poller_error", "error": str(exc)})
|
self._audit.log({"event": "poller_error", "error": str(exc)})
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
async def _register_commands(self, client: httpx.AsyncClient) -> None:
|
||||||
|
"""Populate the Telegram '/' menu via setMyCommands (TELEGRAM_COMMANDS).
|
||||||
|
|
||||||
|
Best-effort: any failure (bad token, network) is logged and swallowed so
|
||||||
|
it never blocks polling. Idempotent — safe to call on every startup.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resp = await client.post(
|
||||||
|
_BASE.format(token=self._cfg.bot_token, method="setMyCommands"),
|
||||||
|
json={"commands": [
|
||||||
|
{"command": c, "description": d} for c, d in TELEGRAM_COMMANDS
|
||||||
|
]},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
body = resp.json()
|
||||||
|
if body.get("ok"):
|
||||||
|
self._audit.log({
|
||||||
|
"event": "telegram_commands_registered",
|
||||||
|
"count": len(TELEGRAM_COMMANDS),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
logger.warning("setMyCommands failed: %s", body)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("setMyCommands error: %s", exc)
|
||||||
|
|
||||||
async def _drain(self, client: httpx.AsyncClient) -> None:
|
async def _drain(self, client: httpx.AsyncClient) -> None:
|
||||||
"""Discard all pending updates at startup so stale commands don't replay."""
|
"""Discard all pending updates at startup so stale commands don't replay."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -18,7 +18,15 @@ comenzi live Telegram. Fan-out trimite același eveniment pe mai multe canale.
|
|||||||
`TelegramNotifier` (sync) rămâne pe **requests**. Nu amesteca cele două.
|
`TelegramNotifier` (sync) rămâne pe **requests**. Nu amesteca cele două.
|
||||||
- **Comenzi live:** `/ss` `/status` `/pause` `/resume` `/rebase` `/3` (interval
|
- **Comenzi live:** `/ss` `/status` `/pause` `/resume` `/rebase` `/3` (interval
|
||||||
min) `/stop` `/window`. **Doar Telegram** primește comenzi — Discord e webhook
|
min) `/stop` `/window`. **Doar Telegram** primește comenzi — Discord e webhook
|
||||||
outbound, fără poller.
|
outbound, fără poller, deci **nu poate avea slash commands** (ar necesita un
|
||||||
|
bot application + interactions endpoint, care nu există).
|
||||||
|
- **Meniul slash din clientul Telegram** (autocomplete pe `/`) vine din
|
||||||
|
`setMyCommands`. Sursa unică e `commands.TELEGRAM_COMMANDS`; poller-ul îl
|
||||||
|
înregistrează **automat la startup** (`TelegramPoller._register_commands`,
|
||||||
|
best-effort — un eșec nu blochează polling-ul). `scripts/register_telegram_commands.py`
|
||||||
|
refolosește aceeași listă pentru (re)set/list/clear manual fără să pornești app-ul.
|
||||||
|
`TELEGRAM_COMMANDS` trebuie să oglindească handler-ul `/help` din `main.py`
|
||||||
|
(aceleași comenzi, aceeași ordine) — modifici una, actualizezi cealaltă în același commit.
|
||||||
|
|
||||||
### Contracte pe comenzi (nu le slăbi fără update aici)
|
### Contracte pe comenzi (nu le slăbi fără update aici)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user