Initial commit: MUD client v0.2

This commit is contained in:
truetype committed 2026-07-07 15:38:28 +00:00
commit 0030fd7d82
26 files changed
+4421

No files matched your search

+209
View File
@@ -0,0 +1,209 @@
# Schema eventi del `EventLogger`
`EventLogger` espone due sink paralleli attivabili via `__init__`:
- **JSONL** (`--log-json PATH`) — uno `{"ts":..., "kind":..., "data":{...}}` per riga.
- **Markdown** (`--log-md PATH`) — heading `## HH:MM:SS.mmm — kind` seguito da
un body specifico del `kind`.
Entrambi `flush()` dopo ogni evento (sub‑os buffer vuoto) → adatto a
`tail -f`.
---
## Timestamp
- **JSONL**: ISO‑8601 UTC con precisione millisecondo + `Z` finale
(es. `"2026-07-05T12:34:56.789Z"`).
- **MD**: solo l'ora, `HH:MM:SS.mmm`.
Entrambi calcolati da `datetime.now(timezone.utc)` non da `time.time()`
(locale‑indipendente).
---
## Tipi di evento
### `session_start` (1 per sessione)
```json
{"kind": "session_start", "data": {"host": "localhost", "port": 6666, "user": ""}}
```
MD: header `# MUD log — <ts>` con `host`, `port`, `user` poi `---`.
### `session_end` (1 per sessione)
```json
{"kind": "session_end", "data": {"reason": "done"}}
```
Possibili `reason`: `done`, `quit`.
### `disconnect`
```json
{"kind": "disconnect", "data": {"reason": "local_close"}}
```
Emesso quando `conn.close()` viene chiamato explicitamente
(`--quit`, EOF, ecc.).
### `text`
```json
{"kind": "text", "data": {"raw": "Inn Hall\n\x1b[36mCobbles.\x1b[0m\n[ Exits: n ]\n"}}
```
Chunk ANSI grezzo dal server, subito dopo la IAC strip. Nel MD:
blocco ` ``` ` con testo privato di ANSI via `strip_ansi`.
> Attenzione: ANSI con byte non validi vengono preservati nel `raw`
> JSONL ma al massimo appaiono come "?" nel markdown. La redazione
> non si applica al text (se il server ti invia una password in
> chiaro la vedrai).
### `room_enter`
```json
{"kind": "room_enter", "data": {"id": "vnum12345", "name": "Inn Hall", "exits": ["n", "e"]}}
```
`id`:
- `vnum{int(num)}` se GMCP `Room.Info.num`/`vnum`/`id` era numerico.
- altrimenti `name.lower()` (case‑insensitive identity).
MD: lista bullet `id` / `name` / `exits`.
### `gmcp`
```json
{"kind": "gmcp", "data": {"module": "Char.Vitals", "payload": {"hp": 100, "max_hp": 120}}}
```
`payload` è il JSON decodificato (potrebbe essere `null` se il modulo
veniva emesso senza body). MD: blocco ` ```json ` con `payload` ordinato
per chiave + indentazione.
### `vitals`
```json
{"kind": "vitals", "data": {"hp": 100, "max_hp": 120, "sp": 50, "max_sp": 60, "mv": 80, "max_mv": 100}}
```
Solo i campi effettivamente presenti nel payload GMCP. MD:
`HP 100/120 — SP 50/60 — MV 80/100`.
### `line_sent`
```json
{"kind": "line_sent", "data": {"text": "north", "redacted": false}}
{"kind": "line_sent", "data": {"text": "***", "redacted": true}}
```
`redacted=true` ⇒ la `text` diventa `"***"` (la stringa originale
viene scartata). MD: `> … *(redacted)*` se redatto, altrimenti `> …`.
### `local_cmd`
```json
{"kind": "local_cmd", "data": {"cmd": "map", "args": [], "redacted": false}}
{"kind": "local_cmd", "data": {"cmd": "password", "args": ["***"], "redacted": true}}
```
Se `redacted=true`, `args` è collassato a una lista di `"***"` di pari
lunghezza all'originale (anche password multi‑parola).
### `subneg` (forward hook diagnostico)
```json
{"kind": "subneg", "data": {"opt": "NAWS", "bytes": 4}}
{"kind": "subneg", "data": {"opt": "TTYPE", "bytes": 0}}
```
Emesso da `cli.py` per ogni subnegoziazione telnet **non‑GMCP**
ricevuta. `opt` è il nome umano (`ECHO`/`SUPPRESS_GO_AHEAD`/
`TTYPE`/`NAWS`/`GMCP` o `opt_<num>` se sconosciuto) da `_OPT_NAMES`,
`bytes` è la lunghezza del payload. Utile in `tail -f` per
diagnosticare la negoziazione delle opzioni senza dover attivare
`--log DEBUG`.
MD: `- **opt**: NAWS` + `- **bytes**: 4`.
### `login`
```json
{"kind": "login", "data": {"phase": "user_sent", "value": "alice", "redact": false}}
{"kind": "login", "data": {"phase": "password_sent", "value": "***", "redact": true}}
{"kind": "login", "data": {"phase": "password_prompt", "value": "", "redact": true}}
```
`phase ∈ {user_sent, password_prompt, password_sent}`.
Default `redact=True` ⇒ `value` registrato come `"***"`. Passa
`redact=False` esplicitamente per i nomi account (devono essere
tracciabili per leggere i log).
MD: `**{phase}** → \`{value}\`` (con back‑tick escape).
---
## Esempio di sessione (estratti)
```bash
$ jq -c '.' < session.jsonl
{"ts":"2026-07-05T12:34:56.789Z","kind":"session_start","data":{"host":"localhost","port":6666,"user":"zork"}}
{"ts":"2026-07-05T12:34:57.012Z","kind":"text","data":{"raw":"Welcome to Dead Souls.\n"}}
{"ts":"2026-07-05T12:34:57.234Z","kind":"text","data":{"raw":"What name do you wish?\n"}}
{"ts":"2026-07-05T12:34:57.235Z","kind":"line_sent","data":{"text":"zork","redacted":false}}
{"ts":"2026-07-05T12:34:57.236Z","kind":"login","data":{"phase":"user_sent","value":"zork","redact":false}}
{"ts":"2026-07-05T12:34:58.001Z","kind":"text","data":{"raw":"Password:\n"}}
{"ts":"2026-07-05T12:34:58.002Z","kind":"login","data":{"phase":"password_prompt","value":"","redact":true}}
{"ts":"2026-07-05T12:35:00.500Z","kind":"line_sent","data":{"text":"***","redacted":true}}
{"ts":"2026-07-05T12:35:00.501Z","kind":"login","data":{"phase":"password_sent","value":"***","redact":true}}
{"ts":"2026-07-05T12:35:01.123Z","kind":"gmcp","data":{"module":"Room.Info","payload":{"num":12345,"name":"Town Square","exits":{"n":12346}}}}
{"ts":"2026-07-05T12:35:01.124Z","kind":"room_enter","data":{"id":"vnum12345","name":"Town Square","exits":["n"]}}
{"ts":"2026-07-05T12:35:30.000Z","kind":"session_end","data":{"reason":"quit"}}
```
---
## Helpers di analisi (`jq`)
```bash
# solo stanze visitate
jq -c 'select(.kind=="room_enter")' session.jsonl
# vitals (ultimo snapshot)
jq -c 'select(.kind=="vitals")' session.jsonl | tail -1
# righe inviate (escludendo redacted)
jq -c 'select(.kind=="line_sent" and .data.redacted==false)' session.jsonl
# tutti i comandi locali slash
jq -c 'select(.kind=="local_cmd")' session.jsonl
```
---
## Idempotenza
`EventLogger.close()` può essere chiamato più volte senza effetti
collaterali. Nel `finally` di `cli._run`, viene chiamato esattamente
una volta anche se l'utente fa `Ctrl‑C` due volte.
---
## Note di privacy
- Il client **non** logga la password in chiaro. Tutti i punti di
emissione relativi a credenziali sono protetti da `redacted=True`.
- `text` chunk può contenere **qualsiasi cosa** il server ti invii. Se
il MUD fa echo della tua password (raro, perché WILL ECHO è attivo),
apparirà nel log.
- `gmcp.payload` è decodificato; se un modulo MUD contiene segreti, li
vedrai nel log.
- Per ambienti condivisi si consiglia `--log-json` su FS cifrato e/o
log ruotato con uno scheduler esterno (es. `logrotate`).