Skip to content

wordoftheday: read from official RSS feed (Merriam-Webster now returns 403) - #3279

Open
gderoo wants to merge 1 commit into
tidbyt:mainfrom
gderoo:fix/wordoftheday-rss-feed
Open

gderoo wants to merge 1 commit into
tidbyt:mainfrom
gderoo:fix/wordoftheday-rss-feed

Conversation

@gderoo

@gderoo gderoo commented Sep 19, 2026

Copy link
Copy Markdown

Problem

The Word Of The Day app always renders "Something went wrong getting today's word!".

The cause is not a stale CSS selector. https://www.merriam-webster.com/word-of-the-day/calendar now returns HTTP 403 to server-side requests, so the app never reaches its parsing step:

[word_of_the_day.star] Starting
[word_of_the_day.star] Cache miss
[word_of_the_day.star] Got code '403' from page response

The response is a Cloudflare bot challenge (cf-mitigated: challenge, body titled "Just a moment..."), returned regardless of User-Agent or request headers. I tested no-UA, a Go-http-client UA, a Chrome UA and a full browser header set — all 403, on both /word-of-the-day/calendar and /word-of-the-day.

Fix

Merriam-Webster's official WOTD RSS feed, https://www.merriam-webster.com/wotd/feed/rss2, is not subject to that rule:

Path Status Cloudflare
/word-of-the-day/calendar 403 cf-mitigated: challenge
/wotd/feed/rss2 200 no mitigation

Same edge, same client, same second — the challenge is scoped to the HTML paths, so the feed is reachable server-side. It also exposes a <merriam:shortdef> element carrying a short definition, which suits the 64x32 display and matches what the calendar scrape was after.

Changes

  • html.star → xpath.star; calendar URL → feed URL
  • Word from item[1]/title, definition from item[1]/merriam:shortdef. Items are newest-first, so the first is today's word — deliberately not date-matched, since the feed publishes at 01:00 ET and a device in a timezone ahead of that would find no entry for its "today"
  • Guard the body for <rss before parsing. xpath.loads raises on non-XML, so an unexpected HTML response would crash the app rather than render the error state — which matters here, since the failure being fixed is Cloudflare serving HTML
  • Empty-check now also catches None, which query() returns for a missing node

No new services or API keys. Render layout, colors, fonts, cache key, TTL and cached payload shape are unchanged, and error states are still never cached.

Testing

Verified with pixlet v0.34.0.

  • pixlet render — renders today's word correctly
  • pixlet lint — clean
  • pixlet check apps/wordoftheday/ — ✔️
  • pixlet format — no changes

Failure paths, each confirmed to render the error state without crashing:

Case Result
Non-200 (403) error state
200 but not XML caught by the <rss guard
Valid RSS, no merriam:shortdef error state
Valid RSS, no <title> error state

The last two were tested against crafted RSS fixtures served locally rather than a live feed. A two-item fixture also confirmed the app selects item 1 and ignores item 2, so the newest-first selection is doing what it claims rather than coincidentally matching today's word. The cache-hit path was verified to read back the written payload.

🤖 Generated with Claude Code

…s 403)

The calendar page the app scraped is behind a Cloudflare bot challenge and
returns HTTP 403 to server-side requests (cf-mitigated: challenge), so the
app never reached its parsing step and always rendered the error state.

Merriam-Webster's official WOTD RSS feed is not subject to that rule and
returns 200 from the same edge. It exposes a <merriam:shortdef> element
carrying a short definition, which suits the display and matches what the
calendar scrape was after.

- html.star -> xpath.star; calendar URL -> feed URL
- word from item[1]/title, definition from item[1]/merriam:shortdef
- guard the body for "<rss" before parsing: xpath.loads errors on non-XML,
  so an unexpected HTML response would crash rather than render the error
- empty-check now also catches None, which query() returns for a missing node

Render layout, colors, fonts, cache key, TTL and cached payload shape are
unchanged, and error states are still never cached.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tidbyt

tidbyt Bot commented Sep 19, 2026

Copy link
Copy Markdown

⚠️ The automated review process is experimental and likely has bugs. Please bear with us as we iron out the kinks and enable you to ship changes at high velocity 🚀

Next Steps

Hello! Thank you so much for your change 🤜 🤛 . There are a few things you need to do:

  • Sign the CLA if you haven't already
  • Ensure your build is green! Any problem will display a proposed solution to try out
  • Get a review, either by Tidbyt Bot or by a Tidbyt engineer

Manual Review Required

Hang tight! A Tidbyt engineer will be by shortly to review your change. Here is what they will be looking for:

Test Details
✅ App Dir All files are in a single app directory
🟡 Modules Usage of http.star requires review
🟡 Original Author The original author (greg-n) does not match the PR author (gderoo)

@tidbyt-bot

tidbyt-bot commented Sep 19, 2026 •

Copy link
Copy Markdown

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@gderoo

gderoo commented Sep 19, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@gderoo

gderoo commented Sep 19, 2026

Copy link
Copy Markdown
Author

recheck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants