diff --git a/CLAUDE.md b/CLAUDE.md index 50a9b31..746ba24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,7 @@ The pipeline runs from `src/app.js`: ### Reddit client (`src/reddit/client.js`), the core of this branch - Calls reddit's public `.json` endpoints on `www.reddit.com` (`/user/x/about.json`, `/user/x/submitted.json`, `/comments/id.json`, `/api/morechildren.json`) with `raw_json=1`. There is no OAuth. -- Anonymous requests run into reddit's bot challenge, so a logged-in session's cookies (`config.reddit.cookies`, a cookie-jar string, or `config.reddit.cookie`, a raw header) are sent. If reddit returns HTML instead of JSON, the cookies have most likely expired. The client warns when the soonest cookie expires within 48h. +- Anonymous requests run into reddit's bot challenge, so a logged-in session's cookies (`config.reddit.cookies`, a cookie-jar string, or `config.reddit.cookie`, a raw header) are sent. Live testing (2026-11) showed that only `reddit_session` (the login, valid ~6 months) is required; without it, reddit returns a 403 HTML page. The client warns when `reddit_session` is missing or expires within 48h. - Social links have no public JSON endpoint (the widgets API needs OAuth). They are scraped from the profile page HTML (`data-faceplate-tracking-context` attributes), which is fragile and breaks whenever reddit changes its markup. - All reddit requests go through **one Bottleneck limiter**: `maxConcurrent: 1`, reservoir 6/min, `minTime` 1500ms (`config.reddit.throttle`). This is deliberately slow, because being flagged as a bot defeats the purpose. Retries for 429/503 and network errors happen **outside** `limiter.schedule()`. Retrying inside it previously caused a deadlock; keep it that way. - Avoid unnecessary reddit calls: gallery posts reuse `media_metadata`/`gallery_data` from the submission listing (`curate/posts.js` → `methods/redditAlbum.js`). `getSubmission` (post only, cheap) is separate from `getSubmissionWithComments` (full comment tree including `more` stubs, which is expensive and only used for self posts). diff --git a/src/reddit/client.js b/src/reddit/client.js index 335aa51..c19916c 100644 --- a/src/reddit/client.js +++ b/src/reddit/client.js @@ -38,17 +38,29 @@ function parseCookieJar(raw) { .join('; '); } -function soonestExpiry(raw) { - const expiries = raw +// per live testing (2026-11), reddit_session (the login itself, valid for ~6 months) is the +// only cookie needed to get past the bot challenge - everything else in a browser export +// (token_v2 ~24h, seeker_session a few hours, ...) is irrelevant here, and warning about those +// expiring just cries wolf. Only fall back to the soonest-expiring cookie if there's no +// reddit_session in the jar at all. +function loginExpiry(raw) { + const cookies = raw .split('\n') .map((line) => line.trim()) .filter((line) => line && !line.startsWith('#')) .map((line) => line.split('\t')) .filter((fields) => fields.length === 7) - .map(([, , , , expiry]) => Number(expiry)) - .filter((expiry) => expiry > 0); + .map(([, , , , expiry, name]) => ({ name, expiry: Number(expiry) })); - return expiries.length ? Math.min(...expiries) : null; + const session = cookies.find(({ name }) => name === 'reddit_session'); + + if (session) { + return { name: session.name, expiry: session.expiry > 0 ? session.expiry : null }; + } + + const expiring = cookies.filter(({ expiry }) => expiry > 0).sort((a, b) => a.expiry - b.expiry); + + return expiring[0] || { name: null, expiry: null }; } let cachedCookieHeader; @@ -64,11 +76,15 @@ function cookieHeader() { : 'No reddit cookies configured, requesting unauthenticated'); if (config.reddit.cookies) { - const expiry = soonestExpiry(config.reddit.cookies); + const { name, expiry } = loginExpiry(config.reddit.cookies); const hoursLeft = expiry ? (expiry - Date.now() / 1000) / 3600 : null; + if (name !== 'reddit_session') { + logger.warn('No reddit_session cookie in config.reddit.cookies - reddit will likely block requests'); + } + if (hoursLeft !== null && hoursLeft < 48) { - logger.warn(`Soonest-expiring reddit cookie expires in ${hoursLeft.toFixed(1)}h (${new Date(expiry * 1000).toISOString()}) - refresh config/cookies.js before then`); + logger.warn(`Reddit cookie '${name}' expires in ${hoursLeft.toFixed(1)}h (${new Date(expiry * 1000).toISOString()}) - refresh config/cookies.js before then`); } } }