Initial commit: MUD client v0.2
This commit is contained in:
commit
0030fd7d82
26 files changed
+4421
No files matched your search
+209
@@ -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`).
|
||||
Reference in new issue
Block a user