Initial NTE history exporter

Initial NTE history exporter

Add a Windows live-capture exporter for NTE pull history with support for
Permanent Monopoly, Limited Monopoly, and Arc Miracle Box histories.

Included:
- live packet capture via Windows raw sockets
- CSV and JSON export generation
- normalized reward and banner mapping files
- Monopoly v7 Points Gift / Chase Reward classification
- Arc Miracle Box decoding with shared pity metadata
- timestamp grouping and UID generation
- Windows launcher scripts with admin prompting
- docs for usage, export format, limitations, and packet notes
- tests covering decoder behaviour and mapping consistency

Generated exports, captures, briefs, and local data are ignored for privacy.
This commit is contained in:
Golumpa 2026-06-10 23:59:01 +01:00
commit bb3fe3b19a
35 changed files with 2439 additions and 0 deletions

View file

@ -0,0 +1,57 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
from nte_history_exporter.constants import POOL_META
from nte_history_exporter.decoder.protocol import decode_response_records
def fmt_packet_time(ts: float | None) -> str:
if ts is None:
return ""
return datetime.fromtimestamp(ts, timezone.utc).strftime("%H:%M:%S.%f")[:-3]
def build_rows_from_pairs(pairs: list[tuple]) -> list[dict[str, Any]]:
rows_out: list[dict[str, Any]] = []
for pair in pairs:
page, offset, req_i, req_ts, resp_i, resp_ts, response_content = pair[:7]
kind = pair[7] if len(pair) > 7 else "permanent"
pool = POOL_META.get(kind, POOL_META["permanent"])
records = decode_response_records(response_content)
if not records:
rows_out.append(
{
"page": page,
"offset": offset,
"row": "",
"pool_group_id": pool["id"],
"pool_group_name": pool["name"],
"request_msg": req_i,
"request_time_utc": fmt_packet_time(req_ts),
"response_msg": resp_i,
"response_time_utc": fmt_packet_time(resp_ts),
"response_len": len(response_content),
"record_count": 0,
"record_hex": response_content.hex(),
}
)
continue
for record in records:
rows_out.append(
{
"page": page,
"offset": offset,
"pool_group_id": pool["id"],
"pool_group_name": pool["name"],
"request_msg": req_i,
"request_time_utc": fmt_packet_time(req_ts),
"response_msg": resp_i,
"response_time_utc": fmt_packet_time(resp_ts),
"response_len": len(response_content),
"record_count": len(records),
**record,
}
)
return rows_out