reddit-mcp-server 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/client/errors.ts","../src/client/response-cache.ts","../src/client/reddit-client.ts","../src/utils/formatters.ts","../src/index.ts"],"sourcesContent":["import { Option } from \"functype\"\n\n/**\n * Typed error channel for the Reddit client.\n *\n * The client's methods are the imperative-to-functional boundary: each captures throws\n * (network, HTTP, JSON parsing, validation, deliberate domain errors) inside a `Try` and\n * converts to a typed `Either<RedditError, T>`. Modelling the error as a discriminated ADT\n * — rather than a bare `Error` — makes the failure contract explicit at the type level, so\n * callers can reason about (and branch on) what actually went wrong without string-matching.\n *\n * `classifyRedditError` is TOTAL and never re-throws — every captured `Error` maps to a\n * variant — which is what makes the migration behavior-preserving: an error that was a\n * graceful `Left` before is still a graceful `Left` after, just with a richer type.\n *\n * Two-tier behavior, to preserve the exact messages of the original try/catch code:\n * - A *deliberate* typed throw (HttpError, NotFoundError, …) already carries its final\n * message, so it passes through unchanged.\n * - An *unexpected* generic error (fetch/JSON/orThrow) is wrapped as UnknownError, with the\n * optional `context` prefix — present for read methods (which prefixed in their catch),\n * absent for write methods (which returned the raw message).\n */\n\nabstract class RedditErrorBase extends Error {}\n\n/** A non-ok HTTP response from the Reddit API. Carries the status for caller branching. */\nexport class HttpError extends RedditErrorBase {\n readonly _tag = \"HttpError\" as const\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message)\n this.name = \"HttpError\"\n }\n}\n\n/** A write operation was attempted without the required user credentials / in a wrong mode. */\nexport class NotAuthenticatedError extends RedditErrorBase {\n readonly _tag = \"NotAuthenticatedError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"NotAuthenticatedError\"\n }\n}\n\n/** Reddit accepted the request but returned errors in its JSON envelope (or an unusable body). */\nexport class ApiError extends RedditErrorBase {\n readonly _tag = \"ApiError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"ApiError\"\n }\n}\n\n/** The requested post/entity does not exist or is not accessible. */\nexport class NotFoundError extends RedditErrorBase {\n readonly _tag = \"NotFoundError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"NotFoundError\"\n }\n}\n\n/** Client-side input or safety-policy rejection (invalid sort, duplicate-content guard). */\nexport class ValidationError extends RedditErrorBase {\n readonly _tag = \"ValidationError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"ValidationError\"\n }\n}\n\n/** Any failure that is not a recognized category: network, JSON parsing, unexpected throws. */\nexport class UnknownError extends RedditErrorBase {\n readonly _tag = \"UnknownError\" as const\n constructor(\n message: string,\n readonly cause?: unknown,\n ) {\n super(message)\n this.name = \"UnknownError\"\n }\n}\n\nexport type RedditError = HttpError | NotAuthenticatedError | ApiError | NotFoundError | ValidationError | UnknownError\n\nexport function isRedditError(error: unknown): error is RedditError {\n return error instanceof RedditErrorBase\n}\n\n/**\n * Total classifier from a captured `Error` to a `RedditError`. Deliberate typed throws pass\n * through unchanged; everything else becomes an `UnknownError`, prefixed with `context` when\n * provided so the observable message text matches the previous try/catch-based wrapping.\n */\nexport function classifyRedditError(error: Error, context?: string): RedditError {\n if (isRedditError(error)) {\n return error\n }\n const message = Option(context).fold(\n () => error.message,\n (ctx) => `${ctx}: ${error.message}`,\n )\n return new UnknownError(message, error)\n}\n","/* eslint-disable functype/prefer-functype-map, functype/prefer-option, functype/no-imperative-loops --\n * This module is a deliberately imperative performance primitive: a mutable, byte-bounded\n * LRU cache. A functype immutable Map cannot express LRU access-order reordering or running\n * byte accounting without rebuilding the whole structure on every operation, and the eviction\n * loop and undefined \"miss\" sentinel are the clearest expression of that stateful contract.\n * Mirrors the imperative-boundary convention used in reddit-client.ts.\n */\n\n/**\n * In-memory cache for read-only Reddit GET responses.\n *\n * Reddit's rate limits are tight (~10 req/min anonymous, 60-100 authenticated),\n * so caching identical reads for a short window meaningfully reduces request\n * pressure. TTLs are adaptive: volatile listings expire quickly while relatively\n * stable resources (top/controversial, search, user/subreddit \"about\") live longer.\n *\n * Eviction is LRU bounded by a byte budget, so the cache can never grow unbounded.\n */\n\ntype CacheEntry = {\n readonly body: string\n readonly status: number\n readonly expiresAt: number\n readonly bytes: number\n}\n\nexport type CachedResponse = {\n readonly body: string\n readonly status: number\n}\n\nconst SECOND = 1_000\n\nexport class ResponseCache {\n private readonly maxBytes: number\n private readonly now: () => number\n // Map iteration order is insertion order, which we use as the LRU ordering:\n // the first key is the least-recently-used entry.\n private readonly entries = new Map<string, CacheEntry>()\n private currentBytes = 0\n\n constructor(options: { readonly maxBytes: number; readonly now?: () => number }) {\n this.maxBytes = options.maxBytes\n this.now = options.now ?? Date.now\n }\n\n /** Adaptive TTL (in milliseconds) for a given request URL. */\n ttlFor(url: string): number {\n if (/\\/(hot|new|rising)\\.json/.test(url)) {\n return 60 * SECOND\n }\n if (/\\/(top|controversial)\\.json/.test(url) || /\\/search\\.json/.test(url) || /\\/about\\.json/.test(url)) {\n return 300 * SECOND\n }\n if (/\\/comments\\//.test(url)) {\n return 60 * SECOND\n }\n return 120 * SECOND\n }\n\n get(url: string): CachedResponse | undefined {\n const entry = this.entries.get(url)\n if (entry === undefined) {\n return undefined\n }\n if (this.now() >= entry.expiresAt) {\n this.entries.delete(url)\n this.currentBytes -= entry.bytes\n return undefined\n }\n // Mark as most-recently-used by reinserting at the end.\n this.entries.delete(url)\n this.entries.set(url, entry)\n return { body: entry.body, status: entry.status }\n }\n\n set(url: string, body: string, status: number): void {\n const bytes = Buffer.byteLength(body, \"utf8\")\n // A single oversized body is simply not cached.\n if (bytes > this.maxBytes) {\n return\n }\n\n const existing = this.entries.get(url)\n if (existing !== undefined) {\n this.entries.delete(url)\n this.currentBytes -= existing.bytes\n }\n\n this.entries.set(url, {\n body,\n status,\n expiresAt: this.now() + this.ttlFor(url),\n bytes,\n })\n this.currentBytes += bytes\n\n this.evictUntilWithinBudget()\n }\n\n private evictUntilWithinBudget(): void {\n while (this.currentBytes > this.maxBytes) {\n const oldestKey = this.entries.keys().next().value\n if (oldestKey === undefined) {\n return\n }\n const oldest = this.entries.get(oldestKey)\n this.entries.delete(oldestKey)\n if (oldest !== undefined) {\n this.currentBytes -= oldest.bytes\n }\n }\n }\n}\n","/* eslint-disable functype/prefer-either --\n * This module is the imperative-to-functional boundary for the Reddit HTTP client.\n * Each public method runs its failure-producing region inside a `Try` and converts the\n * result to `Either<RedditError, T>` via the total `classifyRedditError`. Because the body\n * of `Try.async(() => Promise<T>)` can only signal failure by throwing, the `throw`s here\n * (HTTP/validation/domain errors, and the validateWriteAccess/checkDuplicateContent helpers\n * they call) are local control-flow captured by that `Try` — they never escape the method\n * boundary. prefer-either's \"return Either.left\" suggestion does not apply inside a Try body.\n */\nimport crypto from \"crypto\"\nimport type { Either } from \"functype\"\nimport { Left, Option, Right, Try } from \"functype\"\n\nimport type {\n BotDisclosureConfig,\n ContentRecord,\n Page,\n RedditApiCommentResponse,\n RedditApiCommentTreeData,\n RedditApiEditResponse,\n RedditApiInfoResponse,\n RedditApiLinkFlairResponse,\n RedditApiListingResponse,\n RedditApiMeResponse,\n RedditApiMoreChildrenResponse,\n RedditApiPopularSubredditsResponse,\n RedditApiPostCommentsResponse,\n RedditApiPostData,\n RedditApiRulesResponse,\n RedditApiSubmitResponse,\n RedditApiSubredditResponse,\n RedditApiUserResponse,\n RedditAuthMode,\n RedditClientConfig,\n RedditComment,\n RedditFlair,\n RedditPost,\n RedditRule,\n RedditSubreddit,\n RedditUser,\n RetryConfig,\n SafeModeConfig,\n UserContent,\n} from \"../types\"\nimport type { RedditError } from \"./errors\"\nimport {\n ApiError,\n classifyRedditError,\n HttpError,\n isRedditError,\n NotAuthenticatedError,\n NotFoundError,\n ValidationError,\n} from \"./errors\"\nimport { ResponseCache } from \"./response-cache\"\n\n// Extract Reddit's pagination cursors from a listing's `data`. Reddit returns `after`/`before`\n// as a fullname string or null; we surface only present string cursors (no undefined keys, so\n// this is safe under exactOptionalPropertyTypes).\nfunction listingCursor(data: { readonly [key: string]: unknown }): Pick<Page<unknown>, \"after\" | \"before\"> {\n const after = typeof data.after === \"string\" ? { after: data.after } : {}\n const before = typeof data.before === \"string\" ? { before: data.before } : {}\n return { ...after, ...before }\n}\n\nfunction parsePostData(post: RedditApiPostData): RedditPost {\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: Boolean(post.edited),\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n}\n\nexport class RedditClient {\n private readonly clientId: string\n private readonly clientSecret: string\n private readonly userAgent: string\n private readonly username?: string\n private readonly password?: string\n private readonly baseUrl: string\n private readonly authMode: RedditAuthMode\n private readonly hasCredentials: boolean\n private readonly safeMode: SafeModeConfig\n private readonly botDisclosure: BotDisclosureConfig\n private readonly cache?: ResponseCache\n private readonly retry: RetryConfig\n\n // Mutable state — inherent to a stateful HTTP client with token refresh\n\n private accessToken?: string\n\n private tokenExpiry: number = 0\n\n private authenticated: boolean = false\n\n private lastWriteTime: number = 0\n\n private recentContentRecords: ContentRecord[] = []\n\n constructor(config: RedditClientConfig) {\n this.clientId = config.clientId\n this.clientSecret = config.clientSecret\n this.userAgent = config.userAgent\n this.username = config.username\n this.password = config.password\n this.authMode = config.authMode ?? \"auto\"\n this.hasCredentials = Boolean(this.clientId && this.clientSecret)\n this.baseUrl = this.determineBaseUrl()\n\n this.safeMode = config.safeMode ?? {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n\n this.botDisclosure = config.botDisclosure ?? { enabled: false, footer: \"\" }\n\n this.cache = config.cache?.enabled === true ? new ResponseCache({ maxBytes: config.cache.maxBytes }) : undefined\n\n this.retry = config.retry ?? { maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 60000 }\n }\n\n private determineBaseUrl(): string {\n switch (this.authMode) {\n case \"authenticated\":\n return \"https://oauth.reddit.com\"\n case \"anonymous\":\n return \"https://www.reddit.com\"\n case \"auto\":\n return this.hasCredentials ? \"https://oauth.reddit.com\" : \"https://www.reddit.com\"\n }\n }\n\n // Low-level HTTP boundary. Returns Either<Error, Response>: a Right even for non-ok HTTP\n // statuses (callers inspect response.ok); only thrown failures (network, auth) become Left.\n private async makeRequest(path: string, options: RequestInit = {}): Promise<Either<Error, Response>> {\n const attempt = await Try.async(async (): Promise<Response> => {\n const url = `${this.baseUrl}${path}`\n const method = (options.method ?? \"GET\").toUpperCase()\n const cacheable = this.cache !== undefined && method === \"GET\"\n\n if (cacheable) {\n const cached = this.cache!.get(url)\n if (cached !== undefined) {\n return new Response(cached.body, { status: cached.status })\n }\n }\n\n const requiresAuth = this.authMode === \"authenticated\" || (this.authMode === \"auto\" && this.hasCredentials)\n\n if (requiresAuth && (Date.now() >= this.tokenExpiry || !this.authenticated)) {\n const authResult = await this.authenticate()\n authResult.orThrow()\n }\n\n const headers: Record<string, string> = {\n \"User-Agent\": this.userAgent,\n\n ...(options.headers as Record<string, string> | undefined),\n }\n\n if (requiresAuth && this.accessToken !== undefined) {\n headers[\"Authorization\"] = `Bearer ${this.accessToken}`\n }\n\n const first = await this.fetchWithRetry(url, options, headers, path, 0)\n\n // 401 once-off re-auth, then retry the request (which itself honors 429 backoff).\n const response =\n first.status === 401 && this.authenticated\n ? await this.fetchWithRetry(url, options, { ...headers, Authorization: await this.reauthorize() }, path, 0)\n : first\n\n // Cache successful read responses and return a fresh, readable Response.\n // (A fetch Response body can only be consumed once, so we re-wrap the text.)\n if (cacheable && response.ok) {\n const text = await response.text()\n this.cache!.set(url, text, response.status)\n return new Response(text, { status: response.status })\n }\n\n return response\n })\n\n return attempt.toEither((error) => error)\n }\n\n async authenticate(): Promise<Either<Error, void>> {\n if (this.authMode === \"anonymous\") {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n if (this.authMode === \"authenticated\" && !this.hasCredentials) {\n return Left(new Error(\"Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\"))\n }\n\n if (this.authMode === \"auto\" && !this.hasCredentials) {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n const attempt = await Try.async(async (): Promise<void> => {\n const now = Date.now()\n if (this.accessToken !== undefined && now < this.tokenExpiry) {\n return\n }\n\n const authUrl = \"https://www.reddit.com/api/v1/access_token\"\n const authData = new URLSearchParams()\n\n const { username } = this\n const { password } = this\n const isUserAuth = Boolean(username && password)\n if (isUserAuth && username !== undefined && password !== undefined) {\n authData.append(\"grant_type\", \"password\")\n authData.append(\"username\", username)\n authData.append(\"password\", password)\n } else {\n authData.append(\"grant_type\", \"client_credentials\")\n }\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString(\"base64\")\n const response = await fetch(authUrl, {\n method: \"POST\",\n headers: {\n \"User-Agent\": this.userAgent,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Authorization: `Basic ${credentials}`,\n },\n body: authData.toString(),\n })\n\n if (!response.ok) {\n const statusText = response.statusText !== \"\" ? response.statusText : \"Unknown Error\"\n throw new Error(`Authentication failed: ${response.status} ${statusText}`)\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number }\n this.accessToken = data.access_token\n this.tokenExpiry = now + data.expires_in * 1000\n this.authenticated = true\n })\n\n return attempt.toEither((error) => error)\n }\n\n async checkAuthentication(): Promise<boolean> {\n if (!this.authenticated) {\n const result = await this.authenticate()\n return result.isRight()\n }\n return true\n }\n\n private validateWriteAccess(): void {\n if (this.username === undefined || this.password === undefined) {\n if (this.authMode === \"anonymous\") {\n throw new NotAuthenticatedError(\n \"Write operations not available in anonymous mode. \" +\n \"Set REDDIT_USERNAME, REDDIT_PASSWORD and use 'auto' or 'authenticated' mode.\",\n )\n }\n throw new NotAuthenticatedError(\"Write operations require REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n }\n\n private async enforceWriteRateLimit(): Promise<void> {\n if (!this.safeMode.enabled || this.safeMode.writeDelayMs <= 0) {\n return\n }\n\n const now = Date.now()\n const elapsed = now - this.lastWriteTime\n if (elapsed < this.safeMode.writeDelayMs) {\n const waitTime = this.safeMode.writeDelayMs - elapsed\n console.error(`[SafeMode] Rate limit: waiting ${waitTime}ms before write operation`)\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n }\n this.lastWriteTime = Date.now()\n }\n\n private hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content.trim().toLowerCase()).digest(\"hex\")\n }\n\n private checkDuplicateContent(content: string, subreddit?: string): void {\n if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) {\n return\n }\n\n const hash = this.hashContent(content)\n\n const duplicate = this.recentContentRecords.find((record) => record.hash === hash)\n if (duplicate !== undefined) {\n if (subreddit !== undefined && duplicate.subreddit !== \"\" && subreddit !== duplicate.subreddit) {\n throw new ValidationError(\n \"Cross-subreddit duplicate detected. Reddit's Responsible Builder Policy prohibits \" +\n \"posting identical or substantially similar content across multiple subreddits. \" +\n \"Please create unique content for each subreddit.\",\n )\n }\n throw new ValidationError(\n \"Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. \" +\n \"Please modify your content and try again.\",\n )\n }\n\n this.recentContentRecords.push({\n hash,\n subreddit: subreddit ?? \"\",\n timestamp: Date.now(),\n })\n\n this.recentContentRecords = this.recentContentRecords.slice(-this.safeMode.maxRecentHashes)\n }\n\n // Re-authenticate and return a fresh Bearer header value (throws via orThrow on failure).\n private async reauthorize(): Promise<string> {\n const result = await this.authenticate()\n result.orThrow()\n return `Bearer ${this.accessToken}`\n }\n\n // Fetch with transparent retry on HTTP 429. Honors Retry-After / x-ratelimit-reset, else\n // exponential backoff; surfaces the 429 once retries are exhausted or the required wait\n // exceeds the cap. Recursive (not a loop) to satisfy the functional style.\n private async fetchWithRetry(\n url: string,\n options: RequestInit,\n headers: Record<string, string>,\n path: string,\n attempt: number,\n ): Promise<Response> {\n const response = await fetch(url, { ...options, headers })\n if (response.status !== 429 || attempt >= this.retry.maxRetries) {\n return response\n }\n\n const wait = this.retryAfterMs(response).fold(\n () => Math.min(this.retry.baseDelayMs * 2 ** attempt, this.retry.maxDelayMs),\n (ms) => ms,\n )\n if (wait > this.retry.maxDelayMs) {\n return response\n }\n\n console.error(`[RateLimit] 429 from ${path} — retry ${attempt + 1}/${this.retry.maxRetries} in ${wait}ms`)\n await new Promise((resolve) => setTimeout(resolve, wait))\n return this.fetchWithRetry(url, options, headers, path, attempt + 1)\n }\n\n // Parse a retry delay (ms) from a 429 response: prefer Retry-After (delta-seconds or\n // HTTP-date), then x-ratelimit-reset (seconds). None when no usable header is present,\n // signalling the caller to fall back to exponential backoff.\n private retryAfterMs(response: Response): Option<number> {\n const { headers } = response\n\n const retryAfter = headers.get(\"retry-after\")\n if (retryAfter !== null && retryAfter !== \"\") {\n const seconds = Number(retryAfter)\n if (!Number.isNaN(seconds)) {\n return Option(seconds * 1000)\n }\n const when = Date.parse(retryAfter)\n if (!Number.isNaN(when)) {\n return Option(Math.max(0, when - Date.now()))\n }\n }\n\n const reset = headers.get(\"x-ratelimit-reset\")\n if (reset !== null && reset !== \"\") {\n const seconds = Number(reset)\n if (!Number.isNaN(seconds)) {\n return Option(seconds * 1000)\n }\n }\n\n return Option.none()\n }\n\n private appendBotDisclosure(content: string): string {\n if (!this.botDisclosure.enabled || this.botDisclosure.footer === \"\") {\n return content\n }\n return `${content}${this.botDisclosure.footer}`\n }\n\n async getUser(username: string): Promise<Either<RedditError, RedditUser>> {\n const context = `Failed to get user info for ${username}`\n const attempt = await Try.async(async (): Promise<RedditUser> => {\n const response = (await this.makeRequest(`/user/${username}/about.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiUserResponse\n const { data } = json\n\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma ?? data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n // Fetch + split a mixed user listing (saved/overview) into posts (t3) and comments (t1).\n private async getUserContent(path: string, context: string): Promise<Either<RedditError, UserContent>> {\n const attempt = await Try.async(async (): Promise<UserContent> => {\n const response = (await this.makeRequest(path)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData | RedditApiCommentTreeData>\n const posts = json.data.children\n .filter((child) => child.kind === \"t3\")\n .map((child) => parsePostData(child.data as RedditApiPostData))\n const comments = json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data as RedditApiCommentTreeData\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n parentId: comment.parent_id,\n }\n })\n return { posts, comments, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getMyOverview(\n options: { readonly limit?: number; readonly after?: string } = {},\n ): Promise<Either<RedditError, UserContent>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching your overview requires REDDIT_USERNAME\"))\n }\n const { limit = 25, after } = options\n const params = new URLSearchParams({ limit: limit.toString() })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n return this.getUserContent(`/user/${this.username}/overview.json?${params}`, \"Failed to get your overview\")\n }\n\n async getMySaved(\n options: { readonly limit?: number; readonly after?: string } = {},\n ): Promise<Either<RedditError, UserContent>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching saved content requires REDDIT_USERNAME\"))\n }\n const { limit = 25, after } = options\n const params = new URLSearchParams({ limit: limit.toString() })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n return this.getUserContent(`/user/${this.username}/saved.json?${params}`, \"Failed to get saved content\")\n }\n\n // The authenticated user's own account (requires user credentials — /api/v1/me needs identity).\n async getMe(): Promise<Either<RedditError, RedditUser>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching your account requires REDDIT_USERNAME\"))\n }\n const context = \"Failed to get authenticated user info\"\n const attempt = await Try.async(async (): Promise<RedditUser> => {\n const response = (await this.makeRequest(\"/api/v1/me\")).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const data = (await response.json()) as RedditApiMeResponse\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma ?? data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getSubredditInfo(subredditName: string): Promise<Either<RedditError, RedditSubreddit>> {\n const context = `Failed to get subreddit info for ${subredditName}`\n const attempt = await Try.async(async (): Promise<RedditSubreddit> => {\n const response = (await this.makeRequest(`/r/${subredditName}/about.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubredditResponse\n const { data } = json\n\n return {\n displayName: data.display_name,\n title: data.title,\n description: data.description,\n publicDescription: data.public_description,\n subscribers: data.subscribers,\n activeUserCount: data.active_user_count ?? undefined,\n createdUtc: data.created_utc,\n over18: data.over18,\n subredditType: data.subreddit_type,\n url: data.url,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getSubredditRules(subreddit: string): Promise<Either<RedditError, readonly RedditRule[]>> {\n const context = `Failed to get rules for r/${subreddit}`\n const attempt = await Try.async(async (): Promise<readonly RedditRule[]> => {\n const response = (await this.makeRequest(`/r/${subreddit}/about/rules.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiRulesResponse\n return json.rules.map((rule) => ({\n shortName: rule.short_name,\n description: rule.description,\n kind: rule.kind,\n violationReason: rule.violation_reason,\n priority: rule.priority,\n createdUtc: rule.created_utc,\n }))\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPostFlairs(subreddit: string): Promise<Either<RedditError, readonly RedditFlair[]>> {\n const context = `Failed to get post flairs for r/${subreddit}`\n const attempt = await Try.async(async (): Promise<readonly RedditFlair[]> => {\n const response = (await this.makeRequest(`/r/${subreddit}/api/link_flair_v2.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiLinkFlairResponse\n return json.map((flair) => ({\n id: flair.id,\n text: flair.text,\n type: flair.type,\n textEditable: flair.text_editable,\n }))\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getTopPosts(\n subreddit: string,\n timeFilter: string = \"week\",\n limit: number = 10,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const endpoint = subreddit !== \"\" ? `/r/${subreddit}/top.json` : \"/top.json\"\n const params = new URLSearchParams({\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get top posts for ${subreddit !== \"\" ? subreddit : \"home\"}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to get top posts: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n const items = json.data.children.map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async browseSubreddit(\n subreddit: string,\n sort: string = \"hot\",\n timeFilter: string = \"week\",\n limit: number = 10,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const validSorts = [\"hot\", \"new\", \"top\", \"rising\", \"controversial\"]\n if (!validSorts.includes(sort)) {\n return Left(new ValidationError(`Invalid sort \"${sort}\". Valid options are: ${validSorts.join(\", \")}`))\n }\n\n const endpoint = subreddit !== \"\" ? `/r/${subreddit}/${sort}.json` : `/${sort}.json`\n const params = new URLSearchParams({ limit: limit.toString() })\n // The time filter only applies to top/controversial listings.\n if (sort === \"top\" || sort === \"controversial\") {\n params.set(\"t\", timeFilter)\n }\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const home = subreddit !== \"\" ? subreddit : \"home\"\n const context = `Failed to browse r/${home} (${sort})`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to browse r/${home}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n const items = json.data.children.map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPost(postId: string, subreddit?: string): Promise<Either<RedditError, RedditPost>> {\n const endpoint = Option(subreddit).fold(\n () => `/api/info.json?id=t3_${postId}`,\n (sr) => `/r/${sr}/comments/${postId}.json`,\n )\n const context = `Failed to get post with ID ${postId}`\n\n const attempt = await Try.async(async (): Promise<RedditPost> => {\n const response = (await this.makeRequest(endpoint)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n if (subreddit !== undefined) {\n const json = (await response.json()) as [RedditApiListingResponse<RedditApiPostData>, unknown]\n return parsePostData(json[0].data.children[0].data)\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n if (json.data.children.length === 0) {\n throw new NotFoundError(`Post with ID ${postId} not found`)\n }\n return parsePostData(json.data.children[0].data)\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getTrendingSubreddits(limit: number = 5): Promise<Either<RedditError, readonly string[]>> {\n const params = new URLSearchParams({ limit: limit.toString() })\n const context = `Failed to get trending subreddits`\n\n const attempt = await Try.async(async (): Promise<readonly string[]> => {\n const response = (await this.makeRequest(`/subreddits/popular.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPopularSubredditsResponse\n return json.data.children.map((child) => child.data.display_name)\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async createPost(\n subreddit: string,\n title: string,\n content: string,\n isSelf: boolean = true,\n flairId?: string,\n flairText?: string,\n ): Promise<Either<RedditError, RedditPost>> {\n const attempt = await Try.async(async (): Promise<RedditPost> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(title + content, subreddit)\n\n const finalContent = isSelf ? this.appendBotDisclosure(content) : content\n const kind = isSelf ? \"self\" : \"link\"\n const params = new URLSearchParams()\n params.append(\"sr\", subreddit)\n params.append(\"kind\", kind)\n params.append(\"title\", title)\n params.append(isSelf ? \"text\" : \"url\", finalContent)\n params.append(\"api_type\", \"json\")\n if (flairId !== undefined) {\n params.append(\"flair_id\", flairId)\n }\n if (flairText !== undefined) {\n params.append(\"flair_text\", flairText)\n }\n\n const response = (\n await this.makeRequest(\"/api/submit\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to create post: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubmitResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n }\n\n const postId = json.json.data?.id ?? json.json.data?.name?.replace(\"t3_\", \"\")\n\n if (postId === undefined) {\n throw new ApiError(\"No post ID returned from Reddit\")\n }\n\n return (await this.getPost(postId, subreddit)).orThrow()\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async checkPostExists(postId: string): Promise<boolean> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n const response = (await this.makeRequest(`/api/info.json?id=t3_${postId}`)).orThrow()\n if (!response.ok) {\n return false\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n return json.data.children.length > 0\n })\n\n return attempt.orElse(false)\n }\n\n async replyToPost(postId: string, content: string): Promise<Either<RedditError, RedditComment>> {\n const attempt = await Try.async(async (): Promise<RedditComment> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(content)\n\n const finalContent = this.appendBotDisclosure(content)\n const fullThingId = postId.startsWith(\"t3_\") || postId.startsWith(\"t1_\") ? postId : `t3_${postId}`\n\n if (!postId.startsWith(\"t1_\")) {\n const exists = await this.checkPostExists(postId.replace(/^t3_/, \"\"))\n if (!exists) {\n throw new NotFoundError(`Post with ID ${postId} does not exist or is not accessible`)\n }\n }\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalContent)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/comment\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to reply: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiCommentResponse\n\n if (json.json.data?.things !== undefined && json.json.data.things.length > 0) {\n const commentData = json.json.data.things[0].data\n const author = this.username ?? \"[unknown]\"\n return {\n id: commentData.id,\n author,\n body: content,\n score: 1,\n controversiality: 0,\n subreddit: commentData.subreddit,\n submissionTitle: commentData.link_title ?? \"\",\n createdUtc: Date.now() / 1000,\n edited: false,\n isSubmitter: false,\n permalink: commentData.permalink,\n }\n } else if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n } else {\n throw new ApiError(\"Failed to parse reply response\")\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async deletePost(thingId: string): Promise<Either<RedditError, boolean>> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n this.validateWriteAccess()\n\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"id\", fullThingId)\n\n const response = (\n await this.makeRequest(\"/api/del\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`)\n console.error(`[Reddit API] Error response: ${errorText}`)\n throw new HttpError(response.status, `HTTP ${response.status}: ${errorText}`)\n }\n\n console.error(`[Reddit API] Successfully deleted ${fullThingId}`)\n return true\n })\n\n return attempt.toEither((error) => {\n if (!isRedditError(error)) {\n console.error(`[Reddit API] Delete exception:`, error)\n }\n return classifyRedditError(error)\n })\n }\n\n async deleteComment(thingId: string): Promise<Either<RedditError, boolean>> {\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.deletePost(fullThingId)\n }\n\n async editPost(thingId: string, newText: string): Promise<Either<RedditError, boolean>> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(newText)\n\n const finalText = this.appendBotDisclosure(newText)\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalText)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/editusertext\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to edit: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiEditResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n }\n\n return true\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async editComment(thingId: string, newText: string): Promise<Either<RedditError, boolean>> {\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.editPost(fullThingId, newText)\n }\n\n async searchReddit(\n query: string,\n options: {\n readonly subreddit?: string\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly type?: string\n readonly after?: string\n readonly before?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const { subreddit, sort = \"relevance\", timeFilter = \"all\", limit = 25, type = \"link\", after, before } = options\n const endpoint = Option(subreddit).fold(\n () => \"/search.json\",\n (sr) => `/r/${sr}/search.json`,\n )\n\n const params = new URLSearchParams({\n q: query,\n sort,\n t: timeFilter,\n limit: limit.toString(),\n type,\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of native string | undefined into URLSearchParams init\n ...(subreddit !== undefined ? { restrict_sr: \"true\" } : {}),\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of cursors into URLSearchParams init\n ...(after !== undefined ? { after } : {}),\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of cursors into URLSearchParams init\n ...(before !== undefined ? { before } : {}),\n })\n const context = `Failed to search Reddit for: ${query}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to search Reddit: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const items = json.data.children.filter((child) => child.kind === \"t3\").map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPostComments(\n postId: string,\n subreddit: string,\n options: {\n readonly sort?: string\n readonly limit?: number\n } = {},\n ): Promise<Either<RedditError, { readonly post: RedditPost; readonly comments: readonly RedditComment[] }>> {\n const { sort = \"best\", limit = 100 } = options\n const params = new URLSearchParams({\n sort,\n limit: limit.toString(),\n })\n const context = `Failed to get comments for post ${postId}`\n\n const attempt = await Try.async(\n async (): Promise<{ readonly post: RedditPost; readonly comments: readonly RedditComment[] }> => {\n const response = (await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to get comments: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPostCommentsResponse\n\n const postData = json[0].data.children[0].data\n const post = parsePostData(postData)\n\n const parseComments = (\n commentData: ReadonlyArray<{ readonly kind: string; readonly data: RedditApiCommentTreeData }>,\n depth: number = 0,\n ): readonly RedditComment[] =>\n commentData.flatMap((item) => {\n if (item.kind !== \"t1\" || item.data.body === undefined) return []\n\n const comment: RedditComment = {\n id: item.data.id,\n author: item.data.author,\n body: item.data.body,\n score: item.data.score,\n controversiality: item.data.controversiality,\n subreddit: item.data.subreddit,\n submissionTitle: post.title,\n createdUtc: item.data.created_utc,\n edited: Boolean(item.data.edited),\n isSubmitter: item.data.is_submitter,\n permalink: item.data.permalink,\n depth,\n parentId: item.data.parent_id,\n }\n\n const { replies } = item.data\n const childComments =\n replies !== undefined && typeof replies !== \"string\"\n ? parseComments(replies.data.children, depth + 1)\n : []\n\n return [comment, ...childComments]\n })\n\n const comments: readonly RedditComment[] = parseComments(json[1].data.children)\n\n return { post, comments }\n },\n )\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n // Expand \"load more\" comment stubs via /api/morechildren. `commentIds` are the ids from a\n // `more` node returned by getPostComments. Returns a flat list of the expanded comments.\n async getMoreComments(\n linkId: string,\n commentIds: readonly string[],\n ): Promise<Either<RedditError, readonly RedditComment[]>> {\n const fullLinkId = linkId.startsWith(\"t3_\") ? linkId : `t3_${linkId}`\n const context = `Failed to expand comments for ${fullLinkId}`\n const params = new URLSearchParams({\n api_type: \"json\",\n link_id: fullLinkId,\n children: commentIds.join(\",\"),\n })\n\n const attempt = await Try.async(async (): Promise<readonly RedditComment[]> => {\n const response = (await this.makeRequest(`/api/morechildren?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiMoreChildrenResponse\n const things = json.json.data?.things ?? []\n return things\n .filter((thing) => thing.kind === \"t1\" && thing.data.body !== undefined)\n .map((thing) => {\n const comment = thing.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n parentId: comment.parent_id,\n }\n })\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getUserPosts(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly after?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25, after } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get posts for user ${username}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`/user/${username}/submitted.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const items = json.data.children.filter((child) => child.kind === \"t3\").map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getUserComments(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly after?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditComment>>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25, after } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get comments for user ${username}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditComment>> => {\n const response = (await this.makeRequest(`/user/${username}/comments.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiCommentTreeData>\n\n const items = json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n }\n })\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n}\n\n// Create and export singleton instance\nconst clientHolder: { instance: Option<RedditClient> } = { instance: Option.none() }\n\nexport function initializeRedditClient(config: RedditClientConfig): RedditClient {\n const client = new RedditClient(config)\n\n clientHolder.instance = Option(client)\n return client\n}\n\nexport function getRedditClient(): Option<RedditClient> {\n return clientHolder.instance\n}\n","import { Option, Try } from \"functype\"\n\nimport type {\n FormattedCommentInfo,\n FormattedPostInfo,\n FormattedSubredditInfo,\n FormattedUserInfo,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n} from \"../types\"\n\nexport function formatTimestamp(timestamp: number): string {\n return Try(() => {\n const date = new Date(timestamp * 1000)\n return date\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \" UTC\")\n }).orElse(String(timestamp))\n}\n\nexport function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const insights: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Primarily a commenter, highly engaged in discussions\"]\n : karmaRatio < 0.2\n ? [\"Content creator, focuses on sharing posts\"]\n : [\"Balanced participation in both posting and commenting\"]),\n ...(accountAgeDays < 30\n ? [\"New user, still exploring Reddit\"]\n : accountAgeDays > 365 * 5\n ? [\"Long-time Redditor with extensive platform experience\"]\n : []),\n ...(isMod ? [\"Community leader who helps maintain subreddit quality\"] : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzePostEngagement(score: number, ratio: number, numComments: number): string {\n const insights: readonly string[] = [\n ...(score > 1000 && ratio > 0.95\n ? [\"Highly successful post with strong community approval\"]\n : score > 100 && ratio > 0.8\n ? [\"Well-received post with good engagement\"]\n : ratio < 0.5\n ? [\"Controversial post that sparked debate\"]\n : []),\n ...(numComments > 100\n ? [\"Generated significant discussion\"]\n : numComments > score * 0.5\n ? [\"Highly discussable content with active comment section\"]\n : numComments === 0\n ? [\"Yet to receive community interaction\"]\n : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzeSubredditHealth(subscribers: number, activeUsers: Option<number>, ageDays: number): string {\n const sizeInsights: readonly string[] =\n subscribers > 1000000\n ? [\"Major subreddit with massive following\"]\n : subscribers > 100000\n ? [\"Well-established community\"]\n : subscribers < 1000\n ? [\"Niche community, potential for growth\"]\n : []\n\n const activityInsights: readonly string[] = activeUsers\n .map((active) => active / subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1\n ? [\"Highly active community with strong engagement\"]\n : activityRatio < 0.01\n ? [\"Could benefit from more community engagement initiatives\"]\n : [],\n )\n\n const ageInsights: readonly string[] =\n ageDays > 365 * 5\n ? [\"Mature subreddit with established culture\"]\n : ageDays < 90\n ? [\"New subreddit still forming its community\"]\n : []\n\n return [...sizeInsights, ...activityInsights, ...ageInsights].join(\"\\n - \")\n}\n\nexport function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const recommendations: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Consider creating more posts to share your expertise\"]\n : karmaRatio < 0.2\n ? [\"Engage more in discussions to build community connections\"]\n : []),\n ...(accountAgeDays < 30\n ? [\"Explore popular subreddits in your areas of interest\", \"Read community guidelines before posting\"]\n : []),\n ...(isMod ? [\"Share moderation insights with other community leaders\"] : []),\n ]\n\n return recommendations.length > 0 ? recommendations.join(\"\\n - \") : \"Maintain your balanced engagement across Reddit\"\n}\n\nexport function getBestEngagementTime(createdUtc: number): string {\n const postHour = new Date(createdUtc * 1000).getHours()\n\n if (14 <= postHour && postHour <= 18) {\n return \"Posted during peak engagement hours (2 PM - 6 PM), good timing!\"\n } else if (23 <= postHour || postHour <= 5) {\n return \"Consider posting during more active hours (morning to evening)\"\n } else {\n return \"Posted during moderate activity hours, timing could be optimized\"\n }\n}\n\nexport function getSubredditEngagementTips(subreddit: RedditSubreddit): string {\n const sizeTips: readonly string[] =\n subreddit.subscribers > 1000000\n ? [\"Post during peak hours for maximum visibility\", \"Ensure content is highly polished due to high competition\"]\n : subreddit.subscribers < 1000\n ? [\"Engage actively to help grow the community\", \"Consider cross-posting to related larger subreddits\"]\n : []\n\n const activityTips: readonly string[] = Option(subreddit.activeUserCount)\n .map((active) => active / subreddit.subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1 ? [\"Quick responses recommended due to high activity\"] : ([] as readonly string[]),\n )\n\n const allTips = [...sizeTips, ...activityTips]\n return allTips.length > 0 ? allTips.join(\"\\n - \") : \"Regular engagement recommended to maintain community presence\"\n}\n\nexport function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {\n const insights: readonly string[] = [\n ...(score > 100\n ? [\"Highly upvoted comment with significant community agreement\"]\n : score < 0\n ? [\"Controversial or contested viewpoint\"]\n : []),\n ...(isEdited ? [\"Refined for clarity or accuracy\"] : []),\n ...(isOp ? [\"Author's perspective adds context to original post\"] : []),\n ]\n\n return insights.length > 0 ? insights.join(\"\\n - \") : \"Standard engagement with discussion\"\n}\n\nexport function formatUserInfo(user: RedditUser): FormattedUserInfo {\n const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600)\n const karmaRatio = user.commentKarma / (user.linkKarma === 0 ? 1 : user.linkKarma)\n\n const status: readonly string[] = [\n ...(user.isMod ? [\"Moderator\"] : []),\n ...(user.isGold ? [\"Reddit Gold Member\"] : []),\n ...(user.isEmployee ? [\"Reddit Employee\"] : []),\n ]\n\n return {\n username: user.name,\n karma: {\n commentKarma: user.commentKarma,\n postKarma: user.linkKarma,\n totalKarma: user.totalKarma,\n },\n accountStatus: status.length > 0 ? status : [\"Regular User\"],\n accountCreated: formatTimestamp(user.createdUtc),\n profileUrl: user.profileUrl,\n activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),\n recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),\n }\n}\n\nexport function formatPostInfo(post: RedditPost): FormattedPostInfo {\n const contentType = post.isSelf ? \"Text Post\" : \"Link Post\"\n const content = post.isSelf ? (post.selftext ?? \"\") : (post.url ?? \"\")\n\n const flags: readonly string[] = [\n ...(post.over18 ? [\"NSFW\"] : []),\n ...(post.spoiler === true ? [\"Spoiler\"] : []),\n ...(post.edited ? [\"Edited\"] : []),\n ]\n\n return {\n title: post.title,\n type: contentType,\n content: content.length > 300 ? `${content.substring(0, 297)}...` : content,\n author: post.author,\n subreddit: post.subreddit,\n stats: {\n score: post.score,\n upvoteRatio: post.upvoteRatio,\n comments: post.numComments,\n },\n metadata: {\n posted: formatTimestamp(post.createdUtc),\n flags,\n flair: post.linkFlairText ?? \"None\",\n },\n links: {\n fullPost: `https://reddit.com${post.permalink}`,\n shortLink: `https://redd.it/${post.id}`,\n },\n engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),\n bestTimeToEngage: getBestEngagementTime(post.createdUtc),\n }\n}\n\nexport function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {\n const flags: readonly string[] = [\n ...(subreddit.over18 ? [\"NSFW\"] : []),\n ...Option(subreddit.subredditType).fold(\n () => [] as readonly string[],\n (type) => [`Type: ${type}`] as readonly string[],\n ),\n ]\n\n const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)\n\n return {\n name: subreddit.displayName,\n title: subreddit.title,\n stats: {\n subscribers: subreddit.subscribers,\n activeUsers: Option(subreddit.activeUserCount).fold(\n () => \"Unknown\" as number | string,\n (count) => count as number | string,\n ),\n },\n description: {\n short: subreddit.publicDescription,\n full:\n subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description,\n },\n metadata: {\n created: formatTimestamp(subreddit.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n links: {\n subreddit: `https://reddit.com${subreddit.url}`,\n wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,\n },\n communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, Option(subreddit.activeUserCount), ageDays),\n engagementTips: getSubredditEngagementTips(subreddit),\n }\n}\n\nexport function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {\n const flags: readonly string[] = [...(comment.edited ? [\"Edited\"] : []), ...(comment.isSubmitter ? [\"OP\"] : [])]\n\n return {\n author: comment.author,\n content: comment.body.length > 300 ? `${comment.body.substring(0, 297)}...` : comment.body,\n stats: {\n score: comment.score,\n controversiality: comment.controversiality,\n },\n context: {\n subreddit: comment.subreddit,\n thread: comment.submissionTitle,\n },\n metadata: {\n posted: formatTimestamp(comment.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n link: `https://reddit.com${comment.permalink}`,\n commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),\n }\n}\n\n// Simple formatter for posts (used in search and comment tools)\nexport function formatPost(post: RedditPost) {\n return {\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n score: post.score,\n upvoteRatio: Math.round(post.upvoteRatio * 100),\n numComments: post.numComments,\n createdAt: formatTimestamp(post.createdUtc),\n selftext: post.selftext,\n permalink: post.permalink,\n nsfw: post.over18,\n spoiler: post.spoiler,\n }\n}\n","import crypto from \"crypto\"\nimport dotenv from \"dotenv\"\nimport { FastMCP } from \"fastmcp\"\nimport { Option } from \"functype\"\nimport { z } from \"zod\"\n\nimport { getRedditClient, initializeRedditClient } from \"./client/reddit-client\"\nimport type {\n BotDisclosureConfig,\n CacheConfig,\n RedditAuthMode,\n RedditSafeMode,\n RetryConfig,\n SafeModeConfig,\n UserContent,\n} from \"./types\"\nimport { formatPostInfo, formatSubredditInfo, formatUserInfo } from \"./utils/formatters\"\n\n// Load environment variables\ndotenv.config({ quiet: true })\n\n// Version injected at build time by tsdown\ndeclare const __VERSION__: string\nconst VERSION = (typeof __VERSION__ !== \"undefined\" ? __VERSION__ : \"0.0.0-dev\") as `${number}.${number}.${number}`\n\n// User-Agent validation and building\nfunction validateUserAgent(userAgent: string, username?: string): void {\n const recommendedPattern = /^[\\w-]+:[\\w-]+:[\\d.]+ \\(by \\/u\\/\\w+\\)$/\n if (!recommendedPattern.test(userAgent)) {\n console.error(\"[Warning] User-Agent does not follow Reddit's recommended format\")\n console.error(\"[Warning] Recommended: 'platform:app_id:version (by /u/username)'\")\n console.error(\"[Warning] Non-standard User-Agents may increase ban risk\")\n if (username !== undefined) {\n console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`)\n }\n }\n}\n\nfunction buildUserAgent(customAgent?: string, username?: string): string {\n if (customAgent !== undefined) {\n validateUserAgent(customAgent, username)\n return customAgent\n }\n\n if (username !== undefined) {\n const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`\n console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`)\n return autoAgent\n }\n\n const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`\n console.error(\n \"[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.\",\n )\n return fallbackAgent\n}\n\n// Safe mode configuration\nfunction buildSafeModeConfig(safeMode: RedditSafeMode): SafeModeConfig {\n switch (safeMode) {\n case \"off\":\n return {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n case \"standard\":\n return {\n enabled: true,\n mode: \"standard\",\n writeDelayMs: 2000,\n duplicateCheck: true,\n maxRecentHashes: 10,\n }\n case \"strict\":\n return {\n enabled: true,\n mode: \"strict\",\n writeDelayMs: 5000,\n duplicateCheck: true,\n maxRecentHashes: 20,\n }\n }\n}\n\nfunction unwrapClient() {\n return getRedditClient().orThrow(new Error(\"Reddit client not initialized\"))\n}\n\n// Footer appended to paginated listings when more results are available.\nfunction nextPageHint(after?: string): string {\n return Option(after).fold(\n () => \"\",\n (cursor) => `\\n\\n---\\nMore results available — call again with after=\"${cursor}\" for the next page.`,\n )\n}\n\n// Render a mixed posts+comments listing (saved / overview).\nfunction formatUserContent(heading: string, content: UserContent): string {\n const postsSection =\n content.posts.length === 0\n ? \"\"\n : `## Posts (${content.posts.length})\\n${content.posts\n .map(\n (post, index) =>\n `${index + 1}. ${post.title} — r/${post.subreddit}, score ${post.score.toLocaleString()} — https://reddit.com${post.permalink}`,\n )\n .join(\"\\n\")}\\n\\n`\n\n const commentsSection =\n content.comments.length === 0\n ? \"\"\n : `## Comments (${content.comments.length})\\n${content.comments\n .map((comment, index) => {\n const body = comment.body.length > 200 ? `${comment.body.substring(0, 200)}...` : comment.body\n return `${index + 1}. in r/${comment.subreddit}: ${body} — https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\")}\\n\\n`\n\n const empty = content.posts.length === 0 && content.comments.length === 0 ? \"No items found.\\n\\n\" : \"\"\n\n return `# ${heading}\\n\\n${postsSection}${commentsSection}${empty}`.trimEnd() + nextPageHint(content.after)\n}\n\n// Initialize Reddit client\nasync function setupRedditClient() {\n const clientId = process.env.REDDIT_CLIENT_ID\n const clientSecret = process.env.REDDIT_CLIENT_SECRET\n const customUserAgent = process.env.REDDIT_USER_AGENT\n const username = process.env.REDDIT_USERNAME\n const password = process.env.REDDIT_PASSWORD\n const authMode = (process.env.REDDIT_AUTH_MODE ?? \"auto\") as RedditAuthMode\n const safeMode = (process.env.REDDIT_SAFE_MODE ?? \"standard\") as RedditSafeMode\n\n // Validate auth mode\n if (![\"auto\", \"authenticated\", \"anonymous\"].includes(authMode)) {\n console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`)\n console.error(\"[Error] Valid options are: auto, authenticated, anonymous\")\n process.exit(1)\n }\n\n // Validate safe mode\n if (![\"off\", \"standard\", \"strict\"].includes(safeMode)) {\n console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`)\n console.error(\"[Error] Valid options are: off, standard, strict\")\n process.exit(1)\n }\n\n // In authenticated mode, require credentials\n if (authMode === \"authenticated\" && (clientId === undefined || clientSecret === undefined)) {\n console.error(\"[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n // For auto/anonymous, credentials are optional\n const hasCredentials = Boolean(clientId && clientSecret)\n\n // Build user-agent (auto-format with username if available)\n const userAgent = buildUserAgent(customUserAgent, username)\n\n // Build safe mode config\n const safeModeConfig = buildSafeModeConfig(safeMode)\n\n // Build bot disclosure config\n const botDisclosureMode = process.env.REDDIT_BOT_DISCLOSURE ?? \"off\"\n const defaultFooter =\n \"\\n\\n---\\n^(🤖 I am a bot | Built with) [^reddit-mcp-server](https://github.com/jordanburke/reddit-mcp-server)\"\n const botDisclosureConfig: BotDisclosureConfig = {\n enabled: botDisclosureMode === \"auto\",\n footer: botDisclosureMode === \"auto\" ? (process.env.REDDIT_BOT_FOOTER ?? defaultFooter) : \"\",\n }\n\n // Build cache config (enabled by default to ease Reddit rate limits; opt out with REDDIT_CACHE=off)\n const cacheEnabled = (process.env.REDDIT_CACHE ?? \"on\") !== \"off\"\n const cacheMaxMb = Number(process.env.REDDIT_CACHE_MAX_MB ?? \"50\")\n const cacheConfig: CacheConfig = {\n enabled: cacheEnabled,\n maxBytes: (Number.isFinite(cacheMaxMb) && cacheMaxMb > 0 ? cacheMaxMb : 50) * 1024 * 1024,\n }\n\n // Retry on HTTP 429 with Retry-After backoff (opt out with REDDIT_MAX_RETRIES=0)\n const maxRetriesRaw = Number(process.env.REDDIT_MAX_RETRIES ?? \"3\")\n const retryConfig: RetryConfig = {\n maxRetries: Number.isFinite(maxRetriesRaw) && maxRetriesRaw >= 0 ? Math.floor(maxRetriesRaw) : 3,\n baseDelayMs: 1000,\n maxDelayMs: 60_000,\n }\n\n const client = initializeRedditClient({\n clientId: clientId ?? \"\",\n clientSecret: clientSecret ?? \"\",\n userAgent,\n username,\n password,\n authMode,\n safeMode: safeModeConfig,\n botDisclosure: botDisclosureConfig,\n cache: cacheConfig,\n retry: retryConfig,\n })\n\n console.error(\"[Setup] Reddit client initialized\")\n console.error(`[Setup] Authentication mode: ${authMode}`)\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] Using anonymous Reddit API (~10 req/min)\")\n console.error(\"[Setup] No authentication required - ready to use!\")\n } else {\n console.error(\"[Setup] Testing Reddit API connection...\")\n const isConnected = await client.checkAuthentication()\n\n if (!isConnected) {\n console.error(\"[Error] ✗ Failed to connect to Reddit API\")\n console.error(\"[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n console.error(\"[Setup] ✓ Reddit API connection successful\")\n console.error(\"[Setup] Using OAuth Reddit API (60-100 req/min)\")\n }\n\n if (username !== undefined && password !== undefined) {\n console.error(`[Setup] ✓ User authenticated as: ${username}`)\n console.error(\"[Setup] Write operations enabled (posting, replying, editing, deleting)\")\n } else {\n console.error(\"[Setup] Read-only mode (no user credentials)\")\n console.error(\"[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n\n // Log safe mode status\n if (safeModeConfig.enabled) {\n console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`)\n console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`)\n console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`)\n } else {\n console.error(\n \"[Setup] Safe mode: off (explicitly disabled — ensure compliance with Reddit's Responsible Builder Policy)\",\n )\n }\n\n // Log bot disclosure status\n if (botDisclosureConfig.enabled) {\n console.error(\"[Setup] ✓ Bot disclosure: enabled (automated content will include bot footer)\")\n } else {\n console.error(\"[Setup] Bot disclosure: off\")\n console.error(\"[Setup] For Reddit policy compliance, consider REDDIT_BOT_DISCLOSURE=auto\")\n }\n}\n\n// OAuth token: generate once at startup, never expose in responses\nconst oauthToken = process.env.OAUTH_TOKEN ?? crypto.randomBytes(32).toString(\"hex\")\nif (process.env.OAUTH_ENABLED === \"true\" && process.env.OAUTH_TOKEN === undefined) {\n console.error(`[Auth] Generated OAuth token: ${oauthToken}`)\n}\n\n// Create FastMCP server\nconst server = new FastMCP({\n name: \"reddit-mcp-server\",\n version: VERSION,\n instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.\n\nAvailable capabilities:\n- Fetch Reddit posts, comments, and user information\n- Get subreddit details and statistics\n- Search Reddit content across posts and subreddits\n- Create posts and reply to posts/comments (with authentication)\n- Edit your own posts and comments (with authentication)\n- Delete your own posts and comments (with authentication)\n- Analyze engagement metrics and community insights\n\nFor write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.\n\nIMPORTANT - Reddit Responsible Builder Policy compliance:\n- Data retrieved via these tools must NOT be used for AI model training without Reddit's written approval\n- Data must NOT be sold, licensed, or commercially redistributed\n- Do NOT attempt to de-anonymize or re-identify Reddit users\n- Do NOT post identical or substantially similar content across multiple subreddits\n- Do NOT use these tools to manipulate votes, karma, or circumvent Reddit safety mechanisms\n- All bot-generated content must clearly disclose its automated nature\n- Bots must NOT send private/direct messages without explicit user consent\nFor details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy`,\n\n // Optional OAuth configuration for HTTP transport\n ...(process.env.OAUTH_ENABLED === \"true\" && {\n authenticate: (request: { readonly headers: { readonly authorization?: string } }) => {\n const authHeader = request.headers.authorization\n if (!authHeader?.startsWith(\"Bearer \")) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 401,\n statusText: \"Missing or invalid Authorization header\",\n })\n }\n\n const token = authHeader.slice(7)\n const tokenBuffer = Buffer.from(token)\n const expectedBuffer = Buffer.from(oauthToken)\n const tokenHash = crypto.createHash(\"sha256\").update(tokenBuffer).digest()\n const expectedHash = crypto.createHash(\"sha256\").update(expectedBuffer).digest()\n if (!crypto.timingSafeEqual(tokenHash, expectedHash)) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 403,\n statusText: \"Invalid token\",\n })\n }\n\n return Promise.resolve({ authenticated: true })\n },\n }),\n})\n\n// Test tool\nserver.addTool({\n name: \"test_reddit_mcp_server\",\n description:\n 'Health check for the Reddit MCP server. Read-only and side-effect-free — inspects local configuration only and makes no Reddit API calls. Returns the server version, whether the Reddit client is initialized, whether OAuth credentials are present, and whether write access (REDDIT_USERNAME/REDDIT_PASSWORD) is configured. Use this first to diagnose setup/auth problems. Do NOT use it to check Reddit\\'s own status or connectivity — it never contacts Reddit. A \"✗ Write Access\" result means the write tools (create_post, reply_to_post, edit_*, delete_*) will fail.',\n annotations: {\n title: \"Test Reddit MCP Server\",\n readOnlyHint: true,\n openWorldHint: false,\n },\n parameters: z.object({}),\n execute: () => {\n const client = getRedditClient()\n const hasAuth = client.fold(\n () => \"✗\",\n () => \"✓\",\n )\n const hasWriteAccess =\n process.env.REDDIT_USERNAME !== undefined && process.env.REDDIT_PASSWORD !== undefined ? \"✓\" : \"✗\"\n\n return Promise.resolve(`Reddit MCP Server Status:\n- Server: ✓ Running\n- Reddit Client: ${hasAuth} ${client.fold(\n () => \"Not initialized\",\n () => \"Initialized\",\n )}\n- Write Access: ${hasWriteAccess} ${hasWriteAccess === \"✓\" ? \"Available\" : \"Read-only mode\"}\n- Version: ${VERSION}\n\nReady to handle Reddit API requests!`)\n },\n})\n\n// User tools\nserver.addTool({\n name: \"get_user_info\",\n description:\n \"Get a public profile for any Reddit user: comment/post/total karma, account age and status flags, plus a short activity analysis and engagement tips. Read-only; works in anonymous mode. Returns profile stats only — use get_user_posts / get_user_comments for their actual content. Use get_me instead for your own authenticated account; do NOT expect private fields here, as only public data is returned.\",\n annotations: {\n title: \"Get User Info\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n username: z\n .string()\n .describe(\"The target user's Reddit username, without the u/ prefix (e.g. 'spez', not 'u/spez').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUser(args.username)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user info: ${err.message}`)\n },\n (user) => {\n const formattedUser = formatUserInfo(user)\n\n return `# User Information: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}\n\n## Activity Analysis\n- ${formattedUser.activityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Recommendations\n- ${formattedUser.recommendations.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_me\",\n description:\n \"Get the authenticated user's own profile (karma, account age, status flags). Read-only, but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD) and fails in anonymous mode. Use this instead of get_user_info when you need the current account rather than an arbitrary user. Do NOT use it to look up other users — it always returns the logged-in account.\",\n annotations: {\n title: \"Get My Account\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({}),\n execute: async () => {\n const client = unwrapClient()\n\n const result = await client.getMe()\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get authenticated user: ${err.message}`)\n },\n (user) => {\n const formattedUser = formatUserInfo(user)\n\n return `# Your Account: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_my_overview\",\n description:\n \"Get the authenticated user's own recent activity — posts and comments interleaved, newest first. Read-only but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD). Returns up to `limit` items plus an `after` cursor for the next page. Use get_my_saved for saved items, or get_user_posts / get_user_comments for another user. Do NOT use this to fetch a specific post's thread — use get_post_comments.\",\n annotations: {\n title: \"Get My Overview\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n limit: z.number().min(1).max(100).default(25).describe(\"How many activity items to return, 1–100 (default 25).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value returned by a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMyOverview({ limit: args.limit, after: args.after })\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get your overview: ${err.message}`)\n },\n (content) => formatUserContent(\"Your Overview\", content),\n )\n },\n})\n\nserver.addTool({\n name: \"get_my_saved\",\n description:\n \"Get the authenticated user's saved posts and comments (private to the account). Read-only but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD). Returns up to `limit` items plus an `after` pagination cursor. Use get_my_overview for your authored activity. Do NOT use this for another user — saved items are private and have no cross-user equivalent.\",\n annotations: {\n title: \"Get My Saved\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n limit: z.number().min(1).max(100).default(25).describe(\"How many saved items to return, 1–100 (default 25).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value returned by a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMySaved({ limit: args.limit, after: args.after })\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get saved content: ${err.message}`)\n },\n (content) => formatUserContent(\"Your Saved Content\", content),\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_posts\",\n description:\n \"Get posts submitted by a specific user, with sort (new/hot/top) and time filter. Read-only; works anonymously. Returns a page of posts (title, subreddit, score, upvote ratio, comment count, permalink) plus an `after` cursor for paging. Use get_user_comments for their comments, or get_user_info for karma/profile stats. Do NOT use this to search a subreddit — use search_reddit or browse_subreddit.\",\n annotations: {\n title: \"Get User Posts\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n username: z.string().describe(\"The author's Reddit username, without the u/ prefix (e.g. 'spez').\"),\n sort: z\n .enum([\"new\", \"hot\", \"top\"])\n .default(\"new\")\n .describe(\n \"Ordering: 'new' (most recent), 'hot' (currently active), or 'top' (highest score within `time_filter`). Default 'new'.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time window for scoring; only applies when sort='top'. Ignored for 'new'/'hot'. Default 'all'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many posts to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserPosts(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user posts: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n return `No posts found for u/${args.username} with the specified filters.`\n }\n\n const postSummaries = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_comments\",\n description:\n \"Get comments made by a specific user, with sort (new/hot/top) and time filter. Read-only; works anonymously. Returns a page of comments (subreddit, parent post title, body excerpt, score, permalink) plus an `after` cursor. Use get_user_posts for their submissions, or get_user_info for karma/profile stats. Do NOT use this to read one post's thread — use get_post_comments.\",\n annotations: {\n title: \"Get User Comments\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n username: z.string().describe(\"The author's Reddit username, without the u/ prefix (e.g. 'spez').\"),\n sort: z\n .enum([\"new\", \"hot\", \"top\"])\n .default(\"new\")\n .describe(\n \"Ordering: 'new' (most recent), 'hot' (currently active), or 'top' (highest score within `time_filter`). Default 'new'.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time window for scoring; only applies when sort='top'. Ignored for 'new'/'hot'. Default 'all'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many comments to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserComments(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user comments: ${err.message}`)\n },\n (page) => {\n const comments = page.items\n if (comments.length === 0) {\n return `No comments found for u/${args.username} with the specified filters.`\n }\n\n const commentSummaries = comments\n .map((comment, index) => {\n const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n\n return `### ${index + 1}. Comment ${flags.join(\" \")}\nIn r/${comment.subreddit} on \"${comment.submissionTitle}\"\n\n> ${truncatedBody}\n\n- Score: ${comment.score.toLocaleString()}\n- Posted: ${new Date(comment.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${commentSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Post tools\nserver.addTool({\n name: \"get_reddit_post\",\n description:\n \"Get a single post by subreddit + post id: title, author, self-text or link content, score, upvote ratio, comment count, flair/flags, and an engagement analysis. Read-only; works anonymously. Returns the post only — use get_post_comments for its comment thread. Do NOT use this to list a subreddit's posts (use browse_subreddit / get_top_posts) or to find posts by keyword (use search_reddit).\",\n annotations: {\n title: \"Get Reddit Post\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit the post lives in, without the r/ prefix (e.g. 'programming').\"),\n post_id: z\n .string()\n .describe(\n \"Base36 post id — the segment after /comments/ in a permalink like reddit.com/r/<sub>/comments/<post_id>/... (e.g. '1abc23'). With or without a t3_ prefix.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getPost(args.post_id, args.subreddit)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post from r/${formattedPost.subreddit}\n\n## Post Details\n- Title: ${formattedPost.title}\n- Type: ${formattedPost.type}\n- Author: u/${formattedPost.author}\n\n## Content\n${formattedPost.content}\n\n## Stats\n- Score: ${formattedPost.stats.score.toLocaleString()}\n- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%\n- Comments: ${formattedPost.stats.comments.toLocaleString()}\n\n## Metadata\n- Posted: ${formattedPost.metadata.posted}\n- Flags: ${formattedPost.metadata.flags.length > 0 ? formattedPost.metadata.flags.join(\", \") : \"None\"}\n- Flair: ${formattedPost.metadata.flair}\n\n## Links\n- Full Post: ${formattedPost.links.fullPost}\n- Short Link: ${formattedPost.links.shortLink}\n\n## Engagement Analysis\n- ${formattedPost.engagementAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Best Time to Engage\n${formattedPost.bestTimeToEngage}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_top_posts\",\n description:\n \"Get the top-scoring posts from a subreddit — or from the authenticated home feed if no subreddit is given — within a time window (hour…all). Read-only; works anonymously. Returns a page of posts (title, author, score, upvote ratio, comments, link) plus an `after` cursor. This is a shortcut for the 'top' sort; use browse_subreddit for hot/new/rising/controversial, or search_reddit to find posts by keyword.\",\n annotations: {\n title: \"Get Top Posts\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z\n .string()\n .optional()\n .describe(\n \"Subreddit to read, without the r/ prefix (e.g. 'science'). Omit to use the authenticated home feed (requires credentials).\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time window the 'top' ranking is computed over (e.g. 'day' = top today). Default 'week'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many posts to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getTopPosts(args.subreddit ?? \"\", args.time_filter, args.limit, args.after)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get top posts: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n const location = Option(args.subreddit).fold(\n () => \"home feed\",\n (sr) => `r/${sr}`,\n )\n return `No posts found in ${location} for the specified time period.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const location = Option(args.subreddit).fold(\n () => \"Home Feed\",\n (sr) => `r/${sr}`,\n )\n return `# Top Posts from ${location} (${args.time_filter})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"browse_subreddit\",\n description:\n \"Browse a subreddit — or the authenticated home feed when no subreddit is given — by sort order: hot, new, top, rising, or controversial. Read-only; works anonymously. `time_filter` applies only to the top and controversial sorts. Returns a page of posts (title, author, score, upvote ratio, comments, link) plus an `after` cursor. Use get_top_posts as a shortcut for the top sort, or search_reddit to find posts by keyword rather than by feed order.\",\n annotations: {\n title: \"Browse Subreddit\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z\n .string()\n .optional()\n .describe(\n \"Subreddit to browse, without the r/ prefix (e.g. 'news'). Omit to use the authenticated home feed (requires credentials).\",\n ),\n sort: z\n .enum([\"hot\", \"new\", \"top\", \"rising\", \"controversial\"])\n .default(\"hot\")\n .describe(\n \"Feed ordering: 'hot' (default), 'new', 'rising', 'top', or 'controversial'. 'top'/'controversial' honor `time_filter`.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time window; only applies to sort='top' or 'controversial'. Ignored otherwise. Default 'week'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many posts to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.browseSubreddit(\n args.subreddit ?? \"\",\n args.sort,\n args.time_filter,\n args.limit,\n args.after,\n )\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to browse subreddit: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n const location = Option(args.subreddit).fold(\n () => \"home feed\",\n (sr) => `r/${sr}`,\n )\n if (posts.length === 0) {\n return `No posts found in ${location}.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const timeSuffix = args.sort === \"top\" || args.sort === \"controversial\" ? `, ${args.time_filter}` : \"\"\n const heading = location === \"home feed\" ? \"Home Feed\" : location\n return `# ${args.sort} posts from ${heading} (${args.sort}${timeSuffix})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Subreddit tools\nserver.addTool({\n name: \"get_subreddit_info\",\n description:\n \"Get a subreddit's profile: title, description, subscriber and active-user counts, creation date, flags, wiki/link URLs, plus a community analysis and posting tips. Read-only; works anonymously. Returns metadata about the community itself — use browse_subreddit / get_top_posts for its posts, or get_subreddit_rules for its posting rules. Do NOT use this to find subreddits by topic — use search_reddit with type='sr'.\",\n annotations: {\n title: \"Get Subreddit Info\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name, without the r/ prefix (e.g. 'askscience').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getSubredditInfo(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get subreddit info: ${err.message}`)\n },\n (subreddit) => {\n const formattedSubreddit = formatSubredditInfo(subreddit)\n\n return `# Subreddit Information: r/${formattedSubreddit.name}\n\n## Overview\n- Name: r/${formattedSubreddit.name}\n- Title: ${formattedSubreddit.title}\n- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}\n- Active Users: ${\n typeof formattedSubreddit.stats.activeUsers === \"number\"\n ? formattedSubreddit.stats.activeUsers.toLocaleString()\n : formattedSubreddit.stats.activeUsers\n }\n\n## Description\n${formattedSubreddit.description.short}\n\n## Detailed Description\n${formattedSubreddit.description.full}\n\n## Metadata\n- Created: ${formattedSubreddit.metadata.created}\n- Flags: ${formattedSubreddit.metadata.flags.join(\", \")}\n\n## Links\n- Subreddit: ${formattedSubreddit.links.subreddit}\n- Wiki: ${formattedSubreddit.links.wiki}\n\n## Community Analysis\n- ${formattedSubreddit.communityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Engagement Tips\n- ${formattedSubreddit.engagementTips.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_subreddit_rules\",\n description:\n \"Get a subreddit's posting rules (each rule's name, what it applies to, and its description). Read-only; works anonymously. Returns the rules list, or a note when the subreddit lists none. Call this before create_post to check requirements and avoid auto-removal. For available post flairs use get_post_flairs instead.\",\n annotations: {\n title: \"Get Subreddit Rules\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name, without the r/ prefix (e.g. 'AskReddit').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getSubredditRules(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get subreddit rules: ${err.message}`)\n },\n (rules) => {\n if (rules.length === 0) {\n return `r/${args.subreddit_name} has no listed subreddit-specific rules.`\n }\n\n const ruleList = rules\n .map((rule, index) => {\n const applies = rule.kind === \"all\" ? \"posts & comments\" : `${rule.kind}s`\n const detail = rule.description.trim() === \"\" ? \"\" : `\\n${rule.description.trim()}`\n return `### ${index + 1}. ${rule.shortName} _(applies to ${applies})_${detail}`\n })\n .join(\"\\n\\n\")\n\n return `# Posting Rules for r/${args.subreddit_name}\n\n${ruleList}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_post_flairs\",\n description:\n \"List a subreddit's selectable link flairs (flair text + flair_id) for use with create_post. Read-only, but requires user credentials; many subreddits expose flairs only to members, so this can 403 or return empty anonymously. Pass a returned flair_id (and flair_text for text-editable flairs) to create_post. For the subreddit's posting rules use get_subreddit_rules instead.\",\n annotations: {\n title: \"Get Post Flairs\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name, without the r/ prefix (e.g. 'gadgets').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getPostFlairs(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get post flairs: ${err.message}`)\n },\n (flairs) => {\n if (flairs.length === 0) {\n return `r/${args.subreddit_name} has no selectable link flairs (or none are visible to this account).`\n }\n\n const flairList = flairs\n .map((flair) => {\n const editable = flair.textEditable === true ? \" _(text editable)_\" : \"\"\n return `- ${flair.text}${editable} — \\`flair_id: ${flair.id}\\``\n })\n .join(\"\\n\")\n\n return `# Available Link Flairs for r/${args.subreddit_name}\n\n${flairList}\n\nPass the desired \\`flair_id\\` to \\`create_post\\`.`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_trending_subreddits\",\n description:\n \"Get the subreddits Reddit is currently featuring as trending/popular. Read-only, no parameters; works anonymously. Returns a list of subreddit names that changes through the day (cached briefly server-side). To find subreddits by keyword instead of by trend, use search_reddit with type='sr'.\",\n annotations: {\n title: \"Get Trending Subreddits\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({}),\n execute: async () => {\n const client = unwrapClient()\n\n const result = await client.getTrendingSubreddits()\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get trending subreddits: ${err.message}`)\n },\n (trendingSubreddits) => `# Trending Subreddits\n\n${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join(\"\\n\")}`,\n )\n },\n})\n\n// Search tools\nserver.addTool({\n name: \"search_reddit\",\n description:\n \"Search Reddit for posts — or subreddits/users via `type` — optionally scoped to one subreddit, with sort and time filters. Read-only; works anonymously. Returns a page of results (title, subreddit, author, score, comments, link) plus an `after` cursor for paging. Use this to find content by keyword; use browse_subreddit / get_top_posts to list a known subreddit's feed instead.\",\n annotations: {\n title: \"Search Reddit\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n query: z\n .string()\n .describe(\n \"Search terms; supports Reddit operators (quotes for exact phrases, author:name, self:yes). Must be non-empty.\",\n ),\n subreddit: z\n .string()\n .optional()\n .describe(\n \"Restrict results to this subreddit, without the r/ prefix (e.g. 'python'). Omit to search all of Reddit.\",\n ),\n sort: z\n .enum([\"relevance\", \"hot\", \"top\", \"new\", \"comments\"])\n .default(\"relevance\")\n .describe(\n \"Sort order. Prefer 'relevance' (default) for finding posts about a topic. Use 'top'/'hot' only for what's currently popular and 'new' for the latest — these rank by karma/recency and, especially combined with a narrow time_filter, can surface loosely-matching posts over the best topical results.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Restrict to results from this recent window (e.g. 'week'). Default 'all' (no time limit).\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many results to return, 1–100 (default 10).\"),\n type: z\n .enum([\"link\", \"sr\", \"user\"])\n .default(\"link\")\n .describe(\"What to search for: 'link' = posts (default), 'sr' = subreddits, 'user' = users.\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.query.trim() === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"Search query cannot be empty\")\n }\n\n const result = await client.searchReddit(args.query, {\n subreddit: args.subreddit,\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n type: args.type,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to search: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `No results found for \"${args.query}\"${searchLocation}.`\n }\n\n const searchResults = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Author: u/${post.author}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `# Reddit Search Results for: \"${args.query}\"${searchLocation}\n\nSorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}\n\n${searchResults}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Write tools (require user authentication)\nserver.addTool({\n name: \"create_post\",\n description:\n \"Create a new text or link post in a subreddit. Mutating and NOT idempotent — each call publishes a separate post. Requires REDDIT_USERNAME and REDDIT_PASSWORD; fails without them. Returns the new post's id and URL. Check get_subreddit_rules and get_post_flairs first, since many subreddits require a flair or reject certain content. WARNING: rapid posting or duplicate content may trigger Reddit's spam detection and account bans — enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n annotations: {\n title: \"Create Post\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z.string().describe(\"Target subreddit, without the r/ prefix (e.g. 'test').\"),\n title: z.string().describe(\"Post title (cannot be edited after creation).\"),\n content: z\n .string()\n .describe(\n \"For a self post (is_self=true): the body text, Reddit markdown supported. For a link post (is_self=false): the destination URL.\",\n ),\n is_self: z\n .boolean()\n .default(true)\n .describe(\n \"true = text/self post using `content` as the body (default); false = link post using `content` as the URL.\",\n ),\n flair_id: z\n .string()\n .optional()\n .describe(\n \"Link flair template id from get_post_flairs; many subreddits require one or the post is auto-removed.\",\n ),\n flair_text: z\n .string()\n .optional()\n .describe(\"Custom flair text, allowed only for flairs whose template is text-editable.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.createPost(\n args.subreddit,\n args.title,\n args.content,\n args.is_self,\n args.flair_id,\n args.flair_text,\n )\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to create post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post Created Successfully\n\n## Post Details\n- Title: ${formattedPost.title}\n- Subreddit: r/${formattedPost.subreddit}\n- Type: ${formattedPost.type}\n- Link: ${formattedPost.links.fullPost}\n\nYour post has been successfully submitted to r/${formattedPost.subreddit}.`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"reply_to_post\",\n description:\n \"Post a reply to an existing post or comment. Mutating and NOT idempotent — each call adds a new comment. Requires REDDIT_USERNAME and REDDIT_PASSWORD. The parent is identified by its thing id — t3_ for a post, t1_ for a comment — so this creates both top-level and nested replies. Returns the new comment's id. Use edit_comment to change a reply you already posted. WARNING: rapid or duplicate replies may trigger Reddit's spam detection; enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n annotations: {\n title: \"Reply to Post or Comment\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n parameters: z.object({\n post_id: z\n .string()\n .describe(\n \"Parent thing id to reply under: t3_<id> for a post (creates a top-level comment) or t1_<id> for a comment (creates a nested reply).\",\n ),\n content: z.string().describe(\"Reply body text; Reddit markdown supported.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.replyToPost(args.post_id, args.content)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to reply: ${err.message}`)\n },\n (comment) => `# Reply Posted Successfully\n\n## Comment Details\n- Posted to: ${args.post_id}\n- Author: u/${process.env.REDDIT_USERNAME}\n- Comment ID: ${comment.id}\n\nYour reply has been successfully posted.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_post\",\n description:\n \"Permanently delete one of your own posts. Mutating and destructive but idempotent — deleting an already-deleted post is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and only works on posts authored by the authenticated account. Only affects the post you name; use delete_comment for comments. WARNING: this cannot be undone — the content is removed, though the post id remains.\",\n annotations: {\n title: \"Delete Post\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The post to delete: a full thing id 't3_<id>' or just the base36 post id '<id>' (the 't3_' prefix is added automatically). Must be a post you authored.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deletePost(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete post: ${err.message}`)\n },\n () => `# Post Deleted Successfully\n\nThe post ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_comment\",\n description:\n \"Permanently delete one of your own comments. Mutating and destructive but idempotent — deleting an already-deleted comment is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and only works on comments authored by the authenticated account. Only affects the comment you name; use delete_post for posts. WARNING: this cannot be undone — the content is removed, though the comment id remains.\",\n annotations: {\n title: \"Delete Comment\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The comment to delete: a full thing id 't1_<id>' or just the base36 comment id '<id>' (the 't1_' prefix is added automatically). Must be a comment you authored.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deleteComment(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete comment: ${err.message}`)\n },\n () => `# Comment Deleted Successfully\n\nThe comment ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_post\",\n description:\n 'Replace the body text of one of your own self-text posts. Mutating and idempotent (same text → same result); it overwrites the previous body. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and works only on self posts you authored — titles and link posts cannot be edited. Adds an \"edited\" marker. Use create_post to make a new post, or edit_comment for comments. WARNING: rapid edits may trigger spam detection; enable REDDIT_SAFE_MODE for protection.',\n annotations: {\n title: \"Edit Post\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The post to edit: a full thing id 't3_<id>' or just the base36 post id '<id>' (the 't3_' prefix is added automatically). Must be a self-text post you authored.\",\n ),\n new_text: z\n .string()\n .describe(\"Replacement body text; fully overwrites the current body. Reddit markdown supported.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editPost(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit post: ${err.message}`)\n },\n () => `# Post Edited Successfully\n\nThe post ${args.thing_id} has been updated with your new content.\n\n**Note**:\n- Only self (text) posts can be edited\n- Post titles cannot be edited\n- Link posts cannot be edited\n- An \"edited\" marker will appear on your post`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_comment\",\n description:\n 'Replace the text of one of your own comments. Mutating and idempotent (same text → same result); it overwrites the previous content. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and works only on comments you authored. Adds an \"edited\" marker. Use reply_to_post to add a new comment, or edit_post for posts. WARNING: rapid edits may trigger spam detection; enable REDDIT_SAFE_MODE for protection.',\n annotations: {\n title: \"Edit Comment\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The comment to edit: a full thing id 't1_<id>' or just the base36 comment id '<id>' (the 't1_' prefix is added automatically). Must be a comment you authored.\",\n ),\n new_text: z\n .string()\n .describe(\"Replacement comment text; fully overwrites the current content. Reddit markdown supported.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editComment(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit comment: ${err.message}`)\n },\n () => `# Comment Edited Successfully\n\nThe comment ${args.thing_id} has been updated with your new content.\n\n**Note**: An \"edited\" marker will appear on your comment to show it has been modified.`,\n )\n },\n})\n\n// Comment tools\nserver.addTool({\n name: \"get_post_comments\",\n description:\n \"Get the comment thread for a post (by post id + subreddit), sorted best/top/new/controversial/old/qa. Read-only; works anonymously. Returns the post header plus threaded comments (author, OP/edited badges, score, body, nesting depth) up to `limit`. Long threads are truncated with 'load more' stubs — expand those with get_more_comments. Use get_reddit_post for just the post body, not the thread.\",\n annotations: {\n title: \"Get Post Comments\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n post_id: z\n .string()\n .describe(\n \"Base36 post id — the segment after /comments/ in a permalink (e.g. '1abc23'). With or without a t3_ prefix.\",\n ),\n subreddit: z.string().describe(\"The subreddit the post lives in, without the r/ prefix (e.g. 'movies').\"),\n sort: z\n .enum([\"best\", \"top\", \"new\", \"controversial\", \"old\", \"qa\"])\n .default(\"best\")\n .describe(\"Comment ordering: 'best' (default), 'top', 'new', 'controversial', 'old', or 'qa' (Q&A).\"),\n limit: z\n .number()\n .min(1)\n .max(500)\n .default(100)\n .describe(\n \"Maximum comments to return, 1–500 (default 100). Deeply nested replies may still be truncated as 'load more' stubs.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.post_id === \"\" || args.subreddit === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"post_id and subreddit are required\")\n }\n\n const result = await client.getPostComments(args.post_id, args.subreddit, {\n sort: args.sort,\n limit: args.limit,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get comments: ${err.message}`)\n },\n ({ post, comments }) => {\n const header = `# Comments for: ${post.title}\n\n**Post by u/${post.author} in r/${post.subreddit}**\n- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n\n---\n\n`\n\n if (comments.length === 0) {\n return `${header}No comments found for this post.`\n }\n\n const commentSummaries = comments\n .map((comment) => {\n const indent = \"└─\".repeat(Math.min(comment.depth ?? 0, 3))\n const authorBadge = comment.isSubmitter ? \" **[OP]**\" : \"\"\n const editedBadge = comment.edited ? \" *(edited)*\" : \"\"\n\n return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)\n\n${comment.body}\n\n---`\n })\n .join(\"\\n\\n\")\n\n return header + commentSummaries\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_more_comments\",\n description:\n \"Expand truncated 'load more comments' stubs in a thread. Read-only; works anonymously. Pass the post's link id and the comment ids from a 'more' node (surfaced by get_post_comments) to fetch those hidden comments; returns the expanded comments (author, body excerpt, score, link). Call get_post_comments first to obtain the thread and its 'more' node ids — do NOT invent ids.\",\n annotations: {\n title: \"Get More Comments\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n link_id: z\n .string()\n .describe(\"The parent post's link id (base36, with or without the t3_ prefix) that the stub belongs to.\"),\n comment_ids: z\n .array(z.string())\n .min(1)\n .describe(\n \"Base36 comment ids to expand, taken from a 'more' node returned by get_post_comments (not arbitrary ids).\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMoreComments(args.link_id, args.comment_ids)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to expand comments: ${err.message}`)\n },\n (comments) => {\n if (comments.length === 0) {\n return \"No additional comments were returned for those ids.\"\n }\n\n const commentList = comments\n .map((comment, index) => {\n const truncated = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n return `### ${index + 1}. u/${comment.author} ${flags.join(\" \")}\n> ${truncated}\n\n- Score: ${comment.score.toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Expanded Comments (${comments.length})\n\n${commentList}`\n },\n )\n },\n})\n\n// Initialize and start server\nasync function main() {\n await setupRedditClient()\n\n const useHttp = process.env.TRANSPORT_TYPE === \"httpStream\" || process.env.TRANSPORT_TYPE === \"http\"\n const port = parseInt(process.env.PORT ?? \"3000\")\n const host = process.env.HOST ?? \"127.0.0.1\"\n\n if (useHttp) {\n console.error(`[Setup] Starting HTTP server on ${host}:${port}`)\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port,\n host,\n endpoint: \"/mcp\",\n },\n })\n console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`)\n console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`)\n } else {\n console.error(\"[Setup] Starting in stdio mode\")\n await server.start({\n transportType: \"stdio\",\n })\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nvoid main().catch(console.error)\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/client/errors.ts","../src/utils/reddit-identifiers.ts","../src/client/response-cache.ts","../src/client/reddit-client.ts","../src/utils/formatters.ts","../src/index.ts"],"sourcesContent":["import { Option } from \"functype\"\n\n/**\n * Typed error channel for the Reddit client.\n *\n * The client's methods are the imperative-to-functional boundary: each captures throws\n * (network, HTTP, JSON parsing, validation, deliberate domain errors) inside a `Try` and\n * converts to a typed `Either<RedditError, T>`. Modelling the error as a discriminated ADT\n * — rather than a bare `Error` — makes the failure contract explicit at the type level, so\n * callers can reason about (and branch on) what actually went wrong without string-matching.\n *\n * `classifyRedditError` is TOTAL and never re-throws — every captured `Error` maps to a\n * variant — which is what makes the migration behavior-preserving: an error that was a\n * graceful `Left` before is still a graceful `Left` after, just with a richer type.\n *\n * Two-tier behavior, to preserve the exact messages of the original try/catch code:\n * - A *deliberate* typed throw (HttpError, NotFoundError, …) already carries its final\n * message, so it passes through unchanged.\n * - An *unexpected* generic error (fetch/JSON/orThrow) is wrapped as UnknownError, with the\n * optional `context` prefix — present for read methods (which prefixed in their catch),\n * absent for write methods (which returned the raw message).\n */\n\nabstract class RedditErrorBase extends Error {}\n\n/** A non-ok HTTP response from the Reddit API. Carries the status for caller branching. */\nexport class HttpError extends RedditErrorBase {\n readonly _tag = \"HttpError\" as const\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message)\n this.name = \"HttpError\"\n }\n}\n\n/** A write operation was attempted without the required user credentials / in a wrong mode. */\nexport class NotAuthenticatedError extends RedditErrorBase {\n readonly _tag = \"NotAuthenticatedError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"NotAuthenticatedError\"\n }\n}\n\n/** Reddit accepted the request but returned errors in its JSON envelope (or an unusable body). */\nexport class ApiError extends RedditErrorBase {\n readonly _tag = \"ApiError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"ApiError\"\n }\n}\n\n/** The requested post/entity does not exist or is not accessible. */\nexport class NotFoundError extends RedditErrorBase {\n readonly _tag = \"NotFoundError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"NotFoundError\"\n }\n}\n\n/**\n * Reddit refused the request at the network level rather than for this specific resource.\n *\n * Reddit 403s the unauthenticated JSON API from many IP ranges (datacenters, VPNs, flagged\n * addresses), answering with an HTML block page instead of JSON. A bare `HttpError(403)` reads\n * as \"this subreddit is private\", which sends people looking in the wrong place — the fix is to\n * supply OAuth credentials, which also raises the rate limit from ~10 to 60+ req/min.\n */\nexport class NetworkBlockedError extends RedditErrorBase {\n readonly _tag = \"NetworkBlockedError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"NetworkBlockedError\"\n }\n}\n\n/** Client-side input or safety-policy rejection (invalid sort, duplicate-content guard). */\nexport class ValidationError extends RedditErrorBase {\n readonly _tag = \"ValidationError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"ValidationError\"\n }\n}\n\n/** Any failure that is not a recognized category: network, JSON parsing, unexpected throws. */\nexport class UnknownError extends RedditErrorBase {\n readonly _tag = \"UnknownError\" as const\n constructor(\n message: string,\n readonly cause?: unknown,\n ) {\n super(message)\n this.name = \"UnknownError\"\n }\n}\n\nexport type RedditError =\n HttpError | NotAuthenticatedError | ApiError | NotFoundError | NetworkBlockedError | ValidationError | UnknownError\n\nexport function isRedditError(error: unknown): error is RedditError {\n return error instanceof RedditErrorBase\n}\n\n/**\n * Total classifier from a captured `Error` to a `RedditError`. Deliberate typed throws pass\n * through unchanged; everything else becomes an `UnknownError`, prefixed with `context` when\n * provided so the observable message text matches the previous try/catch-based wrapping.\n */\nexport function classifyRedditError(error: Error, context?: string): RedditError {\n if (isRedditError(error)) {\n return error\n }\n const message = Option(context).fold(\n () => error.message,\n (ctx) => `${ctx}: ${error.message}`,\n )\n return new UnknownError(message, error)\n}\n","/* eslint-disable functype/prefer-either, functype/prefer-fold --\n * These validators are called from inside the `Try` bodies in reddit-client.ts, which can only\n * signal failure by throwing; the throw is captured there and converted to\n * `Either<RedditError, T>` by `classifyRedditError`. Returning Either here would force every\n * call site to unwrap mid-path-construction for no added safety. Same rationale as the\n * file-level disable in reddit-client.ts. The ternary flagged by prefer-fold is a native\n * `undefined` check, not an Option.\n */\n\n/**\n * Validation and normalization for Reddit identifiers that get interpolated into API paths.\n *\n * Every identifier reaching the client is model- or user-supplied. Interpolating one raw is a\n * path-injection hole: URL parsing resolves dot segments, so a `subreddit` of `../../api/v1/me`\n * escapes `/r/{sub}/about.json` and steers the OAuth bearer token to a different endpoint, and an\n * embedded `?` or `&` injects query parameters into the request we build.\n *\n * So each identifier is normalized (stripping the `r/`, `/u/`, `t3_` prefixes people naturally\n * type) and then checked against Reddit's own charset. Every value returned from this module\n * matches `[A-Za-z0-9_+-]+`, which is already URL-path-safe — no percent-encoding is applied,\n * because encoding the `+` in a multireddit (`r/science+space`) would break it.\n */\nimport { ValidationError } from \"../client/errors\"\n\n// Reddit subreddit names: 3-21 chars, letters/digits/underscore. The `u_` prefix denotes a user\n// profile \"subreddit\" (r/u_spez), which the listing endpoints accept.\nconst SUBREDDIT_PATTERN = /^(u_)?[A-Za-z0-9][A-Za-z0-9_]{1,20}$/\n\n// Reddit usernames: 3-20 chars, letters/digits/underscore/hyphen.\nconst USERNAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{1,19}$/\n\n// Thing IDs are base36. Reddit mints them lowercase; we accept either case and normalize down.\nconst THING_ID_PATTERN = /^[a-z0-9]{1,13}$/\n\nconst stripLeading = (value: string, prefixes: readonly string[]): string => {\n const lower = value.toLowerCase()\n const matched = prefixes.find((prefix) => lower.startsWith(prefix))\n return matched === undefined ? value : value.slice(matched.length)\n}\n\nconst bare = (value: string, prefixes: readonly string[]): string =>\n stripLeading(stripLeading(value.trim(), [\"/\"]), prefixes).replace(/\\/+$/, \"\")\n\n/**\n * Normalize a subreddit name for use in a path segment.\n *\n * Accepts `science`, `r/science`, `/r/science`, and `+`-joined multireddits (`science+space`).\n * The empty string passes through unchanged — callers use it to mean \"the home feed\".\n */\nexport function normalizeSubreddit(input: string): string {\n const trimmed = input.trim()\n if (trimmed === \"\") {\n return \"\"\n }\n\n const segments = bare(trimmed, [\"r/\"])\n .split(\"+\")\n .map((segment) => segment.trim())\n\n const invalid = segments.find((segment) => !SUBREDDIT_PATTERN.test(segment))\n if (invalid !== undefined) {\n throw new ValidationError(\n `Invalid subreddit name \"${input}\". Expected 2-21 characters of letters, digits, or underscores (e.g. \"science\").`,\n )\n }\n\n return segments.join(\"+\")\n}\n\n/** Normalize a username for use in a path segment. Accepts `spez`, `u/spez`, `/user/spez`. */\nexport function normalizeUsername(input: string): string {\n const name = bare(input, [\"user/\", \"u/\"])\n\n if (!USERNAME_PATTERN.test(name)) {\n throw new ValidationError(\n `Invalid Reddit username \"${input}\". Expected 2-20 characters of letters, digits, underscores, or hyphens (e.g. \"spez\").`,\n )\n }\n\n return name\n}\n\n/**\n * Normalize a post or comment ID to its bare base36 form, dropping any `t1_`/`t3_` fullname\n * prefix. Use this wherever the ID lands in a path segment or query value.\n */\nexport function normalizeThingId(input: string): string {\n const id = bare(input, [\"t1_\", \"t3_\", \"t4_\", \"t5_\"]).toLowerCase()\n\n if (!THING_ID_PATTERN.test(id)) {\n throw new ValidationError(\n `Invalid Reddit ID \"${input}\". Expected a base36 id such as \"1abc2de\", optionally prefixed with t1_ or t3_.`,\n )\n }\n\n return id\n}\n\n/**\n * Normalize an ID to a Reddit fullname (`t3_1abc2de`), preserving an explicit `t1_`/`t3_` prefix\n * and falling back to `defaultKind` when the caller passed a bare ID.\n */\nexport function normalizeFullname(input: string, defaultKind: \"t1\" | \"t3\"): string {\n const trimmed = input.trim().toLowerCase()\n const kind = trimmed.startsWith(\"t1_\") ? \"t1\" : trimmed.startsWith(\"t3_\") ? \"t3\" : defaultKind\n return `${kind}_${normalizeThingId(input)}`\n}\n","/* eslint-disable functype/prefer-functype-map, functype/prefer-option, functype/no-imperative-loops --\n * This module is a deliberately imperative performance primitive: a mutable, byte-bounded\n * LRU cache. A functype immutable Map cannot express LRU access-order reordering or running\n * byte accounting without rebuilding the whole structure on every operation, and the eviction\n * loop and undefined \"miss\" sentinel are the clearest expression of that stateful contract.\n * Mirrors the imperative-boundary convention used in reddit-client.ts.\n */\n\n/**\n * In-memory cache for read-only Reddit GET responses.\n *\n * Reddit's rate limits are tight (~10 req/min anonymous, 60-100 authenticated),\n * so caching identical reads for a short window meaningfully reduces request\n * pressure. TTLs are adaptive: volatile listings expire quickly while relatively\n * stable resources (top/controversial, search, user/subreddit \"about\") live longer.\n *\n * Eviction is LRU bounded by a byte budget, so the cache can never grow unbounded.\n */\n\ntype CacheEntry = {\n readonly body: string\n readonly status: number\n readonly expiresAt: number\n readonly bytes: number\n}\n\nexport type CachedResponse = {\n readonly body: string\n readonly status: number\n}\n\nconst SECOND = 1_000\n\nexport class ResponseCache {\n private readonly maxBytes: number\n private readonly now: () => number\n // Map iteration order is insertion order, which we use as the LRU ordering:\n // the first key is the least-recently-used entry.\n private readonly entries = new Map<string, CacheEntry>()\n private currentBytes = 0\n\n constructor(options: { readonly maxBytes: number; readonly now?: () => number }) {\n this.maxBytes = options.maxBytes\n this.now = options.now ?? Date.now\n }\n\n /** Adaptive TTL (in milliseconds) for a given request URL. */\n ttlFor(url: string): number {\n if (/\\/(hot|new|rising)\\.json/.test(url)) {\n return 60 * SECOND\n }\n if (/\\/(top|controversial)\\.json/.test(url) || /\\/search\\.json/.test(url) || /\\/about\\.json/.test(url)) {\n return 300 * SECOND\n }\n if (/\\/comments\\//.test(url)) {\n return 60 * SECOND\n }\n return 120 * SECOND\n }\n\n get(url: string): CachedResponse | undefined {\n const entry = this.entries.get(url)\n if (entry === undefined) {\n return undefined\n }\n if (this.now() >= entry.expiresAt) {\n this.entries.delete(url)\n this.currentBytes -= entry.bytes\n return undefined\n }\n // Mark as most-recently-used by reinserting at the end.\n this.entries.delete(url)\n this.entries.set(url, entry)\n return { body: entry.body, status: entry.status }\n }\n\n set(url: string, body: string, status: number): void {\n const bytes = Buffer.byteLength(body, \"utf8\")\n // A single oversized body is simply not cached.\n if (bytes > this.maxBytes) {\n return\n }\n\n const existing = this.entries.get(url)\n if (existing !== undefined) {\n this.entries.delete(url)\n this.currentBytes -= existing.bytes\n }\n\n this.entries.set(url, {\n body,\n status,\n expiresAt: this.now() + this.ttlFor(url),\n bytes,\n })\n this.currentBytes += bytes\n\n this.evictUntilWithinBudget()\n }\n\n private evictUntilWithinBudget(): void {\n while (this.currentBytes > this.maxBytes) {\n const oldestKey = this.entries.keys().next().value\n if (oldestKey === undefined) {\n return\n }\n const oldest = this.entries.get(oldestKey)\n this.entries.delete(oldestKey)\n if (oldest !== undefined) {\n this.currentBytes -= oldest.bytes\n }\n }\n }\n}\n","/* eslint-disable functype/prefer-either --\n * This module is the imperative-to-functional boundary for the Reddit HTTP client.\n * Each public method runs its failure-producing region inside a `Try` and converts the\n * result to `Either<RedditError, T>` via the total `classifyRedditError`. Because the body\n * of `Try.async(() => Promise<T>)` can only signal failure by throwing, the `throw`s here\n * (HTTP/validation/domain errors, and the validateWriteAccess/checkDuplicateContent helpers\n * they call) are local control-flow captured by that `Try` — they never escape the method\n * boundary. prefer-either's \"return Either.left\" suggestion does not apply inside a Try body.\n */\nimport crypto from \"crypto\"\nimport type { Either } from \"functype\"\nimport { Left, Option, Right, Try } from \"functype\"\n\nimport type {\n BotDisclosureConfig,\n ContentRecord,\n Page,\n RedditApiCommentResponse,\n RedditApiCommentTreeData,\n RedditApiEditResponse,\n RedditApiInfoResponse,\n RedditApiLinkFlairResponse,\n RedditApiListingResponse,\n RedditApiMeResponse,\n RedditApiMoreChildrenResponse,\n RedditApiPopularSubredditsResponse,\n RedditApiPostCommentsResponse,\n RedditApiPostData,\n RedditApiRulesResponse,\n RedditApiSubmitResponse,\n RedditApiSubredditResponse,\n RedditApiUserResponse,\n RedditAuthMode,\n RedditClientConfig,\n RedditComment,\n RedditFlair,\n RedditPost,\n RedditRule,\n RedditSubreddit,\n RedditUser,\n RetryConfig,\n SafeModeConfig,\n UserContent,\n} from \"../types\"\nimport { normalizeFullname, normalizeSubreddit, normalizeThingId, normalizeUsername } from \"../utils/reddit-identifiers\"\nimport type { RedditError } from \"./errors\"\nimport {\n ApiError,\n classifyRedditError,\n HttpError,\n isRedditError,\n NetworkBlockedError,\n NotAuthenticatedError,\n NotFoundError,\n ValidationError,\n} from \"./errors\"\nimport { ResponseCache } from \"./response-cache\"\n\n// Extract Reddit's pagination cursors from a listing's `data`. Reddit returns `after`/`before`\n// as a fullname string or null; we surface only present string cursors (no undefined keys, so\n// this is safe under exactOptionalPropertyTypes).\nfunction listingCursor(data: { readonly [key: string]: unknown }): Pick<Page<unknown>, \"after\" | \"before\"> {\n const after = typeof data.after === \"string\" ? { after: data.after } : {}\n const before = typeof data.before === \"string\" ? { before: data.before } : {}\n return { ...after, ...before }\n}\n\n// Read a response header defensively. Real fetch Responses always carry `headers`, but the\n// client is also driven by partial mocks in tests, so treat a missing bag as \"no header\".\nfunction headerValue(response: Response, name: string): string {\n // eslint-disable-next-line functype/prefer-option -- narrowing a lie in the DOM type, not modelling absence\n const headers = response.headers as Headers | undefined\n return headers?.get(name) ?? \"\"\n}\n\nfunction parsePostData(post: RedditApiPostData): RedditPost {\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: Boolean(post.edited),\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n}\n\nexport class RedditClient {\n private readonly clientId: string\n private readonly clientSecret: string\n private readonly userAgent: string\n private readonly username?: string\n private readonly password?: string\n private readonly baseUrl: string\n private readonly authMode: RedditAuthMode\n private readonly hasCredentials: boolean\n private readonly safeMode: SafeModeConfig\n private readonly botDisclosure: BotDisclosureConfig\n private readonly cache?: ResponseCache\n private readonly retry: RetryConfig\n\n // Mutable state — inherent to a stateful HTTP client with token refresh\n\n private accessToken?: string\n\n private tokenExpiry: number = 0\n\n private authenticated: boolean = false\n\n private lastWriteTime: number = 0\n\n private recentContentRecords: ContentRecord[] = []\n\n constructor(config: RedditClientConfig) {\n this.clientId = config.clientId\n this.clientSecret = config.clientSecret\n this.userAgent = config.userAgent\n this.username = config.username\n this.password = config.password\n this.authMode = config.authMode ?? \"auto\"\n this.hasCredentials = Boolean(this.clientId && this.clientSecret)\n this.baseUrl = this.determineBaseUrl()\n\n this.safeMode = config.safeMode ?? {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n\n this.botDisclosure = config.botDisclosure ?? { enabled: false, footer: \"\" }\n\n this.cache = config.cache?.enabled === true ? new ResponseCache({ maxBytes: config.cache.maxBytes }) : undefined\n\n this.retry = config.retry ?? { maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 60000 }\n }\n\n private determineBaseUrl(): string {\n switch (this.authMode) {\n case \"authenticated\":\n return \"https://oauth.reddit.com\"\n case \"anonymous\":\n return \"https://www.reddit.com\"\n case \"auto\":\n return this.hasCredentials ? \"https://oauth.reddit.com\" : \"https://www.reddit.com\"\n }\n }\n\n // Low-level HTTP boundary. Returns Either<Error, Response>: a Right even for non-ok HTTP\n // statuses (callers inspect response.ok); only thrown failures (network, auth) become Left.\n private async makeRequest(path: string, options: RequestInit = {}): Promise<Either<Error, Response>> {\n const attempt = await Try.async(async (): Promise<Response> => {\n const url = `${this.baseUrl}${path}`\n const method = (options.method ?? \"GET\").toUpperCase()\n const cacheable = this.cache !== undefined && method === \"GET\"\n\n if (cacheable) {\n const cached = this.cache!.get(url)\n if (cached !== undefined) {\n return new Response(cached.body, { status: cached.status })\n }\n }\n\n const requiresAuth = this.authMode === \"authenticated\" || (this.authMode === \"auto\" && this.hasCredentials)\n\n if (requiresAuth && (Date.now() >= this.tokenExpiry || !this.authenticated)) {\n const authResult = await this.authenticate()\n authResult.orThrow()\n }\n\n const headers: Record<string, string> = {\n \"User-Agent\": this.userAgent,\n\n ...(options.headers as Record<string, string> | undefined),\n }\n\n if (requiresAuth && this.accessToken !== undefined) {\n headers[\"Authorization\"] = `Bearer ${this.accessToken}`\n }\n\n const first = await this.fetchWithRetry(url, options, headers, path, 0)\n\n // 401 once-off re-auth, then retry the request (which itself honors 429 backoff).\n const response =\n first.status === 401 && this.authenticated\n ? await this.fetchWithRetry(url, options, { ...headers, Authorization: await this.reauthorize() }, path, 0)\n : first\n\n // Reddit network-blocks the unauthenticated JSON API from many IP ranges and answers with\n // an HTML block page. A private/quarantined subreddit also 403s, but does so with a JSON\n // body — so the content type is what separates \"this network is blocked\" from \"this\n // resource is closed\", and only the former is worth redirecting the user to OAuth.\n if (!requiresAuth && response.status === 403 && !headerValue(response, \"content-type\").includes(\"json\")) {\n throw new NetworkBlockedError(\n \"Reddit is blocking unauthenticated requests from this network (HTTP 403 with a block page). \" +\n \"Set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET to authenticate with OAuth, which also raises \" +\n \"the rate limit from ~10 to 60+ requests/min. See https://www.reddit.com/prefs/apps to create an app.\",\n )\n }\n\n // Cache successful read responses and return a fresh, readable Response.\n // (A fetch Response body can only be consumed once, so we re-wrap the text.)\n if (cacheable && response.ok) {\n const text = await response.text()\n this.cache!.set(url, text, response.status)\n return new Response(text, { status: response.status })\n }\n\n return response\n })\n\n return attempt.toEither((error) => error)\n }\n\n async authenticate(): Promise<Either<Error, void>> {\n if (this.authMode === \"anonymous\") {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n if (this.authMode === \"authenticated\" && !this.hasCredentials) {\n return Left(new Error(\"Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\"))\n }\n\n if (this.authMode === \"auto\" && !this.hasCredentials) {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n const attempt = await Try.async(async (): Promise<void> => {\n const now = Date.now()\n if (this.accessToken !== undefined && now < this.tokenExpiry) {\n return\n }\n\n const authUrl = \"https://www.reddit.com/api/v1/access_token\"\n const authData = new URLSearchParams()\n\n const { username } = this\n const { password } = this\n const isUserAuth = Boolean(username && password)\n if (isUserAuth && username !== undefined && password !== undefined) {\n authData.append(\"grant_type\", \"password\")\n authData.append(\"username\", username)\n authData.append(\"password\", password)\n } else {\n authData.append(\"grant_type\", \"client_credentials\")\n }\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString(\"base64\")\n const response = await fetch(authUrl, {\n method: \"POST\",\n headers: {\n \"User-Agent\": this.userAgent,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Authorization: `Basic ${credentials}`,\n },\n body: authData.toString(),\n })\n\n if (!response.ok) {\n const statusText = response.statusText !== \"\" ? response.statusText : \"Unknown Error\"\n throw new Error(`Authentication failed: ${response.status} ${statusText}`)\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number }\n this.accessToken = data.access_token\n this.tokenExpiry = now + data.expires_in * 1000\n this.authenticated = true\n })\n\n return attempt.toEither((error) => error)\n }\n\n async checkAuthentication(): Promise<boolean> {\n if (!this.authenticated) {\n const result = await this.authenticate()\n return result.isRight()\n }\n return true\n }\n\n private validateWriteAccess(): void {\n if (this.username === undefined || this.password === undefined) {\n if (this.authMode === \"anonymous\") {\n throw new NotAuthenticatedError(\n \"Write operations not available in anonymous mode. \" +\n \"Set REDDIT_USERNAME, REDDIT_PASSWORD and use 'auto' or 'authenticated' mode.\",\n )\n }\n throw new NotAuthenticatedError(\"Write operations require REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n }\n\n private async enforceWriteRateLimit(): Promise<void> {\n if (!this.safeMode.enabled || this.safeMode.writeDelayMs <= 0) {\n return\n }\n\n const now = Date.now()\n const elapsed = now - this.lastWriteTime\n if (elapsed < this.safeMode.writeDelayMs) {\n const waitTime = this.safeMode.writeDelayMs - elapsed\n console.error(`[SafeMode] Rate limit: waiting ${waitTime}ms before write operation`)\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n }\n this.lastWriteTime = Date.now()\n }\n\n private hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content.trim().toLowerCase()).digest(\"hex\")\n }\n\n private checkDuplicateContent(content: string, subreddit?: string): void {\n if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) {\n return\n }\n\n const hash = this.hashContent(content)\n\n const duplicate = this.recentContentRecords.find((record) => record.hash === hash)\n if (duplicate !== undefined) {\n if (subreddit !== undefined && duplicate.subreddit !== \"\" && subreddit !== duplicate.subreddit) {\n throw new ValidationError(\n \"Cross-subreddit duplicate detected. Reddit's Responsible Builder Policy prohibits \" +\n \"posting identical or substantially similar content across multiple subreddits. \" +\n \"Please create unique content for each subreddit.\",\n )\n }\n throw new ValidationError(\n \"Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. \" +\n \"Please modify your content and try again.\",\n )\n }\n\n this.recentContentRecords.push({\n hash,\n subreddit: subreddit ?? \"\",\n timestamp: Date.now(),\n })\n\n this.recentContentRecords = this.recentContentRecords.slice(-this.safeMode.maxRecentHashes)\n }\n\n // Re-authenticate and return a fresh Bearer header value (throws via orThrow on failure).\n private async reauthorize(): Promise<string> {\n const result = await this.authenticate()\n result.orThrow()\n return `Bearer ${this.accessToken}`\n }\n\n // Fetch with transparent retry on HTTP 429. Honors Retry-After / x-ratelimit-reset, else\n // exponential backoff; surfaces the 429 once retries are exhausted or the required wait\n // exceeds the cap. Recursive (not a loop) to satisfy the functional style.\n private async fetchWithRetry(\n url: string,\n options: RequestInit,\n headers: Record<string, string>,\n path: string,\n attempt: number,\n ): Promise<Response> {\n const response = await fetch(url, { ...options, headers })\n if (response.status !== 429 || attempt >= this.retry.maxRetries) {\n return response\n }\n\n const wait = this.retryAfterMs(response).fold(\n () => Math.min(this.retry.baseDelayMs * 2 ** attempt, this.retry.maxDelayMs),\n (ms) => ms,\n )\n if (wait > this.retry.maxDelayMs) {\n return response\n }\n\n console.error(`[RateLimit] 429 from ${path} — retry ${attempt + 1}/${this.retry.maxRetries} in ${wait}ms`)\n await new Promise((resolve) => setTimeout(resolve, wait))\n return this.fetchWithRetry(url, options, headers, path, attempt + 1)\n }\n\n // Parse a retry delay (ms) from a 429 response: prefer Retry-After (delta-seconds or\n // HTTP-date), then x-ratelimit-reset (seconds). None when no usable header is present,\n // signalling the caller to fall back to exponential backoff.\n private retryAfterMs(response: Response): Option<number> {\n const { headers } = response\n\n const retryAfter = headers.get(\"retry-after\")\n if (retryAfter !== null && retryAfter !== \"\") {\n const seconds = Number(retryAfter)\n if (!Number.isNaN(seconds)) {\n return Option(seconds * 1000)\n }\n const when = Date.parse(retryAfter)\n if (!Number.isNaN(when)) {\n return Option(Math.max(0, when - Date.now()))\n }\n }\n\n const reset = headers.get(\"x-ratelimit-reset\")\n if (reset !== null && reset !== \"\") {\n const seconds = Number(reset)\n if (!Number.isNaN(seconds)) {\n return Option(seconds * 1000)\n }\n }\n\n return Option.none()\n }\n\n private appendBotDisclosure(content: string): string {\n if (!this.botDisclosure.enabled || this.botDisclosure.footer === \"\") {\n return content\n }\n return `${content}${this.botDisclosure.footer}`\n }\n\n async getUser(username: string): Promise<Either<RedditError, RedditUser>> {\n const context = `Failed to get user info for ${username}`\n const attempt = await Try.async(async (): Promise<RedditUser> => {\n const response = (await this.makeRequest(`/user/${normalizeUsername(username)}/about.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiUserResponse\n const { data } = json\n\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma ?? data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n // Fetch + split a mixed user listing (saved/overview) into posts (t3) and comments (t1).\n private async getUserContent(path: string, context: string): Promise<Either<RedditError, UserContent>> {\n const attempt = await Try.async(async (): Promise<UserContent> => {\n const response = (await this.makeRequest(path)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData | RedditApiCommentTreeData>\n const posts = json.data.children\n .filter((child) => child.kind === \"t3\")\n .map((child) => parsePostData(child.data as RedditApiPostData))\n const comments = json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data as RedditApiCommentTreeData\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n parentId: comment.parent_id,\n }\n })\n return { posts, comments, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getMyOverview(\n options: { readonly limit?: number; readonly after?: string } = {},\n ): Promise<Either<RedditError, UserContent>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching your overview requires REDDIT_USERNAME\"))\n }\n const { limit = 25, after } = options\n const params = new URLSearchParams({ limit: limit.toString() })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n return this.getUserContent(\n `/user/${encodeURIComponent(this.username)}/overview.json?${params}`,\n \"Failed to get your overview\",\n )\n }\n\n async getMySaved(\n options: { readonly limit?: number; readonly after?: string } = {},\n ): Promise<Either<RedditError, UserContent>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching saved content requires REDDIT_USERNAME\"))\n }\n const { limit = 25, after } = options\n const params = new URLSearchParams({ limit: limit.toString() })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n return this.getUserContent(\n `/user/${encodeURIComponent(this.username)}/saved.json?${params}`,\n \"Failed to get saved content\",\n )\n }\n\n // The authenticated user's own account (requires user credentials — /api/v1/me needs identity).\n async getMe(): Promise<Either<RedditError, RedditUser>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching your account requires REDDIT_USERNAME\"))\n }\n const context = \"Failed to get authenticated user info\"\n const attempt = await Try.async(async (): Promise<RedditUser> => {\n const response = (await this.makeRequest(\"/api/v1/me\")).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const data = (await response.json()) as RedditApiMeResponse\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma ?? data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getSubredditInfo(subredditName: string): Promise<Either<RedditError, RedditSubreddit>> {\n const context = `Failed to get subreddit info for ${subredditName}`\n const attempt = await Try.async(async (): Promise<RedditSubreddit> => {\n const response = (await this.makeRequest(`/r/${normalizeSubreddit(subredditName)}/about.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubredditResponse\n const { data } = json\n\n return {\n displayName: data.display_name,\n title: data.title,\n description: data.description,\n publicDescription: data.public_description,\n subscribers: data.subscribers,\n activeUserCount: data.active_user_count ?? undefined,\n createdUtc: data.created_utc,\n over18: data.over18,\n subredditType: data.subreddit_type,\n url: data.url,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getSubredditRules(subreddit: string): Promise<Either<RedditError, readonly RedditRule[]>> {\n const context = `Failed to get rules for r/${subreddit}`\n const attempt = await Try.async(async (): Promise<readonly RedditRule[]> => {\n const response = (await this.makeRequest(`/r/${normalizeSubreddit(subreddit)}/about/rules.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiRulesResponse\n return json.rules.map((rule) => ({\n shortName: rule.short_name,\n description: rule.description,\n kind: rule.kind,\n violationReason: rule.violation_reason,\n priority: rule.priority,\n createdUtc: rule.created_utc,\n }))\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPostFlairs(subreddit: string): Promise<Either<RedditError, readonly RedditFlair[]>> {\n const context = `Failed to get post flairs for r/${subreddit}`\n const attempt = await Try.async(async (): Promise<readonly RedditFlair[]> => {\n const response = (await this.makeRequest(`/r/${normalizeSubreddit(subreddit)}/api/link_flair_v2.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiLinkFlairResponse\n return json.map((flair) => ({\n id: flair.id,\n text: flair.text,\n type: flair.type,\n textEditable: flair.text_editable,\n }))\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getTopPosts(\n subreddit: string,\n timeFilter: string = \"week\",\n limit: number = 10,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const params = new URLSearchParams({\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get top posts for ${subreddit !== \"\" ? subreddit : \"home\"}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const name = normalizeSubreddit(subreddit)\n const endpoint = name !== \"\" ? `/r/${name}/top.json` : \"/top.json\"\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to get top posts: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n const items = json.data.children.map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async browseSubreddit(\n subreddit: string,\n sort: string = \"hot\",\n timeFilter: string = \"week\",\n limit: number = 10,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const validSorts = [\"hot\", \"new\", \"top\", \"rising\", \"controversial\"]\n if (!validSorts.includes(sort)) {\n return Left(new ValidationError(`Invalid sort \"${sort}\". Valid options are: ${validSorts.join(\", \")}`))\n }\n\n const params = new URLSearchParams({ limit: limit.toString() })\n // The time filter only applies to top/controversial listings.\n if (sort === \"top\" || sort === \"controversial\") {\n params.set(\"t\", timeFilter)\n }\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const home = subreddit !== \"\" ? subreddit : \"home\"\n const context = `Failed to browse r/${home} (${sort})`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const name = normalizeSubreddit(subreddit)\n const endpoint = name !== \"\" ? `/r/${name}/${sort}.json` : `/${sort}.json`\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to browse r/${home}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n const items = json.data.children.map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPost(postId: string, subreddit?: string): Promise<Either<RedditError, RedditPost>> {\n const context = `Failed to get post with ID ${postId}`\n\n const attempt = await Try.async(async (): Promise<RedditPost> => {\n const id = normalizeThingId(postId)\n const endpoint = Option(subreddit).fold(\n () => `/api/info.json?id=t3_${id}`,\n (sr) => `/r/${normalizeSubreddit(sr)}/comments/${id}.json`,\n )\n const response = (await this.makeRequest(endpoint)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n if (subreddit !== undefined) {\n const json = (await response.json()) as [RedditApiListingResponse<RedditApiPostData>, unknown]\n return parsePostData(json[0].data.children[0].data)\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n if (json.data.children.length === 0) {\n throw new NotFoundError(`Post with ID ${postId} not found`)\n }\n return parsePostData(json.data.children[0].data)\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getTrendingSubreddits(limit: number = 5): Promise<Either<RedditError, readonly string[]>> {\n const params = new URLSearchParams({ limit: limit.toString() })\n const context = `Failed to get trending subreddits`\n\n const attempt = await Try.async(async (): Promise<readonly string[]> => {\n const response = (await this.makeRequest(`/subreddits/popular.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPopularSubredditsResponse\n return json.data.children.map((child) => child.data.display_name)\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async createPost(\n subreddit: string,\n title: string,\n content: string,\n isSelf: boolean = true,\n flairId?: string,\n flairText?: string,\n ): Promise<Either<RedditError, RedditPost>> {\n const attempt = await Try.async(async (): Promise<RedditPost> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(title + content, subreddit)\n\n const targetSubreddit = normalizeSubreddit(subreddit)\n if (targetSubreddit === \"\") {\n throw new ValidationError(\"A subreddit is required to create a post.\")\n }\n const finalContent = isSelf ? this.appendBotDisclosure(content) : content\n const kind = isSelf ? \"self\" : \"link\"\n const params = new URLSearchParams()\n params.append(\"sr\", targetSubreddit)\n params.append(\"kind\", kind)\n params.append(\"title\", title)\n params.append(isSelf ? \"text\" : \"url\", finalContent)\n params.append(\"api_type\", \"json\")\n if (flairId !== undefined) {\n params.append(\"flair_id\", flairId)\n }\n if (flairText !== undefined) {\n params.append(\"flair_text\", flairText)\n }\n\n const response = (\n await this.makeRequest(\"/api/submit\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to create post: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubmitResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n }\n\n const postId = json.json.data?.id ?? json.json.data?.name?.replace(\"t3_\", \"\")\n\n if (postId === undefined) {\n throw new ApiError(\"No post ID returned from Reddit\")\n }\n\n return (await this.getPost(postId, targetSubreddit)).orThrow()\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async checkPostExists(postId: string): Promise<boolean> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n const response = (await this.makeRequest(`/api/info.json?id=t3_${normalizeThingId(postId)}`)).orThrow()\n if (!response.ok) {\n return false\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n return json.data.children.length > 0\n })\n\n return attempt.orElse(false)\n }\n\n async replyToPost(postId: string, content: string): Promise<Either<RedditError, RedditComment>> {\n const attempt = await Try.async(async (): Promise<RedditComment> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(content)\n\n const finalContent = this.appendBotDisclosure(content)\n const fullThingId = normalizeFullname(postId, \"t3\")\n\n if (!fullThingId.startsWith(\"t1_\")) {\n const exists = await this.checkPostExists(normalizeThingId(postId))\n if (!exists) {\n throw new NotFoundError(`Post with ID ${postId} does not exist or is not accessible`)\n }\n }\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalContent)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/comment\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to reply: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiCommentResponse\n\n if (json.json.data?.things !== undefined && json.json.data.things.length > 0) {\n const commentData = json.json.data.things[0].data\n const author = this.username ?? \"[unknown]\"\n return {\n id: commentData.id,\n author,\n body: content,\n score: 1,\n controversiality: 0,\n subreddit: commentData.subreddit,\n submissionTitle: commentData.link_title ?? \"\",\n createdUtc: Date.now() / 1000,\n edited: false,\n isSubmitter: false,\n permalink: commentData.permalink,\n }\n } else if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n } else {\n throw new ApiError(\"Failed to parse reply response\")\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n private async deleteThing(thingId: string, defaultKind: \"t1\" | \"t3\"): Promise<Either<RedditError, boolean>> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n this.validateWriteAccess()\n\n const fullThingId = normalizeFullname(thingId, defaultKind)\n\n const params = new URLSearchParams()\n params.append(\"id\", fullThingId)\n\n const response = (\n await this.makeRequest(\"/api/del\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`)\n console.error(`[Reddit API] Error response: ${errorText}`)\n throw new HttpError(response.status, `HTTP ${response.status}: ${errorText}`)\n }\n\n console.error(`[Reddit API] Successfully deleted ${fullThingId}`)\n return true\n })\n\n return attempt.toEither((error) => {\n if (!isRedditError(error)) {\n console.error(`[Reddit API] Delete exception:`, error)\n }\n return classifyRedditError(error)\n })\n }\n\n async deletePost(thingId: string): Promise<Either<RedditError, boolean>> {\n return this.deleteThing(thingId, \"t3\")\n }\n\n async deleteComment(thingId: string): Promise<Either<RedditError, boolean>> {\n return this.deleteThing(thingId, \"t1\")\n }\n\n private async editThing(\n thingId: string,\n newText: string,\n defaultKind: \"t1\" | \"t3\",\n ): Promise<Either<RedditError, boolean>> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(newText)\n\n const finalText = this.appendBotDisclosure(newText)\n const fullThingId = normalizeFullname(thingId, defaultKind)\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalText)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/editusertext\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to edit: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiEditResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n }\n\n return true\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async editPost(thingId: string, newText: string): Promise<Either<RedditError, boolean>> {\n return this.editThing(thingId, newText, \"t3\")\n }\n\n async editComment(thingId: string, newText: string): Promise<Either<RedditError, boolean>> {\n return this.editThing(thingId, newText, \"t1\")\n }\n\n async searchReddit(\n query: string,\n options: {\n readonly subreddit?: string\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly type?: string\n readonly after?: string\n readonly before?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const { subreddit, sort = \"relevance\", timeFilter = \"all\", limit = 25, type = \"link\", after, before } = options\n const params = new URLSearchParams({\n q: query,\n sort,\n t: timeFilter,\n limit: limit.toString(),\n type,\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of native string | undefined into URLSearchParams init\n ...(subreddit !== undefined ? { restrict_sr: \"true\" } : {}),\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of cursors into URLSearchParams init\n ...(after !== undefined ? { after } : {}),\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of cursors into URLSearchParams init\n ...(before !== undefined ? { before } : {}),\n })\n const context = `Failed to search Reddit for: ${query}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const endpoint = Option(subreddit).fold(\n () => \"/search.json\",\n (sr) => `/r/${normalizeSubreddit(sr)}/search.json`,\n )\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to search Reddit: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const items = json.data.children.filter((child) => child.kind === \"t3\").map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPostComments(\n postId: string,\n subreddit: string,\n options: {\n readonly sort?: string\n readonly limit?: number\n } = {},\n ): Promise<Either<RedditError, { readonly post: RedditPost; readonly comments: readonly RedditComment[] }>> {\n const { sort = \"best\", limit = 100 } = options\n const params = new URLSearchParams({\n sort,\n limit: limit.toString(),\n })\n const context = `Failed to get comments for post ${postId}`\n\n const attempt = await Try.async(\n async (): Promise<{ readonly post: RedditPost; readonly comments: readonly RedditComment[] }> => {\n const response = (\n await this.makeRequest(\n `/r/${normalizeSubreddit(subreddit)}/comments/${normalizeThingId(postId)}.json?${params}`,\n )\n ).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to get comments: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPostCommentsResponse\n\n const postData = json[0].data.children[0].data\n const post = parsePostData(postData)\n\n const parseComments = (\n commentData: ReadonlyArray<{ readonly kind: string; readonly data: RedditApiCommentTreeData }>,\n depth: number = 0,\n ): readonly RedditComment[] =>\n commentData.flatMap((item) => {\n if (item.kind !== \"t1\" || item.data.body === undefined) return []\n\n const comment: RedditComment = {\n id: item.data.id,\n author: item.data.author,\n body: item.data.body,\n score: item.data.score,\n controversiality: item.data.controversiality,\n subreddit: item.data.subreddit,\n submissionTitle: post.title,\n createdUtc: item.data.created_utc,\n edited: Boolean(item.data.edited),\n isSubmitter: item.data.is_submitter,\n permalink: item.data.permalink,\n depth,\n parentId: item.data.parent_id,\n }\n\n const { replies } = item.data\n const childComments =\n replies !== undefined && typeof replies !== \"string\"\n ? parseComments(replies.data.children, depth + 1)\n : []\n\n return [comment, ...childComments]\n })\n\n const comments: readonly RedditComment[] = parseComments(json[1].data.children)\n\n return { post, comments }\n },\n )\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n // Expand \"load more\" comment stubs via /api/morechildren. `commentIds` are the ids from a\n // `more` node returned by getPostComments. Returns a flat list of the expanded comments.\n async getMoreComments(\n linkId: string,\n commentIds: readonly string[],\n ): Promise<Either<RedditError, readonly RedditComment[]>> {\n const context = `Failed to expand comments for ${linkId}`\n\n const attempt = await Try.async(async (): Promise<readonly RedditComment[]> => {\n const params = new URLSearchParams({\n api_type: \"json\",\n link_id: normalizeFullname(linkId, \"t3\"),\n children: commentIds.map((id) => normalizeThingId(id)).join(\",\"),\n })\n const response = (await this.makeRequest(`/api/morechildren?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiMoreChildrenResponse\n const things = json.json.data?.things ?? []\n return things\n .filter((thing) => thing.kind === \"t1\" && thing.data.body !== undefined)\n .map((thing) => {\n const comment = thing.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n parentId: comment.parent_id,\n }\n })\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getUserPosts(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly after?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25, after } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get posts for user ${username}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (\n await this.makeRequest(`/user/${normalizeUsername(username)}/submitted.json?${params}`)\n ).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const items = json.data.children.filter((child) => child.kind === \"t3\").map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getUserComments(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly after?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditComment>>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25, after } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get comments for user ${username}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditComment>> => {\n const response = (\n await this.makeRequest(`/user/${normalizeUsername(username)}/comments.json?${params}`)\n ).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiCommentTreeData>\n\n const items = json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n }\n })\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n}\n\n// Create and export singleton instance\nconst clientHolder: { instance: Option<RedditClient> } = { instance: Option.none() }\n\nexport function initializeRedditClient(config: RedditClientConfig): RedditClient {\n const client = new RedditClient(config)\n\n clientHolder.instance = Option(client)\n return client\n}\n\nexport function getRedditClient(): Option<RedditClient> {\n return clientHolder.instance\n}\n","import { Option, Try } from \"functype\"\n\nimport type {\n FormattedCommentInfo,\n FormattedPostInfo,\n FormattedSubredditInfo,\n FormattedUserInfo,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n} from \"../types\"\n\nexport function formatTimestamp(timestamp: number): string {\n return Try(() => {\n const date = new Date(timestamp * 1000)\n return date\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \" UTC\")\n }).orElse(String(timestamp))\n}\n\nexport function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const insights: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Primarily a commenter, highly engaged in discussions\"]\n : karmaRatio < 0.2\n ? [\"Content creator, focuses on sharing posts\"]\n : [\"Balanced participation in both posting and commenting\"]),\n ...(accountAgeDays < 30\n ? [\"New user, still exploring Reddit\"]\n : accountAgeDays > 365 * 5\n ? [\"Long-time Redditor with extensive platform experience\"]\n : []),\n ...(isMod ? [\"Community leader who helps maintain subreddit quality\"] : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzePostEngagement(score: number, ratio: number, numComments: number): string {\n const insights: readonly string[] = [\n ...(score > 1000 && ratio > 0.95\n ? [\"Highly successful post with strong community approval\"]\n : score > 100 && ratio > 0.8\n ? [\"Well-received post with good engagement\"]\n : ratio < 0.5\n ? [\"Controversial post that sparked debate\"]\n : []),\n ...(numComments > 100\n ? [\"Generated significant discussion\"]\n : numComments > score * 0.5\n ? [\"Highly discussable content with active comment section\"]\n : numComments === 0\n ? [\"Yet to receive community interaction\"]\n : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzeSubredditHealth(subscribers: number, activeUsers: Option<number>, ageDays: number): string {\n const sizeInsights: readonly string[] =\n subscribers > 1000000\n ? [\"Major subreddit with massive following\"]\n : subscribers > 100000\n ? [\"Well-established community\"]\n : subscribers < 1000\n ? [\"Niche community, potential for growth\"]\n : []\n\n const activityInsights: readonly string[] = activeUsers\n .map((active) => active / subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1\n ? [\"Highly active community with strong engagement\"]\n : activityRatio < 0.01\n ? [\"Could benefit from more community engagement initiatives\"]\n : [],\n )\n\n const ageInsights: readonly string[] =\n ageDays > 365 * 5\n ? [\"Mature subreddit with established culture\"]\n : ageDays < 90\n ? [\"New subreddit still forming its community\"]\n : []\n\n return [...sizeInsights, ...activityInsights, ...ageInsights].join(\"\\n - \")\n}\n\nexport function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const recommendations: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Consider creating more posts to share your expertise\"]\n : karmaRatio < 0.2\n ? [\"Engage more in discussions to build community connections\"]\n : []),\n ...(accountAgeDays < 30\n ? [\"Explore popular subreddits in your areas of interest\", \"Read community guidelines before posting\"]\n : []),\n ...(isMod ? [\"Share moderation insights with other community leaders\"] : []),\n ]\n\n return recommendations.length > 0 ? recommendations.join(\"\\n - \") : \"Maintain your balanced engagement across Reddit\"\n}\n\nexport function getBestEngagementTime(createdUtc: number): string {\n const postHour = new Date(createdUtc * 1000).getHours()\n\n if (14 <= postHour && postHour <= 18) {\n return \"Posted during peak engagement hours (2 PM - 6 PM), good timing!\"\n } else if (23 <= postHour || postHour <= 5) {\n return \"Consider posting during more active hours (morning to evening)\"\n } else {\n return \"Posted during moderate activity hours, timing could be optimized\"\n }\n}\n\nexport function getSubredditEngagementTips(subreddit: RedditSubreddit): string {\n const sizeTips: readonly string[] =\n subreddit.subscribers > 1000000\n ? [\"Post during peak hours for maximum visibility\", \"Ensure content is highly polished due to high competition\"]\n : subreddit.subscribers < 1000\n ? [\"Engage actively to help grow the community\", \"Consider cross-posting to related larger subreddits\"]\n : []\n\n const activityTips: readonly string[] = Option(subreddit.activeUserCount)\n .map((active) => active / subreddit.subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1 ? [\"Quick responses recommended due to high activity\"] : ([] as readonly string[]),\n )\n\n const allTips = [...sizeTips, ...activityTips]\n return allTips.length > 0 ? allTips.join(\"\\n - \") : \"Regular engagement recommended to maintain community presence\"\n}\n\nexport function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {\n const insights: readonly string[] = [\n ...(score > 100\n ? [\"Highly upvoted comment with significant community agreement\"]\n : score < 0\n ? [\"Controversial or contested viewpoint\"]\n : []),\n ...(isEdited ? [\"Refined for clarity or accuracy\"] : []),\n ...(isOp ? [\"Author's perspective adds context to original post\"] : []),\n ]\n\n return insights.length > 0 ? insights.join(\"\\n - \") : \"Standard engagement with discussion\"\n}\n\nexport function formatUserInfo(user: RedditUser): FormattedUserInfo {\n const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600)\n const karmaRatio = user.commentKarma / (user.linkKarma === 0 ? 1 : user.linkKarma)\n\n const status: readonly string[] = [\n ...(user.isMod ? [\"Moderator\"] : []),\n ...(user.isGold ? [\"Reddit Gold Member\"] : []),\n ...(user.isEmployee ? [\"Reddit Employee\"] : []),\n ]\n\n return {\n username: user.name,\n karma: {\n commentKarma: user.commentKarma,\n postKarma: user.linkKarma,\n totalKarma: user.totalKarma,\n },\n accountStatus: status.length > 0 ? status : [\"Regular User\"],\n accountCreated: formatTimestamp(user.createdUtc),\n profileUrl: user.profileUrl,\n activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),\n recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),\n }\n}\n\nexport function formatPostInfo(post: RedditPost): FormattedPostInfo {\n const contentType = post.isSelf ? \"Text Post\" : \"Link Post\"\n const content = post.isSelf ? (post.selftext ?? \"\") : (post.url ?? \"\")\n\n const flags: readonly string[] = [\n ...(post.over18 ? [\"NSFW\"] : []),\n ...(post.spoiler === true ? [\"Spoiler\"] : []),\n ...(post.edited ? [\"Edited\"] : []),\n ]\n\n return {\n title: post.title,\n type: contentType,\n content: content.length > 300 ? `${content.substring(0, 297)}...` : content,\n author: post.author,\n subreddit: post.subreddit,\n stats: {\n score: post.score,\n upvoteRatio: post.upvoteRatio,\n comments: post.numComments,\n },\n metadata: {\n posted: formatTimestamp(post.createdUtc),\n flags,\n flair: post.linkFlairText ?? \"None\",\n },\n links: {\n fullPost: `https://reddit.com${post.permalink}`,\n shortLink: `https://redd.it/${post.id}`,\n },\n engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),\n bestTimeToEngage: getBestEngagementTime(post.createdUtc),\n }\n}\n\nexport function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {\n const flags: readonly string[] = [\n ...(subreddit.over18 ? [\"NSFW\"] : []),\n ...Option(subreddit.subredditType).fold(\n () => [] as readonly string[],\n (type) => [`Type: ${type}`] as readonly string[],\n ),\n ]\n\n const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)\n\n return {\n name: subreddit.displayName,\n title: subreddit.title,\n stats: {\n subscribers: subreddit.subscribers,\n activeUsers: Option(subreddit.activeUserCount).fold(\n () => \"Unknown\" as number | string,\n (count) => count as number | string,\n ),\n },\n description: {\n short: subreddit.publicDescription,\n full:\n subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description,\n },\n metadata: {\n created: formatTimestamp(subreddit.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n links: {\n subreddit: `https://reddit.com${subreddit.url}`,\n wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,\n },\n communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, Option(subreddit.activeUserCount), ageDays),\n engagementTips: getSubredditEngagementTips(subreddit),\n }\n}\n\nexport function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {\n const flags: readonly string[] = [...(comment.edited ? [\"Edited\"] : []), ...(comment.isSubmitter ? [\"OP\"] : [])]\n\n return {\n author: comment.author,\n content: comment.body.length > 300 ? `${comment.body.substring(0, 297)}...` : comment.body,\n stats: {\n score: comment.score,\n controversiality: comment.controversiality,\n },\n context: {\n subreddit: comment.subreddit,\n thread: comment.submissionTitle,\n },\n metadata: {\n posted: formatTimestamp(comment.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n link: `https://reddit.com${comment.permalink}`,\n commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),\n }\n}\n\n// Simple formatter for posts (used in search and comment tools)\nexport function formatPost(post: RedditPost) {\n return {\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n score: post.score,\n upvoteRatio: Math.round(post.upvoteRatio * 100),\n numComments: post.numComments,\n createdAt: formatTimestamp(post.createdUtc),\n selftext: post.selftext,\n permalink: post.permalink,\n nsfw: post.over18,\n spoiler: post.spoiler,\n }\n}\n","import crypto from \"crypto\"\nimport dotenv from \"dotenv\"\nimport { FastMCP } from \"fastmcp\"\nimport { Option } from \"functype\"\nimport { z } from \"zod\"\n\nimport { getRedditClient, initializeRedditClient } from \"./client/reddit-client\"\nimport type {\n BotDisclosureConfig,\n CacheConfig,\n RedditAuthMode,\n RedditSafeMode,\n RetryConfig,\n SafeModeConfig,\n UserContent,\n} from \"./types\"\nimport { formatPostInfo, formatSubredditInfo, formatUserInfo } from \"./utils/formatters\"\n\n// Load environment variables\ndotenv.config({ quiet: true })\n\n// Version injected at build time by tsdown\ndeclare const __VERSION__: string\nconst VERSION = (typeof __VERSION__ !== \"undefined\" ? __VERSION__ : \"0.0.0-dev\") as `${number}.${number}.${number}`\n\n// User-Agent validation and building\nfunction validateUserAgent(userAgent: string, username?: string): void {\n const recommendedPattern = /^[\\w-]+:[\\w-]+:[\\d.]+ \\(by \\/u\\/\\w+\\)$/\n if (!recommendedPattern.test(userAgent)) {\n console.error(\"[Warning] User-Agent does not follow Reddit's recommended format\")\n console.error(\"[Warning] Recommended: 'platform:app_id:version (by /u/username)'\")\n console.error(\"[Warning] Non-standard User-Agents may increase ban risk\")\n if (username !== undefined) {\n console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`)\n }\n }\n}\n\nfunction buildUserAgent(customAgent?: string, username?: string): string {\n if (customAgent !== undefined) {\n validateUserAgent(customAgent, username)\n return customAgent\n }\n\n if (username !== undefined) {\n const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`\n console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`)\n return autoAgent\n }\n\n const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`\n console.error(\n \"[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.\",\n )\n return fallbackAgent\n}\n\n// Safe mode configuration\nfunction buildSafeModeConfig(safeMode: RedditSafeMode): SafeModeConfig {\n switch (safeMode) {\n case \"off\":\n return {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n case \"standard\":\n return {\n enabled: true,\n mode: \"standard\",\n writeDelayMs: 2000,\n duplicateCheck: true,\n maxRecentHashes: 10,\n }\n case \"strict\":\n return {\n enabled: true,\n mode: \"strict\",\n writeDelayMs: 5000,\n duplicateCheck: true,\n maxRecentHashes: 20,\n }\n }\n}\n\nfunction unwrapClient() {\n return getRedditClient().orThrow(new Error(\"Reddit client not initialized\"))\n}\n\n// Footer appended to paginated listings when more results are available.\nfunction nextPageHint(after?: string): string {\n return Option(after).fold(\n () => \"\",\n (cursor) => `\\n\\n---\\nMore results available — call again with after=\"${cursor}\" for the next page.`,\n )\n}\n\n// Render a mixed posts+comments listing (saved / overview).\nfunction formatUserContent(heading: string, content: UserContent): string {\n const postsSection =\n content.posts.length === 0\n ? \"\"\n : `## Posts (${content.posts.length})\\n${content.posts\n .map(\n (post, index) =>\n `${index + 1}. ${post.title} — r/${post.subreddit}, score ${post.score.toLocaleString()} — https://reddit.com${post.permalink}`,\n )\n .join(\"\\n\")}\\n\\n`\n\n const commentsSection =\n content.comments.length === 0\n ? \"\"\n : `## Comments (${content.comments.length})\\n${content.comments\n .map((comment, index) => {\n const body = comment.body.length > 200 ? `${comment.body.substring(0, 200)}...` : comment.body\n return `${index + 1}. in r/${comment.subreddit}: ${body} — https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\")}\\n\\n`\n\n const empty = content.posts.length === 0 && content.comments.length === 0 ? \"No items found.\\n\\n\" : \"\"\n\n return `# ${heading}\\n\\n${postsSection}${commentsSection}${empty}`.trimEnd() + nextPageHint(content.after)\n}\n\n// Initialize Reddit client\nasync function setupRedditClient() {\n const clientId = process.env.REDDIT_CLIENT_ID\n const clientSecret = process.env.REDDIT_CLIENT_SECRET\n const customUserAgent = process.env.REDDIT_USER_AGENT\n const username = process.env.REDDIT_USERNAME\n const password = process.env.REDDIT_PASSWORD\n const authMode = (process.env.REDDIT_AUTH_MODE ?? \"auto\") as RedditAuthMode\n const safeMode = (process.env.REDDIT_SAFE_MODE ?? \"standard\") as RedditSafeMode\n\n // Validate auth mode\n if (![\"auto\", \"authenticated\", \"anonymous\"].includes(authMode)) {\n console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`)\n console.error(\"[Error] Valid options are: auto, authenticated, anonymous\")\n process.exit(1)\n }\n\n // Validate safe mode\n if (![\"off\", \"standard\", \"strict\"].includes(safeMode)) {\n console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`)\n console.error(\"[Error] Valid options are: off, standard, strict\")\n process.exit(1)\n }\n\n // In authenticated mode, require credentials\n if (authMode === \"authenticated\" && (clientId === undefined || clientSecret === undefined)) {\n console.error(\"[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n // For auto/anonymous, credentials are optional\n const hasCredentials = Boolean(clientId && clientSecret)\n\n // Build user-agent (auto-format with username if available)\n const userAgent = buildUserAgent(customUserAgent, username)\n\n // Build safe mode config\n const safeModeConfig = buildSafeModeConfig(safeMode)\n\n // Build bot disclosure config\n const botDisclosureMode = process.env.REDDIT_BOT_DISCLOSURE ?? \"off\"\n const defaultFooter =\n \"\\n\\n---\\n^(🤖 I am a bot | Built with) [^reddit-mcp-server](https://github.com/jordanburke/reddit-mcp-server)\"\n const botDisclosureConfig: BotDisclosureConfig = {\n enabled: botDisclosureMode === \"auto\",\n footer: botDisclosureMode === \"auto\" ? (process.env.REDDIT_BOT_FOOTER ?? defaultFooter) : \"\",\n }\n\n // Build cache config (enabled by default to ease Reddit rate limits; opt out with REDDIT_CACHE=off)\n const cacheEnabled = (process.env.REDDIT_CACHE ?? \"on\") !== \"off\"\n const cacheMaxMb = Number(process.env.REDDIT_CACHE_MAX_MB ?? \"50\")\n const cacheConfig: CacheConfig = {\n enabled: cacheEnabled,\n maxBytes: (Number.isFinite(cacheMaxMb) && cacheMaxMb > 0 ? cacheMaxMb : 50) * 1024 * 1024,\n }\n\n // Retry on HTTP 429 with Retry-After backoff (opt out with REDDIT_MAX_RETRIES=0)\n const maxRetriesRaw = Number(process.env.REDDIT_MAX_RETRIES ?? \"3\")\n const retryConfig: RetryConfig = {\n maxRetries: Number.isFinite(maxRetriesRaw) && maxRetriesRaw >= 0 ? Math.floor(maxRetriesRaw) : 3,\n baseDelayMs: 1000,\n maxDelayMs: 60_000,\n }\n\n const client = initializeRedditClient({\n clientId: clientId ?? \"\",\n clientSecret: clientSecret ?? \"\",\n userAgent,\n username,\n password,\n authMode,\n safeMode: safeModeConfig,\n botDisclosure: botDisclosureConfig,\n cache: cacheConfig,\n retry: retryConfig,\n })\n\n console.error(\"[Setup] Reddit client initialized\")\n console.error(`[Setup] Authentication mode: ${authMode}`)\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] Using anonymous Reddit API (~10 req/min)\")\n console.error(\"[Setup] No authentication required - ready to use!\")\n } else {\n console.error(\"[Setup] Testing Reddit API connection...\")\n const isConnected = await client.checkAuthentication()\n\n if (!isConnected) {\n console.error(\"[Error] ✗ Failed to connect to Reddit API\")\n console.error(\"[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n console.error(\"[Setup] ✓ Reddit API connection successful\")\n console.error(\"[Setup] Using OAuth Reddit API (60-100 req/min)\")\n }\n\n if (username !== undefined && password !== undefined) {\n console.error(`[Setup] ✓ User authenticated as: ${username}`)\n console.error(\"[Setup] Write operations enabled (posting, replying, editing, deleting)\")\n } else {\n console.error(\"[Setup] Read-only mode (no user credentials)\")\n console.error(\"[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n\n // Log safe mode status\n if (safeModeConfig.enabled) {\n console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`)\n console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`)\n console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`)\n } else {\n console.error(\n \"[Setup] Safe mode: off (explicitly disabled — ensure compliance with Reddit's Responsible Builder Policy)\",\n )\n }\n\n // Log bot disclosure status\n if (botDisclosureConfig.enabled) {\n console.error(\"[Setup] ✓ Bot disclosure: enabled (automated content will include bot footer)\")\n } else {\n console.error(\"[Setup] Bot disclosure: off\")\n console.error(\"[Setup] For Reddit policy compliance, consider REDDIT_BOT_DISCLOSURE=auto\")\n }\n}\n\n// OAuth token: generate once at startup, never expose in responses\nconst oauthToken = process.env.OAUTH_TOKEN ?? crypto.randomBytes(32).toString(\"hex\")\nif (process.env.OAUTH_ENABLED === \"true\" && process.env.OAUTH_TOKEN === undefined) {\n console.error(`[Auth] Generated OAuth token: ${oauthToken}`)\n}\n\n// Create FastMCP server\nconst server = new FastMCP({\n name: \"reddit-mcp-server\",\n version: VERSION,\n instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.\n\nAvailable capabilities:\n- Fetch Reddit posts, comments, and user information\n- Get subreddit details and statistics\n- Search Reddit content across posts and subreddits\n- Create posts and reply to posts/comments (with authentication)\n- Edit your own posts and comments (with authentication)\n- Delete your own posts and comments (with authentication)\n- Analyze engagement metrics and community insights\n\nFor write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.\n\nIMPORTANT - Reddit Responsible Builder Policy compliance:\n- Data retrieved via these tools must NOT be used for AI model training without Reddit's written approval\n- Data must NOT be sold, licensed, or commercially redistributed\n- Do NOT attempt to de-anonymize or re-identify Reddit users\n- Do NOT post identical or substantially similar content across multiple subreddits\n- Do NOT use these tools to manipulate votes, karma, or circumvent Reddit safety mechanisms\n- All bot-generated content must clearly disclose its automated nature\n- Bots must NOT send private/direct messages without explicit user consent\nFor details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy`,\n\n // Optional OAuth configuration for HTTP transport\n ...(process.env.OAUTH_ENABLED === \"true\" && {\n authenticate: (request: { readonly headers: { readonly authorization?: string } }) => {\n const authHeader = request.headers.authorization\n if (!authHeader?.startsWith(\"Bearer \")) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 401,\n statusText: \"Missing or invalid Authorization header\",\n })\n }\n\n const token = authHeader.slice(7)\n const tokenBuffer = Buffer.from(token)\n const expectedBuffer = Buffer.from(oauthToken)\n const tokenHash = crypto.createHash(\"sha256\").update(tokenBuffer).digest()\n const expectedHash = crypto.createHash(\"sha256\").update(expectedBuffer).digest()\n if (!crypto.timingSafeEqual(tokenHash, expectedHash)) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 403,\n statusText: \"Invalid token\",\n })\n }\n\n return Promise.resolve({ authenticated: true })\n },\n }),\n})\n\n// Test tool\nserver.addTool({\n name: \"test_reddit_mcp_server\",\n description:\n 'Health check for the Reddit MCP server. Read-only and side-effect-free — inspects local configuration only and makes no Reddit API calls. Returns the server version, whether the Reddit client is initialized, whether OAuth credentials are present, and whether write access (REDDIT_USERNAME/REDDIT_PASSWORD) is configured. Use this first to diagnose setup/auth problems. Do NOT use it to check Reddit\\'s own status or connectivity — it never contacts Reddit. A \"✗ Write Access\" result means the write tools (create_post, reply_to_post, edit_*, delete_*) will fail.',\n annotations: {\n title: \"Test Reddit MCP Server\",\n readOnlyHint: true,\n openWorldHint: false,\n },\n parameters: z.object({}),\n execute: () => {\n const client = getRedditClient()\n const hasAuth = client.fold(\n () => \"✗\",\n () => \"✓\",\n )\n const hasWriteAccess =\n process.env.REDDIT_USERNAME !== undefined && process.env.REDDIT_PASSWORD !== undefined ? \"✓\" : \"✗\"\n\n return Promise.resolve(`Reddit MCP Server Status:\n- Server: ✓ Running\n- Reddit Client: ${hasAuth} ${client.fold(\n () => \"Not initialized\",\n () => \"Initialized\",\n )}\n- Write Access: ${hasWriteAccess} ${hasWriteAccess === \"✓\" ? \"Available\" : \"Read-only mode\"}\n- Version: ${VERSION}\n\nReady to handle Reddit API requests!`)\n },\n})\n\n// User tools\nserver.addTool({\n name: \"get_user_info\",\n description:\n \"Get a public profile for any Reddit user: comment/post/total karma, account age and status flags, plus a short activity analysis and engagement tips. Read-only; works in anonymous mode. Returns profile stats only — use get_user_posts / get_user_comments for their actual content. Use get_me instead for your own authenticated account; do NOT expect private fields here, as only public data is returned.\",\n annotations: {\n title: \"Get User Info\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n username: z\n .string()\n .describe(\"The target user's Reddit username, without the u/ prefix (e.g. 'spez', not 'u/spez').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUser(args.username)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user info: ${err.message}`)\n },\n (user) => {\n const formattedUser = formatUserInfo(user)\n\n return `# User Information: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}\n\n## Activity Analysis\n- ${formattedUser.activityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Recommendations\n- ${formattedUser.recommendations.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_me\",\n description:\n \"Get the authenticated user's own profile (karma, account age, status flags). Read-only, but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD) and fails in anonymous mode. Use this instead of get_user_info when you need the current account rather than an arbitrary user. Do NOT use it to look up other users — it always returns the logged-in account.\",\n annotations: {\n title: \"Get My Account\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({}),\n execute: async () => {\n const client = unwrapClient()\n\n const result = await client.getMe()\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get authenticated user: ${err.message}`)\n },\n (user) => {\n const formattedUser = formatUserInfo(user)\n\n return `# Your Account: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_my_overview\",\n description:\n \"Get the authenticated user's own recent activity — posts and comments interleaved, newest first. Read-only but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD). Returns up to `limit` items plus an `after` cursor for the next page. Use get_my_saved for saved items, or get_user_posts / get_user_comments for another user. Do NOT use this to fetch a specific post's thread — use get_post_comments.\",\n annotations: {\n title: \"Get My Overview\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n limit: z.number().min(1).max(100).default(25).describe(\"How many activity items to return, 1–100 (default 25).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value returned by a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMyOverview({ limit: args.limit, after: args.after })\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get your overview: ${err.message}`)\n },\n (content) => formatUserContent(\"Your Overview\", content),\n )\n },\n})\n\nserver.addTool({\n name: \"get_my_saved\",\n description:\n \"Get the authenticated user's saved posts and comments (private to the account). Read-only but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD). Returns up to `limit` items plus an `after` pagination cursor. Use get_my_overview for your authored activity. Do NOT use this for another user — saved items are private and have no cross-user equivalent.\",\n annotations: {\n title: \"Get My Saved\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n limit: z.number().min(1).max(100).default(25).describe(\"How many saved items to return, 1–100 (default 25).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value returned by a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMySaved({ limit: args.limit, after: args.after })\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get saved content: ${err.message}`)\n },\n (content) => formatUserContent(\"Your Saved Content\", content),\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_posts\",\n description:\n \"Get posts submitted by a specific user, with sort (new/hot/top) and time filter. Read-only; works anonymously. Returns a page of posts (title, subreddit, score, upvote ratio, comment count, permalink) plus an `after` cursor for paging. Use get_user_comments for their comments, or get_user_info for karma/profile stats. Do NOT use this to search a subreddit — use search_reddit or browse_subreddit.\",\n annotations: {\n title: \"Get User Posts\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n username: z.string().describe(\"The author's Reddit username, without the u/ prefix (e.g. 'spez').\"),\n sort: z\n .enum([\"new\", \"hot\", \"top\"])\n .default(\"new\")\n .describe(\n \"Ordering: 'new' (most recent), 'hot' (currently active), or 'top' (highest score within `time_filter`). Default 'new'.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time window for scoring; only applies when sort='top'. Ignored for 'new'/'hot'. Default 'all'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many posts to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserPosts(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user posts: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n return `No posts found for u/${args.username} with the specified filters.`\n }\n\n const postSummaries = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_comments\",\n description:\n \"Get comments made by a specific user, with sort (new/hot/top) and time filter. Read-only; works anonymously. Returns a page of comments (subreddit, parent post title, body excerpt, score, permalink) plus an `after` cursor. Use get_user_posts for their submissions, or get_user_info for karma/profile stats. Do NOT use this to read one post's thread — use get_post_comments.\",\n annotations: {\n title: \"Get User Comments\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n username: z.string().describe(\"The author's Reddit username, without the u/ prefix (e.g. 'spez').\"),\n sort: z\n .enum([\"new\", \"hot\", \"top\"])\n .default(\"new\")\n .describe(\n \"Ordering: 'new' (most recent), 'hot' (currently active), or 'top' (highest score within `time_filter`). Default 'new'.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time window for scoring; only applies when sort='top'. Ignored for 'new'/'hot'. Default 'all'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many comments to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserComments(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user comments: ${err.message}`)\n },\n (page) => {\n const comments = page.items\n if (comments.length === 0) {\n return `No comments found for u/${args.username} with the specified filters.`\n }\n\n const commentSummaries = comments\n .map((comment, index) => {\n const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n\n return `### ${index + 1}. Comment ${flags.join(\" \")}\nIn r/${comment.subreddit} on \"${comment.submissionTitle}\"\n\n> ${truncatedBody}\n\n- Score: ${comment.score.toLocaleString()}\n- Posted: ${new Date(comment.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${commentSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Post tools\nserver.addTool({\n name: \"get_reddit_post\",\n description:\n \"Get a single post by subreddit + post id: title, author, self-text or link content, score, upvote ratio, comment count, flair/flags, and an engagement analysis. Read-only; works anonymously. Returns the post only — use get_post_comments for its comment thread. Do NOT use this to list a subreddit's posts (use browse_subreddit / get_top_posts) or to find posts by keyword (use search_reddit).\",\n annotations: {\n title: \"Get Reddit Post\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit the post lives in, without the r/ prefix (e.g. 'programming').\"),\n post_id: z\n .string()\n .describe(\n \"Base36 post id — the segment after /comments/ in a permalink like reddit.com/r/<sub>/comments/<post_id>/... (e.g. '1abc23'). With or without a t3_ prefix.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getPost(args.post_id, args.subreddit)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post from r/${formattedPost.subreddit}\n\n## Post Details\n- Title: ${formattedPost.title}\n- Type: ${formattedPost.type}\n- Author: u/${formattedPost.author}\n\n## Content\n${formattedPost.content}\n\n## Stats\n- Score: ${formattedPost.stats.score.toLocaleString()}\n- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%\n- Comments: ${formattedPost.stats.comments.toLocaleString()}\n\n## Metadata\n- Posted: ${formattedPost.metadata.posted}\n- Flags: ${formattedPost.metadata.flags.length > 0 ? formattedPost.metadata.flags.join(\", \") : \"None\"}\n- Flair: ${formattedPost.metadata.flair}\n\n## Links\n- Full Post: ${formattedPost.links.fullPost}\n- Short Link: ${formattedPost.links.shortLink}\n\n## Engagement Analysis\n- ${formattedPost.engagementAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Best Time to Engage\n${formattedPost.bestTimeToEngage}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_top_posts\",\n description:\n \"Get the top-scoring posts from a subreddit — or from the authenticated home feed if no subreddit is given — within a time window (hour…all). Read-only; works anonymously. Returns a page of posts (title, author, score, upvote ratio, comments, link) plus an `after` cursor. This is a shortcut for the 'top' sort; use browse_subreddit for hot/new/rising/controversial, or search_reddit to find posts by keyword.\",\n annotations: {\n title: \"Get Top Posts\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z\n .string()\n .optional()\n .describe(\n \"Subreddit to read, without the r/ prefix (e.g. 'science'). Omit to use the authenticated home feed (requires credentials).\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time window the 'top' ranking is computed over (e.g. 'day' = top today). Default 'week'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many posts to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getTopPosts(args.subreddit ?? \"\", args.time_filter, args.limit, args.after)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get top posts: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n const location = Option(args.subreddit).fold(\n () => \"home feed\",\n (sr) => `r/${sr}`,\n )\n return `No posts found in ${location} for the specified time period.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const location = Option(args.subreddit).fold(\n () => \"Home Feed\",\n (sr) => `r/${sr}`,\n )\n return `# Top Posts from ${location} (${args.time_filter})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"browse_subreddit\",\n description:\n \"Browse a subreddit — or the authenticated home feed when no subreddit is given — by sort order: hot, new, top, rising, or controversial. Read-only; works anonymously. `time_filter` applies only to the top and controversial sorts. Returns a page of posts (title, author, score, upvote ratio, comments, link) plus an `after` cursor. Use get_top_posts as a shortcut for the top sort, or search_reddit to find posts by keyword rather than by feed order.\",\n annotations: {\n title: \"Browse Subreddit\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z\n .string()\n .optional()\n .describe(\n \"Subreddit to browse, without the r/ prefix (e.g. 'news'). Omit to use the authenticated home feed (requires credentials).\",\n ),\n sort: z\n .enum([\"hot\", \"new\", \"top\", \"rising\", \"controversial\"])\n .default(\"hot\")\n .describe(\n \"Feed ordering: 'hot' (default), 'new', 'rising', 'top', or 'controversial'. 'top'/'controversial' honor `time_filter`.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time window; only applies to sort='top' or 'controversial'. Ignored otherwise. Default 'week'.\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many posts to return, 1–100 (default 10).\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.browseSubreddit(\n args.subreddit ?? \"\",\n args.sort,\n args.time_filter,\n args.limit,\n args.after,\n )\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to browse subreddit: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n const location = Option(args.subreddit).fold(\n () => \"home feed\",\n (sr) => `r/${sr}`,\n )\n if (posts.length === 0) {\n return `No posts found in ${location}.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const timeSuffix = args.sort === \"top\" || args.sort === \"controversial\" ? `, ${args.time_filter}` : \"\"\n const heading = location === \"home feed\" ? \"Home Feed\" : location\n return `# ${args.sort} posts from ${heading} (${args.sort}${timeSuffix})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Subreddit tools\nserver.addTool({\n name: \"get_subreddit_info\",\n description:\n \"Get a subreddit's profile: title, description, subscriber and active-user counts, creation date, flags, wiki/link URLs, plus a community analysis and posting tips. Read-only; works anonymously. Returns metadata about the community itself — use browse_subreddit / get_top_posts for its posts, or get_subreddit_rules for its posting rules. Do NOT use this to find subreddits by topic — use search_reddit with type='sr'.\",\n annotations: {\n title: \"Get Subreddit Info\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name, without the r/ prefix (e.g. 'askscience').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getSubredditInfo(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get subreddit info: ${err.message}`)\n },\n (subreddit) => {\n const formattedSubreddit = formatSubredditInfo(subreddit)\n\n return `# Subreddit Information: r/${formattedSubreddit.name}\n\n## Overview\n- Name: r/${formattedSubreddit.name}\n- Title: ${formattedSubreddit.title}\n- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}\n- Active Users: ${\n typeof formattedSubreddit.stats.activeUsers === \"number\"\n ? formattedSubreddit.stats.activeUsers.toLocaleString()\n : formattedSubreddit.stats.activeUsers\n }\n\n## Description\n${formattedSubreddit.description.short}\n\n## Detailed Description\n${formattedSubreddit.description.full}\n\n## Metadata\n- Created: ${formattedSubreddit.metadata.created}\n- Flags: ${formattedSubreddit.metadata.flags.join(\", \")}\n\n## Links\n- Subreddit: ${formattedSubreddit.links.subreddit}\n- Wiki: ${formattedSubreddit.links.wiki}\n\n## Community Analysis\n- ${formattedSubreddit.communityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Engagement Tips\n- ${formattedSubreddit.engagementTips.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_subreddit_rules\",\n description:\n \"Get a subreddit's posting rules (each rule's name, what it applies to, and its description). Read-only; works anonymously. Returns the rules list, or a note when the subreddit lists none. Call this before create_post to check requirements and avoid auto-removal. For available post flairs use get_post_flairs instead.\",\n annotations: {\n title: \"Get Subreddit Rules\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name, without the r/ prefix (e.g. 'AskReddit').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getSubredditRules(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get subreddit rules: ${err.message}`)\n },\n (rules) => {\n if (rules.length === 0) {\n return `r/${args.subreddit_name} has no listed subreddit-specific rules.`\n }\n\n const ruleList = rules\n .map((rule, index) => {\n const applies = rule.kind === \"all\" ? \"posts & comments\" : `${rule.kind}s`\n const detail = rule.description.trim() === \"\" ? \"\" : `\\n${rule.description.trim()}`\n return `### ${index + 1}. ${rule.shortName} _(applies to ${applies})_${detail}`\n })\n .join(\"\\n\\n\")\n\n return `# Posting Rules for r/${args.subreddit_name}\n\n${ruleList}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_post_flairs\",\n description:\n \"List a subreddit's selectable link flairs (flair text + flair_id) for use with create_post. Read-only, but requires user credentials; many subreddits expose flairs only to members, so this can 403 or return empty anonymously. Pass a returned flair_id (and flair_text for text-editable flairs) to create_post. For the subreddit's posting rules use get_subreddit_rules instead.\",\n annotations: {\n title: \"Get Post Flairs\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name, without the r/ prefix (e.g. 'gadgets').\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getPostFlairs(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get post flairs: ${err.message}`)\n },\n (flairs) => {\n if (flairs.length === 0) {\n return `r/${args.subreddit_name} has no selectable link flairs (or none are visible to this account).`\n }\n\n const flairList = flairs\n .map((flair) => {\n const editable = flair.textEditable === true ? \" _(text editable)_\" : \"\"\n return `- ${flair.text}${editable} — \\`flair_id: ${flair.id}\\``\n })\n .join(\"\\n\")\n\n return `# Available Link Flairs for r/${args.subreddit_name}\n\n${flairList}\n\nPass the desired \\`flair_id\\` to \\`create_post\\`.`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_trending_subreddits\",\n description:\n \"Get the subreddits Reddit is currently featuring as trending/popular. Read-only, no parameters; works anonymously. Returns a list of subreddit names that changes through the day (cached briefly server-side). To find subreddits by keyword instead of by trend, use search_reddit with type='sr'.\",\n annotations: {\n title: \"Get Trending Subreddits\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({}),\n execute: async () => {\n const client = unwrapClient()\n\n const result = await client.getTrendingSubreddits()\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get trending subreddits: ${err.message}`)\n },\n (trendingSubreddits) => `# Trending Subreddits\n\n${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join(\"\\n\")}`,\n )\n },\n})\n\n// Search tools\nserver.addTool({\n name: \"search_reddit\",\n description:\n \"Search Reddit for posts — or subreddits/users via `type` — optionally scoped to one subreddit, with sort and time filters. Read-only; works anonymously. Returns a page of results (title, subreddit, author, score, comments, link) plus an `after` cursor for paging. Use this to find content by keyword; use browse_subreddit / get_top_posts to list a known subreddit's feed instead.\",\n annotations: {\n title: \"Search Reddit\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n query: z\n .string()\n .describe(\n \"Search terms; supports Reddit operators (quotes for exact phrases, author:name, self:yes). Must be non-empty.\",\n ),\n subreddit: z\n .string()\n .optional()\n .describe(\n \"Restrict results to this subreddit, without the r/ prefix (e.g. 'python'). Omit to search all of Reddit.\",\n ),\n sort: z\n .enum([\"relevance\", \"hot\", \"top\", \"new\", \"comments\"])\n .default(\"relevance\")\n .describe(\n \"Sort order. Prefer 'relevance' (default) for finding posts about a topic. Use 'top'/'hot' only for what's currently popular and 'new' for the latest — these rank by karma/recency and, especially combined with a narrow time_filter, can surface loosely-matching posts over the best topical results.\",\n ),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Restrict to results from this recent window (e.g. 'week'). Default 'all' (no time limit).\"),\n limit: z.number().min(1).max(100).default(10).describe(\"How many results to return, 1–100 (default 10).\"),\n type: z\n .enum([\"link\", \"sr\", \"user\"])\n .default(\"link\")\n .describe(\"What to search for: 'link' = posts (default), 'sr' = subreddits, 'user' = users.\"),\n after: z\n .string()\n .optional()\n .describe(\"Forward pagination cursor: the `after` value from a previous call. Omit for the first page.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.query.trim() === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"Search query cannot be empty\")\n }\n\n const result = await client.searchReddit(args.query, {\n subreddit: args.subreddit,\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n type: args.type,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to search: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `No results found for \"${args.query}\"${searchLocation}.`\n }\n\n const searchResults = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Author: u/${post.author}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `# Reddit Search Results for: \"${args.query}\"${searchLocation}\n\nSorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}\n\n${searchResults}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Write tools (require user authentication)\nserver.addTool({\n name: \"create_post\",\n description:\n \"Create a new text or link post in a subreddit. Mutating and NOT idempotent — each call publishes a separate post. Requires REDDIT_USERNAME and REDDIT_PASSWORD; fails without them. Returns the new post's id and URL. Check get_subreddit_rules and get_post_flairs first, since many subreddits require a flair or reject certain content. WARNING: rapid posting or duplicate content may trigger Reddit's spam detection and account bans — enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n annotations: {\n title: \"Create Post\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n parameters: z.object({\n subreddit: z.string().describe(\"Target subreddit, without the r/ prefix (e.g. 'test').\"),\n title: z.string().describe(\"Post title (cannot be edited after creation).\"),\n content: z\n .string()\n .describe(\n \"For a self post (is_self=true): the body text, Reddit markdown supported. For a link post (is_self=false): the destination URL.\",\n ),\n is_self: z\n .boolean()\n .default(true)\n .describe(\n \"true = text/self post using `content` as the body (default); false = link post using `content` as the URL.\",\n ),\n flair_id: z\n .string()\n .optional()\n .describe(\n \"Link flair template id from get_post_flairs; many subreddits require one or the post is auto-removed.\",\n ),\n flair_text: z\n .string()\n .optional()\n .describe(\"Custom flair text, allowed only for flairs whose template is text-editable.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.createPost(\n args.subreddit,\n args.title,\n args.content,\n args.is_self,\n args.flair_id,\n args.flair_text,\n )\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to create post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post Created Successfully\n\n## Post Details\n- Title: ${formattedPost.title}\n- Subreddit: r/${formattedPost.subreddit}\n- Type: ${formattedPost.type}\n- Link: ${formattedPost.links.fullPost}\n\nYour post has been successfully submitted to r/${formattedPost.subreddit}.`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"reply_to_post\",\n description:\n \"Post a reply to an existing post or comment. Mutating and NOT idempotent — each call adds a new comment. Requires REDDIT_USERNAME and REDDIT_PASSWORD. The parent is identified by its thing id — t3_ for a post, t1_ for a comment — so this creates both top-level and nested replies. Returns the new comment's id. Use edit_comment to change a reply you already posted. WARNING: rapid or duplicate replies may trigger Reddit's spam detection; enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n annotations: {\n title: \"Reply to Post or Comment\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n parameters: z.object({\n post_id: z\n .string()\n .describe(\n \"Parent thing id to reply under: t3_<id> for a post (creates a top-level comment) or t1_<id> for a comment (creates a nested reply).\",\n ),\n content: z.string().describe(\"Reply body text; Reddit markdown supported.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.replyToPost(args.post_id, args.content)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to reply: ${err.message}`)\n },\n (comment) => `# Reply Posted Successfully\n\n## Comment Details\n- Posted to: ${args.post_id}\n- Author: u/${process.env.REDDIT_USERNAME}\n- Comment ID: ${comment.id}\n\nYour reply has been successfully posted.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_post\",\n description:\n \"Permanently delete one of your own posts. Mutating and destructive but idempotent — deleting an already-deleted post is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and only works on posts authored by the authenticated account. Only affects the post you name; use delete_comment for comments. WARNING: this cannot be undone — the content is removed, though the post id remains.\",\n annotations: {\n title: \"Delete Post\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The post to delete: a full thing id 't3_<id>' or just the base36 post id '<id>' (the 't3_' prefix is added automatically). Must be a post you authored.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deletePost(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete post: ${err.message}`)\n },\n () => `# Post Deleted Successfully\n\nThe post ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_comment\",\n description:\n \"Permanently delete one of your own comments. Mutating and destructive but idempotent — deleting an already-deleted comment is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and only works on comments authored by the authenticated account. Only affects the comment you name; use delete_post for posts. WARNING: this cannot be undone — the content is removed, though the comment id remains.\",\n annotations: {\n title: \"Delete Comment\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The comment to delete: a full thing id 't1_<id>' or just the base36 comment id '<id>' (the 't1_' prefix is added automatically). Must be a comment you authored.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deleteComment(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete comment: ${err.message}`)\n },\n () => `# Comment Deleted Successfully\n\nThe comment ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_post\",\n description:\n 'Replace the body text of one of your own self-text posts. Mutating and idempotent (same text → same result); it overwrites the previous body. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and works only on self posts you authored — titles and link posts cannot be edited. Adds an \"edited\" marker. Use create_post to make a new post, or edit_comment for comments. WARNING: rapid edits may trigger spam detection; enable REDDIT_SAFE_MODE for protection.',\n annotations: {\n title: \"Edit Post\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The post to edit: a full thing id 't3_<id>' or just the base36 post id '<id>' (the 't3_' prefix is added automatically). Must be a self-text post you authored.\",\n ),\n new_text: z\n .string()\n .describe(\"Replacement body text; fully overwrites the current body. Reddit markdown supported.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editPost(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit post: ${err.message}`)\n },\n () => `# Post Edited Successfully\n\nThe post ${args.thing_id} has been updated with your new content.\n\n**Note**:\n- Only self (text) posts can be edited\n- Post titles cannot be edited\n- Link posts cannot be edited\n- An \"edited\" marker will appear on your post`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_comment\",\n description:\n 'Replace the text of one of your own comments. Mutating and idempotent (same text → same result); it overwrites the previous content. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and works only on comments you authored. Adds an \"edited\" marker. Use reply_to_post to add a new comment, or edit_post for posts. WARNING: rapid edits may trigger spam detection; enable REDDIT_SAFE_MODE for protection.',\n annotations: {\n title: \"Edit Comment\",\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The comment to edit: a full thing id 't1_<id>' or just the base36 comment id '<id>' (the 't1_' prefix is added automatically). Must be a comment you authored.\",\n ),\n new_text: z\n .string()\n .describe(\"Replacement comment text; fully overwrites the current content. Reddit markdown supported.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editComment(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit comment: ${err.message}`)\n },\n () => `# Comment Edited Successfully\n\nThe comment ${args.thing_id} has been updated with your new content.\n\n**Note**: An \"edited\" marker will appear on your comment to show it has been modified.`,\n )\n },\n})\n\n// Comment tools\nserver.addTool({\n name: \"get_post_comments\",\n description:\n \"Get the comment thread for a post (by post id + subreddit), sorted best/top/new/controversial/old/qa. Read-only; works anonymously. Returns the post header plus threaded comments (author, OP/edited badges, score, body, nesting depth) up to `limit`. Long threads are truncated with 'load more' stubs — expand those with get_more_comments. Use get_reddit_post for just the post body, not the thread.\",\n annotations: {\n title: \"Get Post Comments\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n post_id: z\n .string()\n .describe(\n \"Base36 post id — the segment after /comments/ in a permalink (e.g. '1abc23'). With or without a t3_ prefix.\",\n ),\n subreddit: z.string().describe(\"The subreddit the post lives in, without the r/ prefix (e.g. 'movies').\"),\n sort: z\n .enum([\"best\", \"top\", \"new\", \"controversial\", \"old\", \"qa\"])\n .default(\"best\")\n .describe(\"Comment ordering: 'best' (default), 'top', 'new', 'controversial', 'old', or 'qa' (Q&A).\"),\n limit: z\n .number()\n .min(1)\n .max(500)\n .default(100)\n .describe(\n \"Maximum comments to return, 1–500 (default 100). Deeply nested replies may still be truncated as 'load more' stubs.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.post_id === \"\" || args.subreddit === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"post_id and subreddit are required\")\n }\n\n const result = await client.getPostComments(args.post_id, args.subreddit, {\n sort: args.sort,\n limit: args.limit,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get comments: ${err.message}`)\n },\n ({ post, comments }) => {\n const header = `# Comments for: ${post.title}\n\n**Post by u/${post.author} in r/${post.subreddit}**\n- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n\n---\n\n`\n\n if (comments.length === 0) {\n return `${header}No comments found for this post.`\n }\n\n const commentSummaries = comments\n .map((comment) => {\n const indent = \"└─\".repeat(Math.min(comment.depth ?? 0, 3))\n const authorBadge = comment.isSubmitter ? \" **[OP]**\" : \"\"\n const editedBadge = comment.edited ? \" *(edited)*\" : \"\"\n\n return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)\n\n${comment.body}\n\n---`\n })\n .join(\"\\n\\n\")\n\n return header + commentSummaries\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_more_comments\",\n description:\n \"Expand truncated 'load more comments' stubs in a thread. Read-only; works anonymously. Pass the post's link id and the comment ids from a 'more' node (surfaced by get_post_comments) to fetch those hidden comments; returns the expanded comments (author, body excerpt, score, link). Call get_post_comments first to obtain the thread and its 'more' node ids — do NOT invent ids.\",\n annotations: {\n title: \"Get More Comments\",\n readOnlyHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n link_id: z\n .string()\n .describe(\"The parent post's link id (base36, with or without the t3_ prefix) that the stub belongs to.\"),\n comment_ids: z\n .array(z.string())\n .min(1)\n .describe(\n \"Base36 comment ids to expand, taken from a 'more' node returned by get_post_comments (not arbitrary ids).\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMoreComments(args.link_id, args.comment_ids)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to expand comments: ${err.message}`)\n },\n (comments) => {\n if (comments.length === 0) {\n return \"No additional comments were returned for those ids.\"\n }\n\n const commentList = comments\n .map((comment, index) => {\n const truncated = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n return `### ${index + 1}. u/${comment.author} ${flags.join(\" \")}\n> ${truncated}\n\n- Score: ${comment.score.toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Expanded Comments (${comments.length})\n\n${commentList}`\n },\n )\n },\n})\n\n// Initialize and start server\nasync function main() {\n await setupRedditClient()\n\n const useHttp = process.env.TRANSPORT_TYPE === \"httpStream\" || process.env.TRANSPORT_TYPE === \"http\"\n const port = parseInt(process.env.PORT ?? \"3000\")\n const host = process.env.HOST ?? \"127.0.0.1\"\n\n if (useHttp) {\n console.error(`[Setup] Starting HTTP server on ${host}:${port}`)\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port,\n host,\n endpoint: \"/mcp\",\n },\n })\n console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`)\n console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`)\n } else {\n console.error(\"[Setup] Starting in stdio mode\")\n await server.start({\n transportType: \"stdio\",\n })\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nvoid main().catch(console.error)\n"],"mappings":"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"}