Track suspended/deleted accounts and prune them from the users file after a grace period, also as standalone --cleanup. Only fetch social links when the profile description is saved, raised reddit throttle to 15/min.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
'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,
|
||||
};
|
||||
Reference in New Issue
Block a user