From 42a1a0e7fdf7dabad10d71f5ab8db17b1c168ae7 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Sat, 18 Apr 2026 13:11:44 +0300 Subject: [PATCH] feat: /help command, atm.bat launcher, tzdata fix pentru Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Telegram /h /help — listă comenzi în română - atm.bat — pornire cu venv local automat, pip install la primul run - tzdata adăugat în deps principale cu marker sys_platform==win32 - README: secțiuni dev, instalare Windows, flow-uri calibrare Co-Authored-By: Claude Sonnet 4.6 --- README.md | 51 +++++++++++++++++++++++++++++++++++++----- atm.bat | 15 +++++++++++++ pyproject.toml | 1 + src/atm/commands.py | 4 +++- src/atm/main.py | 12 ++++++++++ tests/test_commands.py | 8 +++++++ 6 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 atm.bat diff --git a/README.md b/README.md index 2408c12..3088eb2 100644 --- a/README.md +++ b/README.md @@ -43,20 +43,60 @@ atm/ Python 3.11+. -```bash -pip install -e ".[windows]" # Windows: capture live + focus fereastră -pip install -e ".[dev]" # Linux/macOS/WSL: doar dev + teste (fără capture) +### Windows (producție) + +```powershell +python -m venv .venv +.venv\Scripts\activate +pip install -e ".[windows]" +# → creează .venv\Scripts\atm.exe atm --help ``` -**WSL/Linux:** recomandat să folosești un virtualenv local: +`[windows]` aduce `mss`, `pygetwindow`, `pywin32`. Fără venv, `pip install -e ".[windows]"` direct în Python-ul global funcționează la fel. + +Pornire rapidă cu scriptul inclus — instalează automat la primul run: +```powershell +atm.bat # prima rulare: pip install + atm run +atm.bat run --stop-at 23:00 +atm.bat debug +``` + +### WSL / Linux (dev + teste) + ```bash python3 -m venv .venv source .venv/bin/activate pip install -e ".[dev]" ``` -`[windows]` aduce `mss`, `pygetwindow`, `pywin32` (nu le pune pe WSL). +`[dev]` aduce `pytest`, `pytest-cov`, `pytest-asyncio`. Nu include dependențele Windows (`mss`, `pygetwindow`, `pywin32`) — nu rulează capture live. + +--- + +## Dev + +```bash +pytest -q # toate testele (184+) +pytest tests/test_commands.py # un modul specific +pytest -q --cov=atm --cov-report=term-missing # cu coverage +``` + +Smoke-test fără Windows (stub de captură din `samples/`): +```bash +atm run --capture-stub --duration 0.05 +``` + +Structura testelor: + +| Fișier | Ce acoperă | +|---|---| +| `test_commands.py` | parsing comenzi Telegram | +| `test_config.py` | loader TOML, attach_screenshots | +| `test_handle_tick.py` | loop principal, snapshot, FSM | +| `test_main.py` | lifecycle, operating hours, canary, dispatcher | +| `test_validate.py` | gate offline clasificare culori | +| `test_canary.py` | drift + callback pauză | --- @@ -299,6 +339,7 @@ Trimiți în chat-ul bot-ului: | `/resume force` | Elimină și drift-pause-ul canary (după recalibrare) | | `/3` sau `/interval 3` | Interval auto-screenshot = 3 min | | `/stop` | Oprește scheduler-ul de screenshot | +| `/h` sau `/help` | Listă scurtă a tuturor comenzilor disponibile | Doar `allowed_chat_ids` sunt acceptate. După 3 `401` consecutive, poller-ul intră în mod degradat. diff --git a/atm.bat b/atm.bat new file mode 100644 index 0000000..328cf27 --- /dev/null +++ b/atm.bat @@ -0,0 +1,15 @@ +@echo off +cd /d "%~dp0" + +if not exist ".venv\Scripts\atm.exe" ( + echo Instalez atm in venv local... + python -m venv .venv + call .venv\Scripts\activate.bat + pip install -e ".[windows]" +) + +if "%~1"=="" ( + .venv\Scripts\atm.exe run +) else ( + .venv\Scripts\atm.exe %* +) diff --git a/pyproject.toml b/pyproject.toml index b6bb738..b7bea7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "requests>=2.31", "rich>=13.0", "httpx>=0.27", + "tzdata>=2024.1; sys_platform == 'win32'", ] [project.optional-dependencies] diff --git a/src/atm/commands.py b/src/atm/commands.py index 2d6f6cf..7b774d5 100644 --- a/src/atm/commands.py +++ b/src/atm/commands.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -CommandAction = Literal["set_interval", "stop", "status", "ss", "pause", "resume"] +CommandAction = Literal["set_interval", "stop", "status", "ss", "pause", "resume", "help"] _BASE = "https://api.telegram.org/bot{token}/{method}" @@ -148,6 +148,8 @@ class TelegramPoller: t = text.lstrip("/").strip() if not t: return None + if t in ("h", "help"): + return Command(action="help") if t == "stop": return Command(action="stop") if t == "status": diff --git a/src/atm/main.py b/src/atm/main.py index 3c6589c..b2b45e0 100644 --- a/src/atm/main.py +++ b/src/atm/main.py @@ -1065,6 +1065,18 @@ async def _dispatch_command(ctx: RunContext, cmd) -> None: title = "Monitorizare reluată" body = "" ctx.notifier.send(Alert(kind="status", title=title, body=body)) + elif cmd.action == "help": + body = ( + "/status — stare FSM, uptime, ultima detecție\n" + "/ss — screenshot acum\n" + "/pause — oprește detecția (heartbeat continuă)\n" + "/resume — reia detecția (doar pauza user)\n" + "/resume force — reia + anulează drift-pause canary\n" + "/3 — screenshot automat la fiecare 3 min (sau orice număr)\n" + "/stop — oprește screenshot-urile automate\n" + "/h — acest mesaj" + ) + ctx.notifier.send(Alert(kind="status", title="Comenzi ATM", body=body)) async def _drain_cmd_queue(ctx: RunContext) -> None: diff --git a/tests/test_commands.py b/tests/test_commands.py index 7a9bdf6..9df48c4 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -36,6 +36,14 @@ def test_parse_resume_force(): assert cmd.value == 1 +def test_parse_help(): + p = _make_poller() + assert p._parse_command("h") == Command(action="help") + assert p._parse_command("/h") == Command(action="help") + assert p._parse_command("help") == Command(action="help") + assert p._parse_command("/help") == Command(action="help") + + def test_parse_existing_commands_still_work(): """Regression: adding pause/resume must not break stop/status/ss/interval.""" p = _make_poller()