Silently check today's NYT Wordle answer on startup

Queries the NYT wordle/v2 endpoint once per calendar day (cached via
~/.wordle_solver/state.json) and adds the word via the existing
/addword mechanism if it's missing from answers.txt. Fully silent in
every outcome; --no-nyt-check / --force-nyt-check control it from the
CLI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ert8f6E6yzmWg5qbcFPDLP
This commit is contained in:
2026-09-02 07:48:50 -04:00
parent d9295fa366
commit babca5df51
3 changed files with 214 additions and 0 deletions
+8
View File
@@ -5,6 +5,7 @@ import random
from collections import Counter
from .game import Clue, score_guess, str_to_pattern
from .nyt_check import maybe_check_nyt_word
from .solver import MAX_GUESSES, WORD_LEN, Solver, rank_guesses
from .wager import Wager
from .words import add_answer_word, load_answers, load_extra_guesses
@@ -155,6 +156,11 @@ def run(args=None):
help="weight on per-position letter frequency (default: 1.0)")
parser.add_argument("--presence-weight", type=float, default=1.0,
help="weight on overall letter presence frequency (default: 1.0)")
nyt_group = parser.add_mutually_exclusive_group()
nyt_group.add_argument("--no-nyt-check", action="store_true",
help="skip the automatic check of today's NYT Wordle answer")
nyt_group.add_argument("--force-nyt-check", action="store_true",
help="check today's NYT Wordle answer even if already checked today")
opts = parser.parse_args(args)
answers = load_answers()
@@ -163,6 +169,8 @@ def run(args=None):
wager = Wager()
state = {"secret": None, "guesses_made": 0}
maybe_check_nyt_word(solver, skip=opts.no_nyt_check, force=opts.force_nyt_check)
print("Wordle hard-mode assistant. Type 'help' for instructions.")
print(f"Wager prices: {wager.prices_str()}")
print_suggestions(solver, opts.top, opts.position_weight, opts.presence_weight)
+83
View File
@@ -0,0 +1,83 @@
"""Silent, once-a-day check of the NYT Wordle answer against our word list.
NYT's `svc/wordle/v2` endpoint discloses the solution for a given date as
soon as the local calendar rolls to that date. On startup we check today's
date once per day and add the word via the same mechanism as `/addword` if
it's missing from `answers.txt`. Never prints anything and never raises —
failures are silently ignored and retried on the next run.
"""
import json
import urllib.request
from datetime import date
from pathlib import Path
from .game import score_guess
from .solver import WORD_LEN, Solver
from .words import DATA_DIR, add_answer_word
STATE_PATH = Path.home() / ".wordle_solver" / "state.json"
NYT_URL_TEMPLATE = "https://www.nytimes.com/svc/wordle/v2/{date}.json"
REQUEST_TIMEOUT = 3
def get_state_path() -> Path:
return STATE_PATH
def load_state(state_path: Path = STATE_PATH) -> dict:
try:
return json.loads(state_path.read_text())
except (OSError, ValueError):
return {}
def save_state(state: dict, state_path: Path = STATE_PATH) -> None:
state_path.parent.mkdir(parents=True, exist_ok=True)
state_path.write_text(json.dumps(state))
def fetch_todays_answer() -> str | None:
url = NYT_URL_TEMPLATE.format(date=date.today().isoformat())
try:
with urllib.request.urlopen(url, timeout=REQUEST_TIMEOUT) as resp:
body = json.loads(resp.read())
word = body["solution"].strip().lower()
except Exception:
return None
if len(word) != WORD_LEN or not word.isalpha():
return None
return word
def maybe_check_nyt_word(solver: Solver, data_dir: Path = DATA_DIR,
state_path: Path = STATE_PATH,
skip: bool = False, force: bool = False) -> str | None:
if skip:
return None
today = date.today().isoformat()
state = load_state(state_path)
if not force and state.get("last_checked") == today:
return None
word = fetch_todays_answer()
if word is None:
return None
state["last_checked"] = today
save_state(state, state_path)
if word in solver.answers:
return None
result = add_answer_word(word, data_dir)
solver.answers.append(word)
solver.answers.sort()
if result["removed_from_guesses"]:
solver.extra_guesses.remove(word)
if all(score_guess(rec.guess, word) == rec.pattern for rec in solver.history):
solver.candidates.append(word)
return word