Compare commits

..

No commits in common. "13dac398c5e2e8a8c10f0fd3b05e2e1a270df3cc" and "66ed711fec1e7b9a351bcd8284ff6631d97997b8" have entirely different histories.

3 changed files with 58 additions and 82 deletions

View file

@ -1,2 +0,0 @@
reformat:
black *py

84
db.py
View file

@ -3,15 +3,13 @@ from typing import Dict
DB_FILE = "dereth.db" DB_FILE = "dereth.db"
def init_db() -> None: def init_db() -> None:
"""Create tables if they do not exist (extended with kills_per_hour and onlinetime).""" """Create tables if they do not exist (extended with kills_per_hour and onlinetime)."""
conn = sqlite3.connect(DB_FILE) conn = sqlite3.connect(DB_FILE)
c = conn.cursor() c = conn.cursor()
# History log # History log
c.execute( c.execute("""
"""
CREATE TABLE IF NOT EXISTS telemetry_log ( CREATE TABLE IF NOT EXISTS telemetry_log (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
character_name TEXT NOT NULL, character_name TEXT NOT NULL,
@ -29,12 +27,10 @@ def init_db() -> None:
prismatic_taper_count INTEGER, prismatic_taper_count INTEGER,
vt_state TEXT vt_state TEXT
) )
""" """)
)
# Live snapshot (upsert) # Live snapshot (upsert)
c.execute( c.execute("""
"""
CREATE TABLE IF NOT EXISTS live_state ( CREATE TABLE IF NOT EXISTS live_state (
character_name TEXT PRIMARY KEY, character_name TEXT PRIMARY KEY,
char_tag TEXT, char_tag TEXT,
@ -51,49 +47,41 @@ def init_db() -> None:
prismatic_taper_count INTEGER, prismatic_taper_count INTEGER,
vt_state TEXT vt_state TEXT
) )
""" """)
)
conn.commit() conn.commit()
conn.close() conn.close()
def save_snapshot(data: Dict) -> None: def save_snapshot(data: Dict) -> None:
"""Insert snapshot into history and upsert into live_state (with new fields).""" """Insert snapshot into history and upsert into live_state (with new fields)."""
conn = sqlite3.connect(DB_FILE) conn = sqlite3.connect(DB_FILE)
c = conn.cursor() c = conn.cursor()
# Insert full history row # Insert full history row
c.execute( c.execute("""
"""
INSERT INTO telemetry_log ( INSERT INTO telemetry_log (
character_name, char_tag, session_id, timestamp, character_name, char_tag, session_id, timestamp,
ew, ns, z, ew, ns, z,
kills, kills_per_hour, onlinetime, kills, kills_per_hour, onlinetime,
deaths, rares_found, prismatic_taper_count, vt_state deaths, rares_found, prismatic_taper_count, vt_state
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """, (
( data["character_name"],
data["character_name"], data.get("char_tag", ""),
data.get("char_tag", ""), data["session_id"],
data["session_id"], data["timestamp"],
data["timestamp"], data["ew"], data["ns"], data.get("z", 0.0),
data["ew"], data["kills"],
data["ns"], data.get("kills_per_hour", ""),
data.get("z", 0.0), data.get("onlinetime", ""),
data["kills"], data.get("deaths", 0),
data.get("kills_per_hour", ""), data.get("rares_found", 0),
data.get("onlinetime", ""), data.get("prismatic_taper_count", 0),
data.get("deaths", 0), data.get("vt_state", "Unknown"),
data.get("rares_found", 0), ))
data.get("prismatic_taper_count", 0),
data.get("vt_state", "Unknown"),
),
)
# Upsert into live_state # Upsert into live_state
c.execute( c.execute("""
"""
INSERT INTO live_state ( INSERT INTO live_state (
character_name, char_tag, session_id, timestamp, character_name, char_tag, session_id, timestamp,
ew, ns, z, ew, ns, z,
@ -114,24 +102,20 @@ def save_snapshot(data: Dict) -> None:
rares_found = excluded.rares_found, rares_found = excluded.rares_found,
prismatic_taper_count = excluded.prismatic_taper_count, prismatic_taper_count = excluded.prismatic_taper_count,
vt_state = excluded.vt_state vt_state = excluded.vt_state
""", """, (
( data["character_name"],
data["character_name"], data.get("char_tag", ""),
data.get("char_tag", ""), data["session_id"],
data["session_id"], data["timestamp"],
data["timestamp"], data["ew"], data["ns"], data.get("z", 0.0),
data["ew"], data["kills"],
data["ns"], data.get("kills_per_hour", ""),
data.get("z", 0.0), data.get("onlinetime", ""),
data["kills"], data.get("deaths", 0),
data.get("kills_per_hour", ""), data.get("rares_found", 0),
data.get("onlinetime", ""), data.get("prismatic_taper_count", 0),
data.get("deaths", 0), data.get("vt_state", "Unknown"),
data.get("rares_found", 0), ))
data.get("prismatic_taper_count", 0),
data.get("vt_state", "Unknown"),
),
)
conn.commit() conn.commit()
conn.close() conn.close()

54
main.py
View file

@ -19,10 +19,9 @@ app = FastAPI()
live_snapshots: Dict[str, dict] = {} live_snapshots: Dict[str, dict] = {}
SHARED_SECRET = "your_shared_secret" SHARED_SECRET = "your_shared_secret"
# LOG_FILE = "telemetry_log.jsonl" #LOG_FILE = "telemetry_log.jsonl"
# ------------------------------------------------------------------ # ------------------------------------------------------------------
ACTIVE_WINDOW = timedelta(seconds=30) # player is “online” if seen in last 30 s ACTIVE_WINDOW = timedelta(seconds=30) # player is “online” if seen in last 30 s
class TelemetrySnapshot(BaseModel): class TelemetrySnapshot(BaseModel):
character_name: str character_name: str
@ -30,9 +29,9 @@ class TelemetrySnapshot(BaseModel):
session_id: str session_id: str
timestamp: datetime timestamp: datetime
ew: float # +E / W ew: float # +E / W
ns: float # +N / S ns: float # +N / S
z: float z: float
kills: int kills: int
kills_per_hour: Optional[str] = None # now optional kills_per_hour: Optional[str] = None # now optional
@ -52,7 +51,8 @@ def on_startup():
@app.post("/position") @app.post("/position")
@app.post("/position/") @app.post("/position/")
async def receive_snapshot( async def receive_snapshot(
snapshot: TelemetrySnapshot, x_plugin_secret: str = Header(None) snapshot: TelemetrySnapshot,
x_plugin_secret: str = Header(None)
): ):
if x_plugin_secret != SHARED_SECRET: if x_plugin_secret != SHARED_SECRET:
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
@ -64,12 +64,10 @@ async def receive_snapshot(
save_snapshot(snapshot.dict()) save_snapshot(snapshot.dict())
# optional log-file append # optional log-file append
# with open(LOG_FILE, "a") as f: #with open(LOG_FILE, "a") as f:
# f.write(json.dumps(snapshot.dict(), default=str) + "\n") # f.write(json.dumps(snapshot.dict(), default=str) + "\n")
print( print(f"[{datetime.now()}] {snapshot.character_name} @ NS={snapshot.ns:+.2f}, EW={snapshot.ew:+.2f}")
f"[{datetime.now()}] {snapshot.character_name} @ NS={snapshot.ns:+.2f}, EW={snapshot.ew:+.2f}"
)
return {"status": "ok"} return {"status": "ok"}
@ -92,19 +90,18 @@ def get_live_players():
cutoff = datetime.utcnow().replace(tzinfo=timezone.utc) - ACTIVE_WINDOW cutoff = datetime.utcnow().replace(tzinfo=timezone.utc) - ACTIVE_WINDOW
players = [ players = [
dict(r) dict(r) for r in rows
for r in rows if datetime.fromisoformat(
if datetime.fromisoformat(r["timestamp"].replace("Z", "+00:00")) > cutoff r["timestamp"].replace('Z', '+00:00')
) > cutoff
] ]
return JSONResponse(content={"players": players}) return JSONResponse(content={"players": players})
@app.get("/history/") @app.get("/history/")
@app.get("/history") @app.get("/history")
def get_history( def get_history(
from_ts: str | None = Query(None, alias="from"), from_ts: str | None = Query(None, alias="from"),
to_ts: str | None = Query(None, alias="to"), to_ts: str | None = Query(None, alias="to"),
): ):
""" """
Returns a timeordered list of telemetry snapshots: Returns a timeordered list of telemetry snapshots:
@ -145,16 +142,15 @@ def get_history(
data = [ data = [
{ {
"timestamp": row["timestamp"], "timestamp": row["timestamp"],
"character_name": row["character_name"], "character_name":row["character_name"],
"kills": row["kills"], "kills": row["kills"],
"kph": row["kph"], "kph": row["kph"],
} }
for row in rows for row in rows
] ]
return JSONResponse(content={"data": data}) return JSONResponse(content={"data": data})
# ------------------------ GET Trails --------------------------------- # ------------------------ GET Trails ---------------------------------
@app.get("/trails") @app.get("/trails")
@app.get("/trails/") @app.get("/trails/")
@ -166,9 +162,7 @@ def get_trails(
for the past `seconds` seconds. for the past `seconds` seconds.
""" """
# match the same string format as stored timestamps (via str(datetime)) # match the same string format as stored timestamps (via str(datetime))
cutoff_dt = datetime.utcnow().replace(tzinfo=timezone.utc) - timedelta( cutoff_dt = datetime.utcnow().replace(tzinfo=timezone.utc) - timedelta(seconds=seconds)
seconds=seconds
)
cutoff = str(cutoff_dt) cutoff = str(cutoff_dt)
conn = sqlite3.connect(DB_FILE) conn = sqlite3.connect(DB_FILE)
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
@ -179,16 +173,16 @@ def get_trails(
WHERE timestamp >= ? WHERE timestamp >= ?
ORDER BY character_name, timestamp ORDER BY character_name, timestamp
""", """,
(cutoff,), (cutoff,)
).fetchall() ).fetchall()
conn.close() conn.close()
trails = [ trails = [
{ {
"timestamp": r["timestamp"], "timestamp": r["timestamp"],
"character_name": r["character_name"], "character_name": r["character_name"],
"ew": r["ew"], "ew": r["ew"],
"ns": r["ns"], "ns": r["ns"],
"z": r["z"], "z": r["z"],
} }
for r in rows for r in rows
] ]