Spaces:
Paused
Paused
Download app/store_sqlite.py from ihhereanth/roblox-npc-api: direct link, hf CLI and curl.
- Browser
- Download file 5.65 kB
-
https://huggingface.co/spaces/ihhereanth/roblox-npc-api/resolve/main/app/store_sqlite.py
- Command line
-
hf download hf://spaces/ihhereanth/roblox-npc-api/app/store_sqlite.py
-
curl -L -o store_sqlite.py https://huggingface.co/spaces/ihhereanth/roblox-npc-api/resolve/main/app/store_sqlite.py
5.65 kB
| # app/store_sqlite.py | |
| import json | |
| import os | |
| import sqlite3 | |
| import threading | |
| import time | |
| WINDOW = 6 # ΰΉΰΈΰΉΰΈΰΈΰΈ΅ΰΉΰΉΰΈΰΈ΄ΰΈ£ΰΉΰΈΰΈ₯ΰΉΰΈ²ΰΈͺΰΈΈΰΈ | |
| TURN_TTL = 7200 # 2 ΰΈΰΈ‘. | |
| SESS_TTL = 86400 # 24 ΰΈΰΈ‘. | |
| def _pick_db_path() -> str: | |
| """ΰΈ«ΰΈ²ΰΉΰΈΰΈ₯ΰΉΰΈΰΈΰΈ£ΰΉΰΈΰΈ΅ΰΉΰΉΰΈΰΈ΅ΰΈ’ΰΈΰΉΰΈΰΉΰΈΰΈ£ΰΈ΄ΰΈ β Space ΰΈΰΈ£ΰΈ΅ΰΉΰΈ‘ΰΉΰΈ‘ΰΈ΅ /data""" | |
| cands = [] | |
| if os.environ.get("DB_PATH"): | |
| cands.append(os.environ["DB_PATH"]) | |
| cands += ["/data/npc.db", "/home/user/app/data/npc.db", "/tmp/npc.db"] | |
| for c in cands: | |
| try: | |
| d = os.path.dirname(c) or "." | |
| os.makedirs(d, exist_ok=True) | |
| probe = os.path.join(d, ".wtest") | |
| with open(probe, "w") as f: | |
| f.write("1") | |
| os.remove(probe) | |
| return c | |
| except Exception: | |
| continue | |
| return ":memory:" | |
| DB_PATH = _pick_db_path() | |
| print(f"[store] db path = {DB_PATH}", flush=True) | |
| _conn = sqlite3.connect(DB_PATH, check_same_thread=False, timeout=5.0) | |
| _conn.execute("PRAGMA journal_mode=WAL") # ΰΈΰΉΰΈ²ΰΈ/ΰΉΰΈΰΈ΅ΰΈ’ΰΈΰΈΰΈ£ΰΉΰΈΰΈ‘ΰΈΰΈ±ΰΈΰΉΰΈΰΉ | |
| _conn.execute("PRAGMA synchronous=NORMAL") # ΰΉΰΈ£ΰΉΰΈ§ΰΈΰΈΆΰΉΰΈ ~3x | |
| _conn.execute("PRAGMA busy_timeout=5000") | |
| _lock = threading.Lock() | |
| _conn.executescript(""" | |
| CREATE TABLE IF NOT EXISTS turns ( | |
| sid TEXT, seq INTEGER, u TEXT, a TEXT, beat TEXT, ts INTEGER, | |
| PRIMARY KEY (sid, seq)); | |
| CREATE TABLE IF NOT EXISTS sessions ( | |
| sid TEXT PRIMARY KEY, pid TEXT, npc TEXT, | |
| summary TEXT DEFAULT '', n INTEGER DEFAULT 0, ts INTEGER); | |
| CREATE TABLE IF NOT EXISTS players ( | |
| pid TEXT PRIMARY KEY, name TEXT, facts TEXT DEFAULT '{}', | |
| rel TEXT DEFAULT '{}', quests TEXT DEFAULT '{}', | |
| v INTEGER DEFAULT 0, last_seen INTEGER); | |
| CREATE TABLE IF NOT EXISTS rl ( | |
| pid TEXT, minute INTEGER, n INTEGER, PRIMARY KEY (pid, minute)); | |
| CREATE INDEX IF NOT EXISTS ix_turns ON turns(sid, seq DESC); | |
| """) | |
| _conn.commit() | |
| # βββββββββββ window βββββββββββ | |
| def push_turn(sid, pid, npc, u, a): | |
| now = int(time.time()) | |
| with _lock, _conn: | |
| _conn.execute( | |
| "INSERT INTO sessions(sid,pid,npc,n,ts) VALUES(?,?,?,1,?) " | |
| "ON CONFLICT(sid) DO UPDATE SET n=n+1, ts=excluded.ts", | |
| (sid, pid, npc, now)) | |
| n = _conn.execute("SELECT n FROM sessions WHERE sid=?", (sid,)).fetchone()[0] | |
| _conn.execute("INSERT OR REPLACE INTO turns VALUES(?,?,?,?,?,?)", | |
| (sid, n, u, a, None, now)) | |
| _conn.execute("DELETE FROM turns WHERE sid=? AND seq<=?", (sid, n - WINDOW)) | |
| return n | |
| def get_window(sid): | |
| rows = _conn.execute( | |
| "SELECT u,a,beat FROM turns WHERE sid=? ORDER BY seq ASC", (sid,)).fetchall() | |
| return [{"u": r[0], "a": r[1], "beat": r[2]} for r in rows] | |
| def get_turn_count(sid): | |
| r = _conn.execute("SELECT n FROM sessions WHERE sid=?", (sid,)).fetchone() | |
| return r[0] if r else 0 | |
| def reset_session(sid): | |
| with _lock, _conn: | |
| _conn.execute("DELETE FROM turns WHERE sid=?", (sid,)) | |
| _conn.execute("DELETE FROM sessions WHERE sid=?", (sid,)) | |
| # βββββββββββ summary βββββββββββ | |
| def get_summary(sid): | |
| r = _conn.execute("SELECT summary FROM sessions WHERE sid=?", (sid,)).fetchone() | |
| return (r[0] if r else "") or "" | |
| def set_summary(sid, text): | |
| with _lock, _conn: | |
| _conn.execute("UPDATE sessions SET summary=? WHERE sid=?", (text, sid)) | |
| # βββββββββββ profile βββββββββββ | |
| def get_profile(pid): | |
| r = _conn.execute( | |
| "SELECT name,facts,rel,quests,v FROM players WHERE pid=?", (pid,)).fetchone() | |
| if not r: | |
| return {"pid": pid, "name": None, "facts": {}, "rel": {}, "quests": {}, "v": 0} | |
| return {"pid": pid, "name": r[0], "facts": json.loads(r[1]), | |
| "rel": json.loads(r[2]), "quests": json.loads(r[3]), "v": r[4]} | |
| def save_profile(p): | |
| with _lock, _conn: | |
| _conn.execute( | |
| "INSERT INTO players(pid,name,facts,rel,quests,v,last_seen) " | |
| "VALUES(?,?,?,?,?,?,?) ON CONFLICT(pid) DO UPDATE SET " | |
| "name=excluded.name, facts=excluded.facts, rel=excluded.rel, " | |
| "quests=excluded.quests, v=players.v+1, last_seen=excluded.last_seen", | |
| (p["pid"], p.get("name"), json.dumps(p.get("facts", {})), | |
| json.dumps(p.get("rel", {})), json.dumps(p.get("quests", {})), | |
| p.get("v", 0) + 1, int(time.time()))) | |
| # βββββββββββ rate limit βββββββββββ | |
| def rate_limit(pid, limit=12): | |
| m = int(time.time() // 60) | |
| with _lock, _conn: | |
| _conn.execute("INSERT INTO rl(pid,minute,n) VALUES(?,?,1) " | |
| "ON CONFLICT(pid,minute) DO UPDATE SET n=n+1", (pid, m)) | |
| n = _conn.execute("SELECT n FROM rl WHERE pid=? AND minute=?", | |
| (pid, m)).fetchone()[0] | |
| return n <= limit | |
| # βββββββββββ janitor (ΰΉΰΈΰΈ TTL ΰΈΰΈΰΈ Redis) βββββββββββ | |
| def janitor(): | |
| now = int(time.time()) | |
| with _lock, _conn: | |
| _conn.execute("DELETE FROM rl WHERE minute < ?", (now // 60 - 2,)) | |
| _conn.execute("DELETE FROM turns WHERE ts < ?", (now - TURN_TTL,)) | |
| _conn.execute("DELETE FROM sessions WHERE ts < ?", (now - SESS_TTL,)) | |
| def stats(): | |
| q = lambda s: _conn.execute(s).fetchone()[0] | |
| return {"db": DB_PATH, "turns": q("SELECT COUNT(*) FROM turns"), | |
| "sessions": q("SELECT COUNT(*) FROM sessions"), | |
| "players": q("SELECT COUNT(*) FROM players")} |