Add account browsing simulation

This commit is contained in:
jjbrunton committed 2026-08-11 21:40:31 +01:00
1 parent e2d7dc4b82
commit 27012399ec
31 files changed
+1476 -28

No files matched your search

+69
View File
@@ -125,6 +125,23 @@ export interface RedditPost {
pinned?: boolean;
distinguished?: string | null;
removed_by_category?: string | null;
score?: number;
}
/** Typed Reddit content used only by the simulation-only browsing reader. */
export interface BrowsingThing {
fullname: string;
kind: 'post' | 'comment';
subreddit: string;
author: string;
permalink: string;
createdAt: number;
score: number | null;
text: string;
nsfw: boolean;
parentPostId: string;
distinguished: string | null;
removed: boolean;
}
interface RedditListingChild {
@@ -478,6 +495,58 @@ export class RedditApiClient {
}
}
/**
* Typed top-level comments for browsing simulation. This is intentionally
* separate from `getThreadTopComments()`, whose string-only contract belongs
* to comment generation.
*/
async getBrowsingComments(
subreddit: string,
postId: string,
limit = 25,
): Promise<BrowsingThing[]> {
const cookieHeader = await this.getCookieHeader();
const headers: Record<string, string> = {};
if (cookieHeader) headers.Cookie = cookieHeader;
try {
const response = await this.scheduleRequest(() => this.client.get(
`/r/${subreddit}/comments/${postId}.json`,
{ params: { limit, depth: 1, sort: 'hot', raw_json: 1 }, headers },
));
const listings = Array.isArray(response.data) ? response.data : [];
const children = (listings[1] as { data?: { children?: unknown } } | undefined)?.data?.children;
if (!Array.isArray(children)) return [];
return children.flatMap((child): BrowsingThing[] => {
const data = (child as { data?: Record<string, unknown> })?.data;
if (!data || typeof data.name !== 'string' || !data.name.startsWith('t1_')) return [];
const author = typeof data.author === 'string' ? data.author : '[deleted]';
const body = typeof data.body === 'string' ? data.body : '';
return [{
fullname: data.name,
kind: 'comment',
subreddit: typeof data.subreddit === 'string' ? data.subreddit : subreddit,
author,
permalink: typeof data.permalink === 'string'
? data.permalink
: `/r/${subreddit}/comments/${postId}/_/${data.name.slice(3)}/`,
createdAt: typeof data.created_utc === 'number' ? Math.round(data.created_utc * 1_000) : 0,
score: typeof data.score === 'number' ? data.score : null,
text: body,
nsfw: data.over_18 === true,
parentPostId: postId,
distinguished: typeof data.distinguished === 'string' ? data.distinguished : null,
removed: body === '[removed]' || body === '[deleted]' || data.removed === true,
}];
}).slice(0, limit);
} catch (error) {
const rateLimitError = this.toRateLimitError(error);
if (rateLimitError) throw rateLimitError;
this.logger.warn(`Failed to read browsing comments for ${postId}:`, error);
return [];
}
}
private isValidPost(post: RedditPost, type: 'image' | 'chat' = 'image'): boolean {
// Skip deleted/removed authors
if (post.author === '[deleted]' || post.author === 'AutoModerator') return false;