Compare commits
7 commits
lundberg_a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ad5e1d478a | |||
|
|
b19568b1ab | ||
|
|
4a826e5ac2 | ||
|
|
c3cb93d903 | ||
|
|
a00cfb688c | ||
|
|
4f9fdb911e | ||
| 13dac398c5 |
5 changed files with 95 additions and 3 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
45
generate_data.py
Normal file
45
generate_data.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import httpx
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from time import sleep
|
||||||
|
from main import TelemetrySnapshot
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
wait = 10
|
||||||
|
online_time = 24 * 3600 # start at 1 day
|
||||||
|
ew = 0
|
||||||
|
ns = 0
|
||||||
|
while True:
|
||||||
|
snapshot = TelemetrySnapshot(
|
||||||
|
character_name="Test name",
|
||||||
|
char_tag="test_tag",
|
||||||
|
session_id="test_session_id",
|
||||||
|
timestamp=datetime.now(tz=timezone.utc),
|
||||||
|
ew=ew,
|
||||||
|
ns=ns,
|
||||||
|
z=0,
|
||||||
|
kills=0,
|
||||||
|
kills_per_hour="kph_str",
|
||||||
|
onlinetime=str(timedelta(seconds=online_time)),
|
||||||
|
deaths=0,
|
||||||
|
rares_found=0,
|
||||||
|
prismatic_taper_count=0,
|
||||||
|
vt_state="test state",
|
||||||
|
)
|
||||||
|
resp = httpx.post(
|
||||||
|
"http://localhost:8000/position/",
|
||||||
|
data=snapshot.model_dump_json(),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-PLUGIN-SECRET": "your_shared_secret",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
print(resp)
|
||||||
|
sleep(wait)
|
||||||
|
ew += 0.1
|
||||||
|
ns += 0.1
|
||||||
|
online_time += wait
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
40
main.py
40
main.py
|
|
@ -3,7 +3,7 @@ import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from fastapi import FastAPI, Header, HTTPException, Query
|
from fastapi import FastAPI, Header, HTTPException, Query, WebSocket, WebSocketDisconnect
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.routing import APIRoute
|
from fastapi.routing import APIRoute
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
@ -11,6 +11,8 @@ from pydantic import BaseModel
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from db import init_db, save_snapshot, DB_FILE
|
from db import init_db, save_snapshot, DB_FILE
|
||||||
|
import asyncio
|
||||||
|
from starlette.concurrency import run_in_threadpool
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
@ -194,6 +196,42 @@ def get_trails(
|
||||||
]
|
]
|
||||||
return JSONResponse(content={"trails": trails})
|
return JSONResponse(content={"trails": trails})
|
||||||
|
|
||||||
|
# -------------------- WebSocket endpoints -----------------------
|
||||||
|
browser_conns: set[WebSocket] = set()
|
||||||
|
|
||||||
|
async def _broadcast_to_browser_clients(snapshot: dict):
|
||||||
|
for ws in list(browser_conns):
|
||||||
|
try:
|
||||||
|
await ws.send_json(snapshot)
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
browser_conns.remove(ws)
|
||||||
|
|
||||||
|
@app.websocket("/ws/position")
|
||||||
|
async def ws_receive_snapshots(websocket: WebSocket, secret: str = Query(...)):
|
||||||
|
await websocket.accept()
|
||||||
|
if secret != SHARED_SECRET:
|
||||||
|
await websocket.close(code=1008)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = await websocket.receive_json()
|
||||||
|
snap = TelemetrySnapshot.parse_obj(data)
|
||||||
|
live_snapshots[snap.character_name] = snap.dict()
|
||||||
|
await run_in_threadpool(save_snapshot, snap.dict())
|
||||||
|
await _broadcast_to_browser_clients(snap.dict())
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@app.websocket("/ws/live")
|
||||||
|
async def ws_live_updates(websocket: WebSocket):
|
||||||
|
await websocket.accept()
|
||||||
|
browser_conns.add(websocket)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(3600)
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
browser_conns.remove(websocket)
|
||||||
|
|
||||||
|
|
||||||
# -------------------- static frontend ---------------------------
|
# -------------------- static frontend ---------------------------
|
||||||
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,8 @@ function render(players) {
|
||||||
<span class="stat kph">${p.kills_per_hour}</span>
|
<span class="stat kph">${p.kills_per_hour}</span>
|
||||||
<span class="stat rares">${p.rares_found}</span>
|
<span class="stat rares">${p.rares_found}</span>
|
||||||
<span class="stat meta">${p.vt_state}</span>
|
<span class="stat meta">${p.vt_state}</span>
|
||||||
|
<span class="stat onlinetime">${p.onlinetime}</span>
|
||||||
|
<span class="stat deaths">${p.deaths}</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
li.addEventListener('click', () => selectPlayer(p, x, y));
|
li.addEventListener('click', () => selectPlayer(p, x, y));
|
||||||
|
|
|
||||||
|
|
@ -156,11 +156,12 @@ body {
|
||||||
#playerList li {
|
#playerList li {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
grid-template-rows: auto auto auto;
|
grid-template-rows: auto auto auto auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"name loc"
|
"name loc"
|
||||||
"kills kph"
|
"kills kph"
|
||||||
"rares meta";
|
"rares meta"
|
||||||
|
"onlinetime deaths";
|
||||||
gap: 4px 8px;
|
gap: 4px 8px;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
|
|
@ -178,6 +179,8 @@ body {
|
||||||
.stat.kph { grid-area: kph; }
|
.stat.kph { grid-area: kph; }
|
||||||
.stat.rares { grid-area: rares; }
|
.stat.rares { grid-area: rares; }
|
||||||
.stat.meta { grid-area: meta; }
|
.stat.meta { grid-area: meta; }
|
||||||
|
.stat.onlinetime { grid-area: onlinetime; }
|
||||||
|
.stat.deaths { grid-area: deaths; }
|
||||||
|
|
||||||
/* pill styling */
|
/* pill styling */
|
||||||
#playerList li .stat {
|
#playerList li .stat {
|
||||||
|
|
@ -198,6 +201,8 @@ body {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: #111;
|
color: #111;
|
||||||
}
|
}
|
||||||
|
.stat.onlinetime::before { content: "🕑 "}
|
||||||
|
.stat.deaths::before { content: "💀 "}
|
||||||
|
|
||||||
/* hover & selected states */
|
/* hover & selected states */
|
||||||
#playerList li:hover { background: var(--card-hov); }
|
#playerList li:hover { background: var(--card-hov); }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue