Use NYT Wordle API for daily word instead of local list

Fetches today's word from https://www.nytimes.com/svc/wordle/v2/YYYY-MM-DD.json
so puzzle numbers and solutions match the official NYT Wordle. Results are
cached per day; falls back to the local answer list if the API is unreachable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 10:21:30 -05:00
parent d0fd260336
commit 1219b73492

View File

@@ -11,6 +11,7 @@ from dataclasses import dataclass, field
from datetime import date from datetime import date
from pathlib import Path from pathlib import Path
import aiohttp
from nio import AsyncClient from nio import AsyncClient
from utils import send_text, send_html, get_or_create_dm from utils import send_text, send_html, get_or_create_dm
@@ -27,6 +28,9 @@ logger = logging.getLogger("matrixbot")
_WORDLE_EPOCH = date(2021, 6, 19) _WORDLE_EPOCH = date(2021, 6, 19)
# Cache: date string -> (word, puzzle_number)
_nyt_cache: dict[str, tuple[str, int]] = {}
# Build lookup sets at import time # Build lookup sets at import time
_ANSWER_LIST = [w.upper() for w in ANSWERS] _ANSWER_LIST = [w.upper() for w in ANSWERS]
_VALID_SET = frozenset(w.upper() for w in VALID_GUESSES) | frozenset(_ANSWER_LIST) _VALID_SET = frozenset(w.upper() for w in VALID_GUESSES) | frozenset(_ANSWER_LIST)
@@ -152,9 +156,36 @@ def _record_game_result(player_id: str, game: WordleGame):
# Core algorithms # Core algorithms
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def get_daily_word() -> tuple[str, int]: async def get_daily_word() -> tuple[str, int]:
"""Return (word, puzzle_number) for today's daily puzzle.""" """Return (word, puzzle_number) for today's daily puzzle.
Fetches from the NYT Wordle API. Falls back to the local word list
if the request fails.
"""
today = date.today() today = date.today()
date_str = today.strftime("%Y-%m-%d")
if date_str in _nyt_cache:
return _nyt_cache[date_str]
try:
url = f"https://www.nytimes.com/svc/wordle/v2/{date_str}.json"
timeout = aiohttp.ClientTimeout(total=5)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as resp:
if resp.status == 200:
data = await resp.json(content_type=None)
word = data["solution"].upper()
puzzle_number = int(data["id"])
_nyt_cache[date_str] = (word, puzzle_number)
logger.info("NYT Wordle #%d: %s", puzzle_number, word)
return word, puzzle_number
else:
logger.warning("NYT Wordle API returned %d, falling back to local list", resp.status)
except Exception as e:
logger.warning("Failed to fetch NYT Wordle word: %s — falling back to local list", e)
# Fallback: use local answer list
puzzle_number = (today - _WORDLE_EPOCH).days puzzle_number = (today - _WORDLE_EPOCH).days
word = _ANSWER_LIST[puzzle_number % len(_ANSWER_LIST)] word = _ANSWER_LIST[puzzle_number % len(_ANSWER_LIST)]
return word, puzzle_number return word, puzzle_number
@@ -455,7 +486,7 @@ async def wordle_start_or_status(client: AsyncClient, room_id: str, sender: str,
return return
# Check if already completed today's puzzle # Check if already completed today's puzzle
word, puzzle_number = get_daily_word() word, puzzle_number = await get_daily_word()
stats = _get_player_stats(sender) stats = _get_player_stats(sender)
if stats["last_daily"] == puzzle_number: if stats["last_daily"] == puzzle_number: