From 1219b734922560c70fa22157be8ded939f0f1569 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Mon, 23 Feb 2026 10:21:30 -0500 Subject: [PATCH] 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 --- wordle.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/wordle.py b/wordle.py index cccf4fa..b95a0b2 100644 --- a/wordle.py +++ b/wordle.py @@ -11,6 +11,7 @@ from dataclasses import dataclass, field from datetime import date from pathlib import Path +import aiohttp from nio import AsyncClient 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) +# Cache: date string -> (word, puzzle_number) +_nyt_cache: dict[str, tuple[str, int]] = {} + # Build lookup sets at import time _ANSWER_LIST = [w.upper() for w in ANSWERS] _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 # --------------------------------------------------------------------------- -def get_daily_word() -> tuple[str, int]: - """Return (word, puzzle_number) for today's daily puzzle.""" +async def get_daily_word() -> tuple[str, int]: + """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() + 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 word = _ANSWER_LIST[puzzle_number % len(_ANSWER_LIST)] return word, puzzle_number @@ -455,7 +486,7 @@ async def wordle_start_or_status(client: AsyncClient, room_id: str, sender: str, return # 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) if stats["last_daily"] == puzzle_number: