Lobby: desktop layout with chat column, dice hotbar and shared dice tray, server-side rolls
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
+48
-11
@@ -521,6 +521,19 @@ def read_lobby(lid: str, user: dict = Depends(current_user)):
|
||||
"code": lobby["code"] if is_dm else None, "members": members}
|
||||
|
||||
|
||||
def _recipient(con, lobby, user: dict, to):
|
||||
"""User id to whisper to (the DM or a member, not yourself), None for everybody."""
|
||||
to = str(to or "").strip()
|
||||
if not to:
|
||||
return None
|
||||
target = con.execute("""SELECT u.id FROM users u WHERE u.username=? AND (u.id=? OR EXISTS
|
||||
(SELECT 1 FROM lobby_members m WHERE m.lobby_id=? AND m.user_id=u.id))""",
|
||||
(to, lobby["owner_id"], lobby["id"])).fetchone()
|
||||
if not target or target["id"] == user["id"]:
|
||||
raise HTTPException(422, "You can only whisper to other people in this lobby")
|
||||
return target["id"]
|
||||
|
||||
|
||||
@api.get("/lobbies/{lid}/messages")
|
||||
def read_messages(lid: str, after: int = 0, user: dict = Depends(current_user)):
|
||||
"""Chat and event log. Whispers are only returned to the sender and the recipient (not even the DM sees them).
|
||||
@@ -534,8 +547,14 @@ def read_messages(lid: str, after: int = 0, user: dict = Depends(current_user)):
|
||||
rows = con.execute(q + " AND m.id>? ORDER BY m.id LIMIT 200", (lid, user["id"], user["id"], after)).fetchall()
|
||||
else:
|
||||
rows = list(reversed(con.execute(q + " ORDER BY m.id DESC LIMIT 100", (lid, user["id"], user["id"])).fetchall()))
|
||||
return [{"id": r["id"], "kind": r["kind"], "text": r["text"], "time": r["created"], "from": r["sender"] or "",
|
||||
"to": r["target"] or "", "mine": r["user_id"] == user["id"] and r["kind"] != "event"} for r in rows]
|
||||
out = []
|
||||
for r in rows:
|
||||
item = {"id": r["id"], "kind": r["kind"], "text": r["text"], "time": r["created"], "from": r["sender"] or "",
|
||||
"to": r["target"] or "", "mine": r["user_id"] == user["id"] and r["kind"] != "event"}
|
||||
if r["kind"] == "roll":
|
||||
item["roll"], item["text"] = json.loads(r["text"]), ""
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
@api.post("/lobbies/{lid}/messages")
|
||||
@@ -549,15 +568,7 @@ def send_message(lid: str, body: dict = Body(...), user: dict = Depends(current_
|
||||
limiter.fail(f"chat:{user['id']}")
|
||||
with db() as con:
|
||||
lobby = _lobby_for(con, lid, user)
|
||||
to = str(body.get("to") or "").strip()
|
||||
to_id = None
|
||||
if to:
|
||||
target = con.execute("""SELECT u.id FROM users u WHERE u.username=? AND (u.id=? OR EXISTS
|
||||
(SELECT 1 FROM lobby_members m WHERE m.lobby_id=? AND m.user_id=u.id))""",
|
||||
(to, lobby["owner_id"], lid)).fetchone()
|
||||
if not target or target["id"] == user["id"]:
|
||||
raise HTTPException(422, "You can only whisper to other people in this lobby")
|
||||
to_id = target["id"]
|
||||
to_id = _recipient(con, lobby, user, body.get("to"))
|
||||
cur = con.execute("INSERT INTO lobby_messages (lobby_id, user_id, to_user_id, kind, text, created) VALUES (?,?,?,?,?,?)",
|
||||
(lid, user["id"], to_id, "whisper" if to_id else "chat", text, time.time()))
|
||||
con.execute("""DELETE FROM lobby_messages WHERE lobby_id=? AND id <= COALESCE(
|
||||
@@ -565,6 +576,32 @@ def send_message(lid: str, body: dict = Body(...), user: dict = Depends(current_
|
||||
return {"id": cur.lastrowid}
|
||||
|
||||
|
||||
DICE = (4, 6, 8, 10, 12, 20, 100)
|
||||
MAX_DICE = 10
|
||||
|
||||
|
||||
@api.post("/lobbies/{lid}/rolls")
|
||||
def roll_dice(lid: str, body: dict = Body(...), user: dict = Depends(current_user)):
|
||||
"""Rolls on the server (so nobody can cheat) and posts the result in the chat; with `to` it is a private roll."""
|
||||
try:
|
||||
sides, count = int(body.get("sides")), int(body.get("count", 1))
|
||||
except (TypeError, ValueError):
|
||||
raise HTTPException(422, "Invalid dice")
|
||||
if sides not in DICE or not 1 <= count <= MAX_DICE:
|
||||
raise HTTPException(422, f"Roll 1 to {MAX_DICE} dice of: " + ", ".join(f"d{d}" for d in DICE))
|
||||
throttle(f"chat:{user['id']}", 30, 60)
|
||||
limiter.fail(f"chat:{user['id']}")
|
||||
results = [secrets.randbelow(sides) + 1 for _ in range(count)]
|
||||
with db() as con:
|
||||
lobby = _lobby_for(con, lid, user)
|
||||
to_id = _recipient(con, lobby, user, body.get("to"))
|
||||
cur = con.execute("INSERT INTO lobby_messages (lobby_id, user_id, to_user_id, kind, text, created) VALUES (?,?,?,?,?,?)",
|
||||
(lid, user["id"], to_id, "roll", json.dumps({"sides": sides, "results": results}), time.time()))
|
||||
con.execute("""DELETE FROM lobby_messages WHERE lobby_id=? AND id <= COALESCE(
|
||||
(SELECT id FROM lobby_messages WHERE lobby_id=? ORDER BY id DESC LIMIT 1 OFFSET ?), 0)""", (lid, lid, KEEP_MESSAGES))
|
||||
return {"id": cur.lastrowid, "sides": sides, "results": results}
|
||||
|
||||
|
||||
@api.put("/lobbies/{lid}")
|
||||
def rename_lobby(lid: str, body: dict = Body(...), user: dict = Depends(current_user)):
|
||||
name = str(body.get("name", "")).strip()[:60]
|
||||
|
||||
Reference in New Issue
Block a user