Fires 3x/day to Discord: a morning digest, then afternoon/evening reminders that only mention what's still outstanding (silent if everything's already done — no nagging for the sake of it). The morning ping also includes a live "All quests" snapshot for MS Rewards and Xbox (see below) — informational only, not tied to streaks.
- Brilliant Daily — ✅ auto-verified, via a real headless browser (Kernel) reading your actual rendered streak number.
- MS Rewards — Bing daily set — ✅ auto-verified, via the same Kernel approach, reading the real dashboard's "Activity: N/N" text.
- MS Rewards — Xbox / bonus activities — ⚠️ not a streak item at all
(removed from
ITEMS2026-09-12). Quests rotate (daily/weekly/monthly) rather than being a clean "done once a day" thing, so there was never a real boolean to verify —checkMsRewardsXbox(still inlib/checkers/msRewards.ts) always returned{ done: null }, which permanently stuck both this item and the combined "full day" streak at zero. Rather than show a checklist line and a streak number that could never move, it's just not inITEMSany more. Xbox is still covered properly — every current quest is listed in the morning "All quests" snapshot (see below).
A separate Discord message listing every current MS Rewards perk/activity and every current Xbox quest with its progress, e.g.:
Bing — Search: 1/1
Daily Set — Activity: 3/3
Play a PC game -- 0/1 (20 pts, Available)
Weekly PC bonus -- 1/5 (260 pts, Expired)
- MS Rewards' half is computed live, for free, during the same Kernel
session as the Bing daily-set check above (it also visits
/earn, not just the dashboard — no extra Kernel cost, just one more page navigation in the same browser session). Scoped to exactly three sections from Earn, per Connor's request (2026-09-12): Streaks, Explore on Bing, and Keep earning — not the dashboard's "Your perks", nor Earn's "Level up activities"/"Quests". Streaks are listed individually; Explore on Bing/Keep earning are summarized as counts (mostly one-off quiz/article cards, not meaningfully individual quests). SeeextractEarnPageLinesinlib/checkers/msRewards.tsto adjust scope again later. - Xbox's half comes from a local PC script (
C:\dev\xbox-quest-check, see below) that runs once each morning and reports in viaquests.ts, since the Xbox app can only be read from Connor's own machine.
The Xbox app's quests have no web page and no public API (confirmed: checked xbox.com/Rewards, the signed-in xbox.com/play hub, site search, and actual network traffic — nothing). A full network-capture attempt with mitmproxy also came up empty even with loopback exemptions and both WinINET/WinHTTP proxy settings correctly configured — the app simply doesn't route through either proxy surface, not even a rejected connection attempt showed up.
What does work: the Xbox app's UI Automation tree (the same accessibility
API screen readers use) already contains everything as plain text, e.g.
"Weekly PC bonus, Play 5 days this week on PC for 15 minutes., 260 points, 1 out of 5 completed, Expired". C:\dev\xbox-quest-check\check.ps1 reads
that tree directly — no cookies, no auth, no network capture, just reading
what's already rendered on screen. Full details in that project's own
README.
Both Brilliant and MS Rewards Bing use Kernel (real
headless Chrome in the cloud) and both need their session cookie injected
via Puppeteer's page.setCookie(), NOT
page.setExtraHTTPHeaders({ Cookie: ... }).
This isn't a style preference — it was the difference between working and not. The debugging trail, for whenever this breaks again:
- A plain authenticated
fetch()(no browser at all) genuinely can't see MS Rewards' data from Val Town's IP, Kernel's IP, or Connor's own home IP — always landed on the logged-out/aboutpage. - Tried: re-grabbing a fresher cookie, grabbing it via DevTools' Network
tab instead of
document.cookie(to catch httpOnly cookies), using "Copy value" instead of drag-select, adding a full realistic Chrome header set (Sec-Fetch-*,sec-ch-ua, etc.), forcing TLS 1.2. None of it worked — every one of those attempts still used a rawCookieheader override, whether viafetch()or Puppeteer'ssetExtraHTTPHeaders(). - Switching to
page.setCookie()— letting Chrome's own cookie jar carry the session instead of forcing a header — worked immediately, first try, from Val Town's IP. Whatever Microsoft's edge was checking, it cared about how the cookie arrived, not just its value or freshness.
Lesson for future checkers on either of these sites (or similar
Microsoft/Google-style properties): always use setCookie(), never
setExtraHTTPHeaders for auth cookies.
✅ Already set.
- Create a free account at https://onkernel.com.
- Go to Settings → API Keys → generate one.
- Paste it here:
👉 Add KERNEL_API_KEY here: https://www.val.town/x/CAS98/daily-streak-checklist/environment-variables?key=KERNEL_API_KEY
A real session credential — treat it like a password. It rides along in every request as proof you're logged in, and it expires periodically (you'll need to repeat this every so often when the check starts coming back "unverifiable").
- Log into https://rewards.bing.com in your browser as normal.
- Open DevTools (F12) → Network tab, then reload the page.
- Click the first request to
rewards.bing.com, scroll to Request Headers, right-click theCookierow → Copy value (not drag-select — long values wrap and drag-select can silently corrupt them). - Paste it here:
👉 Add REWARDS_COOKIE here: https://www.val.town/x/CAS98/daily-streak-checklist/environment-variables?key=REWARDS_COOKIE
Same idea, same method (Network tab, not document.cookie — Brilliant's
auth cookie is httpOnly):
- Log into https://brilliant.org, go to https://brilliant.org/you/.
- DevTools (F12) → Network tab → reload.
- Click the first request to
brilliant.org→ Request Headers → Copy value onCookie. - Paste it here:
👉 Add BRILLIANT_COOKIE here: https://www.val.town/x/CAS98/daily-streak-checklist/environment-variables?key=BRILLIANT_COOKIE
Shared secret that quests.ts checks against, so only trusted local
scripts (currently just the Xbox quest reader) can write into
quest_snapshots. An earlier version of this README called it unused and
safe to delete — that was wrong and caused a real outage (the Xbox script
started getting 401s once it was deleted). It is used. If it ever needs
regenerating, update it in both places — here and in
C:\dev\xbox-quest-check's XBOX_REPORT_SECRET line in secrets.env — or
every local reporter script breaks silently until someone notices missing
quest data.
- Both Kernel-based scrapers are unofficial — they parse undocumented
page markup that can change without notice. If an item starts showing
"⚠️ couldn't auto-verify" every run, check
get_logsfor this val — usually either a cookie expiring (re-grab via the steps above) or the site changing its markup, in which case the relevant file inlib/checkers/needs a look. - Both cookies are live session tokens. Anyone who has one can act as your logged-in session on that site. They're stored encrypted as Val Town env vars (same protection as any other secret here), but they're inherently more sensitive than a typical API key — to kill one, sign out of that browser session and grab a fresh one.
- Brilliant detection is "did the streak number change since last time we looked," not a true daily flag (Brilliant doesn't expose one). This is correct even across a missed-day reset, but if you complete two days' worth of lessons back-to-back before a scheduled check runs, only one day gets credited (the counter can't tell you did 2 vs 1).
- Cron runs in UTC, fixed at 07:00 / 13:00 / 20:00 UTC — during UK
daylight saving (BST, roughly late Mar–late Oct) that's 8am/2pm/9pm
local; during GMT (winter) it drifts an hour earlier. Adjust the cron in
morning.ts/afternoon.ts/evening.ts(viawrite_interval_settings) if that bugs you. - Streak day boundary is midnight Europe/London, independent of the ping times above.
- Kernel costs a small amount per check (3x/day × 2 checkers = 6 headless browser sessions/day). Check Kernel's pricing if that matters.
- The Xbox quest snapshot depends on a local Scheduled Task
(
XboxQuestCheck, ~7:40am daily) on Connor's PC, which itself depends on the Xbox app being launchable and the PC being on. If it's ever missing from the morning ping, checkC:\dev\xbox-quest-check\check.logfirst.
Add an entry to ITEMS in lib/config.ts — a key, displayName, and a
checker: () => Promise<CheckOutcome> — for something with a real daily
done/not-done state. No other file needs to change — streak tracking and
messaging pick it up automatically. But only add it if the checker can
sometimes return { done: true } — an item whose checker always returns
{ done: null } (nothing to verify, ever) will just sit there permanently
unfilled, exactly what happened with Xbox before it got pulled out. For
that kind of thing (no clean daily boolean, or genuinely unverifiable),
follow the quest-snapshot pattern below instead.
For a quest-list-style addition (rotating items, no clean daily
done/not-done) rather than a streak item, follow the Xbox pattern instead:
report into quest_snapshots via quests.ts (or compute inline if it can
run in Val Town) and add it to buildQuestMessage in lib/runCheck.ts.
morning.ts/afternoon.ts/evening.ts— the three cron entrypoints.quests.ts— HTTP endpoint for external scripts (currently just the Xbox reader) to report a quest snapshot intoquest_snapshots.lib/runCheck.ts— orchestration: runs checkers, updates streaks, builds and sends the Discord messages (checklist + quest snapshot).lib/checkers/— one file per verified streak item.lib/db.ts— SQLite-backed state:items/daily_checks(streaks) andquest_snapshots(live quest lists).lib/discord.ts— webhook sender.lib/config.ts— the list of tracked streak items + theCheckOutcometype.C:\dev\xbox-quest-check(local, not in this val) — the Xbox UI Automation reader + its own Scheduled Task and README.
Use run_file on morning.ts (or afternoon.ts/evening.ts) to trigger a
check manually instead of waiting for the schedule, then check get_logs
for any checker warnings. To test the Xbox side, run
C:\dev\xbox-quest-check\check.ps1 directly and check its check.log.