aboutsummaryrefslogtreecommitdiff
path: root/dailypuzzle.py
blob: d4f884e5f4a9077b1c1c7fd1d59b72f4365a0578 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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)