'use strict'; const config = require('config'); const curateUser = require('../curate/user'); const logger = require('../logger')(__filename); // reddit tells deleted and suspended accounts apart (verified live, 2026-11): // - deleted: /user/x/about.json returns a JSON 404 (a shadowbanned account reportedly looks // the same, so a 404 isn't necessarily permanent - see accountStatus.js's grace period) // - suspended: about.json returns 200 with only { name, is_suspended: true, ... } - no date of // suspension is exposed anywhere // Anything else (network errors, 5xx, or the HTML bot-challenge page, which carries no // `status` - see reddit/client.js's requestJson()) is 'error', which must never be treated as // dead: a blocked or expired session would otherwise get the entire users file cleaned out. async function fetchUser(username, reddit) { try { const user = await reddit.getUser(username); if (user.is_suspended) { return { status: 'suspended', user: null }; } return { status: 'active', user }; } catch (error) { if (error.status === 404) { return { status: 'deleted', user: null }; } logger.error(`Failed to fetch reddit user '${username}': ${error.message} (https://reddit.com/user/${username})`); return { status: 'error', user: null, error }; } } async function getSocialLinks(username, reddit) { try { return await reddit.getSocialLinks(username); } catch (error) { // supplementary, not worth failing the whole user fetch over - fall back to none logger.warn(`Failed to fetch social links for '${username}': ${error.message} (https://reddit.com/user/${username})`); return []; } } // social links are only ever used for the profile description file (save/profileDetails.js), // which is skipped once it's in the index (unless --redownload-profile) - scraping the profile // page for them regardless costs one throttled reddit request per user per run for nothing function needsSocialLinks(indexedProfile, args) { return Boolean(config.library.profile.description && (args.redownloadProfile || !indexedProfile?.description)); } // the user as getIndex() needs it to resolve the index path - the full curated user (minus social // links, which depend on the index) in case a custom library.index.file pattern uses more than // the name, otherwise the same fallback as resolveUser() below function indexUser(username, fetched) { return fetched.status === 'active' ? curateUser(fetched.user) : { name: username, fallback: true }; } // curated user, or the same minimal fallback as before for an account that couldn't be fetched // (or is suspended/deleted, when the caller still wants to proceed - e.g. a --post by them) async function resolveUser(username, fetched, indexedProfile, reddit, args) { if (fetched.status !== 'active') { return { name: username, fallback: true }; } const socialLinks = fetched.user.subreddit && needsSocialLinks(indexedProfile, args) ? await getSocialLinks(username, reddit) : []; return curateUser(fetched.user, socialLinks); } module.exports = { fetchUser, indexUser, resolveUser, };