Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
3.2 KiB
JavaScript
80 lines
3.2 KiB
JavaScript
'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,
|
|
};
|