aboutsummaryrefslogtreecommitdiff
path: root/dailypuzzle.py
diff options
context:
space:
mode:
authorn0tori <188390306+n0tori@users.noreply.github.com>2026-06-17 21:13:19 +0100
committern0tori <188390306+n0tori@users.noreply.github.com>2026-06-17 21:13:19 +0100
commit52fdbc7682b53d9876e9550ace399b7280450a23 (patch)
treece9b557ef5c541839ea4f80f1025bbc6423f7dd8 /dailypuzzle.py
parentbc16fab83ac9ba897a469d7e8d24c7f6bc58658f (diff)
files
Diffstat (limited to 'dailypuzzle.py')
-rw-r--r--dailypuzzle.py59
1 files changed, 59 insertions, 0 deletions
diff --git a/dailypuzzle.py b/dailypuzzle.py
new file mode 100644
index 0000000..d4f884e
--- /dev/null
+++ b/dailypuzzle.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+import chess
+import chess.pgn
+import io
+import json
+import os
+import time
+import urllib.request
+
+OUT = "/var/www/aryanmustafa/api/dailypuzzle"
+INTERVAL = 3600 # check every hour; Lichess updates once per day
+
+os.makedirs(os.path.dirname(OUT), exist_ok=True)
+
+while True:
+ try:
+ req = urllib.request.Request(
+ "https://lichess.org/api/puzzle/daily",
+ headers={"Accept": "application/json"},
+ )
+ with urllib.request.urlopen(req, timeout=10) as r:
+ raw = json.loads(r.read())
+
+ puzzle = raw["puzzle"]
+ game = raw["game"]
+
+ # Lichess returns the full game PGN; the puzzle starts at initialPly.
+ # Replay up to that ply with python-chess to get the exact FEN.
+ pgn_text = game["pgn"]
+ initial_ply = puzzle["initialPly"]
+
+ parsed = chess.pgn.read_game(io.StringIO(pgn_text))
+ board = parsed.board()
+ for i, move in enumerate(parsed.mainline_moves()):
+ board.push(move)
+ if i >= initial_ply:
+ break
+
+ result = {
+ "id": puzzle["id"],
+ "fen": board.fen(),
+ "side": "white" if board.turn == chess.WHITE else "black",
+ "solution": puzzle["solution"], # UCI list e.g. ["e2e4","d7d5"]
+ "rating": puzzle.get("rating"),
+ "themes": puzzle.get("themes", []),
+ "url": f"https://lichess.org/training/{puzzle['id']}",
+ }
+
+ tmp = OUT + ".tmp"
+ with open(tmp, "w") as f:
+ json.dump(result, f)
+ os.replace(tmp, OUT) # atomic write
+
+ print(f"updated: puzzle {result['id']}", flush=True)
+
+ except Exception as e:
+ print(f"error: {e}", flush=True)
+
+ time.sleep(INTERVAL)