feat: /help command, atm.bat launcher, tzdata fix pentru Windows
- 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 <noreply@anthropic.com>
This commit is contained in:
51
README.md
51
README.md
@@ -43,20 +43,60 @@ atm/
|
|||||||
|
|
||||||
Python 3.11+.
|
Python 3.11+.
|
||||||
|
|
||||||
```bash
|
### Windows (producție)
|
||||||
pip install -e ".[windows]" # Windows: capture live + focus fereastră
|
|
||||||
pip install -e ".[dev]" # Linux/macOS/WSL: doar dev + teste (fără capture)
|
```powershell
|
||||||
|
python -m venv .venv
|
||||||
|
.venv\Scripts\activate
|
||||||
|
pip install -e ".[windows]"
|
||||||
|
# → creează .venv\Scripts\atm.exe
|
||||||
atm --help
|
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
|
```bash
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
pip install -e ".[dev]"
|
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) |
|
| `/resume force` | Elimină și drift-pause-ul canary (după recalibrare) |
|
||||||
| `/3` sau `/interval 3` | Interval auto-screenshot = 3 min |
|
| `/3` sau `/interval 3` | Interval auto-screenshot = 3 min |
|
||||||
| `/stop` | Oprește scheduler-ul de screenshot |
|
| `/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.
|
Doar `allowed_chat_ids` sunt acceptate. După 3 `401` consecutive, poller-ul intră în mod degradat.
|
||||||
|
|
||||||
|
|||||||
15
atm.bat
Normal file
15
atm.bat
Normal file
@@ -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 %*
|
||||||
|
)
|
||||||
@@ -14,6 +14,7 @@ dependencies = [
|
|||||||
"requests>=2.31",
|
"requests>=2.31",
|
||||||
"rich>=13.0",
|
"rich>=13.0",
|
||||||
"httpx>=0.27",
|
"httpx>=0.27",
|
||||||
|
"tzdata>=2024.1; sys_platform == 'win32'",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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}"
|
_BASE = "https://api.telegram.org/bot{token}/{method}"
|
||||||
|
|
||||||
@@ -148,6 +148,8 @@ class TelegramPoller:
|
|||||||
t = text.lstrip("/").strip()
|
t = text.lstrip("/").strip()
|
||||||
if not t:
|
if not t:
|
||||||
return None
|
return None
|
||||||
|
if t in ("h", "help"):
|
||||||
|
return Command(action="help")
|
||||||
if t == "stop":
|
if t == "stop":
|
||||||
return Command(action="stop")
|
return Command(action="stop")
|
||||||
if t == "status":
|
if t == "status":
|
||||||
|
|||||||
@@ -1065,6 +1065,18 @@ async def _dispatch_command(ctx: RunContext, cmd) -> None:
|
|||||||
title = "Monitorizare reluată"
|
title = "Monitorizare reluată"
|
||||||
body = ""
|
body = ""
|
||||||
ctx.notifier.send(Alert(kind="status", title=title, body=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:
|
async def _drain_cmd_queue(ctx: RunContext) -> None:
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ def test_parse_resume_force():
|
|||||||
assert cmd.value == 1
|
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():
|
def test_parse_existing_commands_still_work():
|
||||||
"""Regression: adding pause/resume must not break stop/status/ss/interval."""
|
"""Regression: adding pause/resume must not break stop/status/ss/interval."""
|
||||||
p = _make_poller()
|
p = _make_poller()
|
||||||
|
|||||||
Reference in New Issue
Block a user