reddit-mcp-server 1.6.0 → 1.6.1

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/README.md CHANGED
@@ -82,14 +82,16 @@ REDDIT_CLIENT_ID=your_client_id REDDIT_CLIENT_SECRET=your_client_secret \
82
82
 
83
83
  ### Write Tools (Require User Credentials)
84
84
 
85
- | Tool | Description |
86
- | ---------------- | ------------------------------------------- |
87
- | `create_post` | Create a new post in a subreddit |
88
- | `reply_to_post` | Post a reply to an existing post or comment |
89
- | `edit_post` | Edit your own Reddit post (self-text only) |
90
- | `edit_comment` | Edit your own Reddit comment |
91
- | `delete_post` | Permanently delete your own post |
92
- | `delete_comment` | Permanently delete your own comment |
85
+ | Tool | Description |
86
+ | ---------------- | ---------------------------------------------- |
87
+ | `create_post` | Create a new post in a subreddit |
88
+ | `reply_to_post` | Post a reply to an existing post or comment |
89
+ | `edit_post` | Edit your own Reddit post (self-text only) |
90
+ | `edit_comment` | Edit your own Reddit comment |
91
+ | `save_content` | Save a post or comment to your account |
92
+ | `unsave_content` | Remove a post or comment from your saved items |
93
+ | `delete_post` | Permanently delete your own post |
94
+ | `delete_comment` | Permanently delete your own comment |
93
95
 
94
96
  ## Configuration
95
97
 
package/dist/bin.js CHANGED
@@ -3,12 +3,12 @@
3
3
  process.env.TRANSPORT_TYPE ??= "stdio";
4
4
  const args = process.argv.slice(2);
5
5
  if (args.includes("--version") || args.includes("-v")) {
6
- console.log("1.6.0");
6
+ console.log("1.6.1");
7
7
  process.exit(0);
8
8
  }
9
9
  if (args.includes("--help") || args.includes("-h")) {
10
10
  console.log(`
11
- Reddit MCP Server v1.6.0
11
+ Reddit MCP Server v1.6.1
12
12
 
13
13
  Usage: reddit-mcp-server [options]
14
14
 
package/dist/index.js CHANGED
@@ -340,6 +340,42 @@ function parsePostData(post) {
340
340
  permalink: post.permalink
341
341
  };
342
342
  }
343
+ function parseSubredditSearchData(sub) {
344
+ return {
345
+ id: String(sub.id ?? sub.display_name),
346
+ title: `r/${sub.display_name} (${sub.subscribers.toLocaleString("en-US")} subscribers)${sub.title ? ` — ${sub.title}` : ""}`,
347
+ author: "",
348
+ subreddit: sub.display_name,
349
+ selftext: sub.public_description,
350
+ url: `https://reddit.com${sub.url}`,
351
+ score: sub.subscribers,
352
+ upvoteRatio: 1,
353
+ numComments: 0,
354
+ createdUtc: sub.created_utc,
355
+ over18: sub.over18,
356
+ edited: false,
357
+ isSelf: false,
358
+ permalink: sub.url
359
+ };
360
+ }
361
+ function parseUserSearchData(user) {
362
+ const karma = user.total_karma ?? user.link_karma + user.comment_karma;
363
+ return {
364
+ id: user.id,
365
+ title: `u/${user.name} (${karma.toLocaleString("en-US")} karma)`,
366
+ author: user.name,
367
+ subreddit: "",
368
+ url: `https://reddit.com/user/${user.name}`,
369
+ score: karma,
370
+ upvoteRatio: 1,
371
+ numComments: 0,
372
+ createdUtc: user.created_utc,
373
+ over18: false,
374
+ edited: false,
375
+ isSelf: false,
376
+ permalink: `/user/${user.name}`
377
+ };
378
+ }
343
379
  var RedditClient = class {
344
380
  clientId;
345
381
  clientSecret;
@@ -890,6 +926,28 @@ var RedditClient = class {
890
926
  async editComment(thingId, newText) {
891
927
  return this.editThing(thingId, newText, "t1");
892
928
  }
929
+ async setSaved(thingId, saved, category) {
930
+ return (await Try.async(async () => {
931
+ this.validateWriteAccess();
932
+ const fullThingId = normalizeFullname(thingId, "t3");
933
+ const params = new URLSearchParams();
934
+ params.append("id", fullThingId);
935
+ if (saved && category !== void 0) params.append("category", category);
936
+ const response = (await this.makeRequest(saved ? "/api/save" : "/api/unsave", {
937
+ method: "POST",
938
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
939
+ body: params.toString()
940
+ })).orThrow();
941
+ if (!response.ok) throw new HttpError(response.status, `Failed to ${saved ? "save" : "unsave"}: HTTP ${response.status}`);
942
+ return true;
943
+ })).toEither((error) => classifyRedditError(error));
944
+ }
945
+ async saveContent(thingId, category) {
946
+ return this.setSaved(thingId, true, category);
947
+ }
948
+ async unsaveContent(thingId) {
949
+ return this.setSaved(thingId, false);
950
+ }
893
951
  async searchReddit(query, options = {}) {
894
952
  if (this.usesRss) return this.requiresOAuthError("search_reddit");
895
953
  const { subreddit, sort = "relevance", timeFilter = "all", limit = 25, type = "link", after, before } = options;
@@ -910,7 +968,12 @@ var RedditClient = class {
910
968
  if (!response.ok) throw new HttpError(response.status, `Failed to search Reddit: HTTP ${response.status}`);
911
969
  const json = await response.json();
912
970
  return {
913
- items: json.data.children.filter((child) => child.kind === "t3").map((child) => parsePostData(child.data)),
971
+ items: json.data.children.flatMap((child) => {
972
+ if (child.kind === "t3") return [parsePostData(child.data)];
973
+ if (child.kind === "t5") return [parseSubredditSearchData(child.data)];
974
+ if (child.kind === "t2") return [parseUserSearchData(child.data)];
975
+ return [];
976
+ }),
914
977
  ...listingCursor(json.data)
915
978
  };
916
979
  })).toEither((error) => classifyRedditError(error, context));
@@ -1181,7 +1244,7 @@ function formatSubredditInfo(subreddit) {
1181
1244
  //#endregion
1182
1245
  //#region src/index.ts
1183
1246
  dotenv.config({ quiet: true });
1184
- const VERSION = "1.6.0";
1247
+ const VERSION = "1.6.1";
1185
1248
  function validateUserAgent(userAgent, username) {
1186
1249
  if (!/^[\w-]+:[\w-]+:[\d.]+ \(by \/u\/\w+\)$/.test(userAgent)) {
1187
1250
  console.error("[Warning] User-Agent does not follow Reddit's recommended format");
@@ -1364,6 +1427,7 @@ Available capabilities:
1364
1427
  - Search Reddit content across posts and subreddits
1365
1428
  - Create posts and reply to posts/comments (with authentication)
1366
1429
  - Edit your own posts and comments (with authentication)
1430
+ - Save or unsave posts and comments (with authentication)
1367
1431
  - Delete your own posts and comments (with authentication)
1368
1432
  - Analyze engagement metrics and community insights
1369
1433
 
@@ -2107,6 +2171,51 @@ The comment ${args.thing_id} has been updated with your new content.
2107
2171
  **Note**: An "edited" marker will appear on your comment to show it has been modified.`);
2108
2172
  }
2109
2173
  });
2174
+ server.addTool({
2175
+ name: "save_content",
2176
+ description: "Save a post or comment to your account (a private bookmark, listed by get_my_saved). Mutating but idempotent — saving an already-saved item is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD; works on any post/comment you can see, not just your own. Accepts a full thing id (t3_ for a post, t1_ for a comment) — a bare id is treated as a post. Use unsave_content to undo.",
2177
+ annotations: {
2178
+ title: "Save Post or Comment",
2179
+ readOnlyHint: false,
2180
+ destructiveHint: false,
2181
+ idempotentHint: true,
2182
+ openWorldHint: true
2183
+ },
2184
+ parameters: z.object({
2185
+ thing_id: z.string().describe("The post or comment to save: a full thing id 't3_<id>' (post) or 't1_<id>' (comment). A bare id is treated as a post."),
2186
+ category: z.string().optional().describe("Optional save category to file this under (Reddit Premium feature; ignored on other accounts).")
2187
+ }),
2188
+ execute: async (args) => {
2189
+ const client = unwrapClient();
2190
+ if (process.env.REDDIT_USERNAME === void 0 || process.env.REDDIT_PASSWORD === void 0) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
2191
+ return (await client.saveContent(args.thing_id, args.category)).fold((err) => {
2192
+ throw new Error(`Failed to save content: ${err.message}`);
2193
+ }, () => `# Content Saved Successfully
2194
+
2195
+ ${args.thing_id} has been saved to your account. Use get_my_saved to view your saved items.`);
2196
+ }
2197
+ });
2198
+ server.addTool({
2199
+ name: "unsave_content",
2200
+ description: "Remove a post or comment from your saved items (undoes save_content). Mutating but idempotent — unsaving an item that isn't saved is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD. Accepts a full thing id (t3_ for a post, t1_ for a comment) — a bare id is treated as a post.",
2201
+ annotations: {
2202
+ title: "Unsave Post or Comment",
2203
+ readOnlyHint: false,
2204
+ destructiveHint: false,
2205
+ idempotentHint: true,
2206
+ openWorldHint: true
2207
+ },
2208
+ parameters: z.object({ thing_id: z.string().describe("The post or comment to unsave: a full thing id 't3_<id>' (post) or 't1_<id>' (comment). A bare id is treated as a post.") }),
2209
+ execute: async (args) => {
2210
+ const client = unwrapClient();
2211
+ if (process.env.REDDIT_USERNAME === void 0 || process.env.REDDIT_PASSWORD === void 0) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
2212
+ return (await client.unsaveContent(args.thing_id)).fold((err) => {
2213
+ throw new Error(`Failed to unsave content: ${err.message}`);
2214
+ }, () => `# Content Unsaved Successfully
2215
+
2216
+ ${args.thing_id} has been removed from your saved items.`);
2217
+ }
2218
+ });
2110
2219
  server.addTool({
2111
2220
  name: "get_post_comments",
2112
2221
  description: "Get the comment thread for a post (by post id + subreddit), sorted best/top/new/controversial/old/qa. Read-only; requires OAuth credentials. 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.",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/client/errors.ts","../src/utils/reddit-identifiers.ts","../src/client/response-cache.ts","../src/client/rss-client.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-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","import { XMLParser } from \"fast-xml-parser\"\nimport type { Either } from \"functype\"\nimport { Try } from \"functype\"\n\nimport type { Page, RedditPost } from \"../types\"\nimport { normalizeSubreddit } from \"../utils/reddit-identifiers\"\nimport type { RedditError } from \"./errors\"\nimport { classifyRedditError, HttpError } from \"./errors\"\n\ntype AtomContent = string | { readonly \"#text\"?: string; readonly \"@_type\"?: string }\n\ntype AtomEntry = {\n readonly author?: { readonly name?: string; readonly uri?: string }\n readonly category?: { readonly \"@_term\"?: string; readonly \"@_label\"?: string }\n readonly content?: AtomContent\n readonly id?: string\n readonly link?: { readonly \"@_href\"?: string }\n readonly updated?: string\n readonly published?: string\n readonly title?: string\n}\n\nfunction contentText(content: AtomContent | undefined): string {\n if (content === undefined) return \"\"\n if (typeof content === \"string\") return content\n return content[\"#text\"] ?? \"\"\n}\n\ntype AtomFeed = {\n readonly feed?: {\n readonly entry?: AtomEntry | readonly AtomEntry[]\n readonly subtitle?: string\n readonly title?: string\n }\n}\n\nconst parser = new XMLParser({\n ignoreAttributes: false,\n removeNSPrefix: true,\n parseTagValue: false,\n})\n\nexport function parseAtomFeed(xml: string): readonly AtomEntry[] {\n const result = parser.parse(xml) as AtomFeed\n const entries = result.feed?.entry\n if (!entries) return []\n return (Array.isArray(entries) ? entries : [entries]) as readonly AtomEntry[]\n}\n\nfunction decodeHtmlEntities(text: string): string {\n return text\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&#32;/g, \" \")\n .replace(/&amp;/g, \"&\")\n}\n\nfunction extractLinkUrl(contentHtml: string): string | undefined {\n const match = contentHtml.match(/href=\"([^\"]+)\">\\[link\\]/)\n return match?.[1] ? decodeHtmlEntities(match[1]) : undefined\n}\n\nfunction extractSelfText(contentHtml: string): string {\n const match = contentHtml.match(/<!-- SC_OFF -->(.*?)<!-- SC_ON -->/s)\n if (!match) return \"\"\n return decodeHtmlEntities(match[1].replace(/<[^>]+>/g, \"\")).trim()\n}\n\nexport function atomEntryToRedditPost(entry: AtomEntry): RedditPost {\n const permalink = entry.link?.[\"@_href\"] ?? \"\"\n const contentHtml = contentText(entry.content)\n const externalUrl = extractLinkUrl(contentHtml)\n const isSelf = !externalUrl || externalUrl.includes(\"/comments/\")\n const author = (entry.author?.name ?? \"\").replace(/^\\/u\\//, \"\")\n const subreddit = entry.category?.[\"@_term\"] ?? \"\"\n const thingId = entry.id ?? \"\"\n const postId = thingId.replace(/^t3_/, \"\")\n\n return {\n id: postId,\n title: entry.title ?? \"\",\n author,\n subreddit,\n selftext: extractSelfText(contentHtml),\n url: isSelf ? permalink : (externalUrl ?? permalink),\n score: 0,\n upvoteRatio: 0,\n numComments: 0,\n createdUtc: Math.floor(new Date(entry.published ?? entry.updated ?? \"\").getTime() / 1000),\n over18: false,\n edited: false,\n isSelf,\n permalink: permalink.replace(\"https://www.reddit.com\", \"\"),\n }\n}\n\ntype CacheEntry = { readonly page: Page<RedditPost>; readonly expiresAt: number }\n\nconst RSS_CACHE_TTL_MS = 60_000\n\nexport class RssClient {\n private readonly userAgent: string\n private readonly cache = new Map<string, CacheEntry>()\n\n constructor(userAgent: string) {\n this.userAgent = userAgent\n }\n\n async fetchSubredditPosts(\n subreddit: string,\n sort: string,\n timeFilter?: string,\n limit?: number,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const sub = normalizeSubreddit(subreddit)\n const basePath = sub === \"\" ? \"\" : `/r/${sub}`\n const sortPath = sort === \"hot\" ? \"\" : `/${sort}`\n const params = new URLSearchParams()\n if (timeFilter && (sort === \"top\" || sort === \"controversial\")) {\n params.set(\"t\", timeFilter)\n }\n if (limit !== undefined) {\n params.set(\"limit\", String(limit))\n }\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const qs = params.size > 0 ? `?${params}` : \"\"\n const url = `https://www.reddit.com${basePath}${sortPath}/.rss${qs}`\n\n const cached = this.cache.get(url)\n if (cached && Date.now() < cached.expiresAt) {\n return cached.page\n }\n\n const response = await fetch(url, {\n headers: { \"User-Agent\": this.userAgent },\n })\n\n if (response.status === 429) {\n throw new HttpError(429, \"RSS rate limit exceeded (~1 req/min for unauthenticated feeds). Try again shortly.\")\n }\n\n if (!response.ok) {\n throw new HttpError(response.status, `RSS feed request failed: ${response.status} ${response.statusText}`)\n }\n\n const xml = await response.text()\n const entries = parseAtomFeed(xml)\n const allItems = entries.map(atomEntryToRedditPost)\n const cap = limit ?? 25\n const items = allItems.length > cap ? allItems.slice(0, cap) : allItems\n const hasMore = allItems.length > items.length\n const afterCursor = hasMore ? `t3_${items[items.length - 1]!.id}` : undefined\n const page: Page<RedditPost> = { items, source: \"rss\" as const, ...(afterCursor ? { after: afterCursor } : {}) }\n\n this.cache.set(url, { page, expiresAt: Date.now() + RSS_CACHE_TTL_MS })\n return page\n })\n\n return attempt.toEither((error) => classifyRedditError(error, \"RSS fetch\"))\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 NotAuthenticatedError,\n NotFoundError,\n ValidationError,\n} from \"./errors\"\nimport { ResponseCache } from \"./response-cache\"\nimport { RssClient } from \"./rss-client\"\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 private readonly rssClient: RssClient\n readonly usesRss: boolean\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 this.usesRss = this.authMode === \"anonymous\" || (this.authMode === \"auto\" && !this.hasCredentials)\n this.rssClient = new RssClient(this.userAgent)\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.usesRss) {\n throw new NotAuthenticatedError(\n \"Write operations require OAuth credentials (REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET) \" +\n \"in addition to REDDIT_USERNAME and REDDIT_PASSWORD.\",\n )\n }\n if (this.username === undefined || this.password === undefined) {\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 private requiresOAuthError(tool: string): Either<RedditError, never> {\n return Left(\n new NotAuthenticatedError(\n `${tool} requires OAuth credentials (REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET). ` +\n \"RSS fallback only supports browse_subreddit and get_top_posts.\",\n ),\n )\n }\n\n async getUser(username: string): Promise<Either<RedditError, RedditUser>> {\n if (this.usesRss) return this.requiresOAuthError(\"get_user_info\")\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.usesRss) return this.requiresOAuthError(\"get_my_overview\")\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.usesRss) return this.requiresOAuthError(\"get_my_saved\")\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.usesRss) return this.requiresOAuthError(\"get_me\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_subreddit_info\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_subreddit_rules\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_post_flairs\")\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 if (this.usesRss) {\n return this.rssClient.fetchSubredditPosts(subreddit, \"top\", timeFilter, limit, after)\n }\n\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 if (this.usesRss) {\n return this.rssClient.fetchSubredditPosts(subreddit, sort, timeFilter, limit, after)\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 if (this.usesRss) return this.requiresOAuthError(\"get_reddit_post\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_trending_subreddits\")\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 if (this.usesRss) return this.requiresOAuthError(\"search_reddit\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_post_comments\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_more_comments\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_user_posts\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_user_comments\")\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 FormattedPostInfo,\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\nfunction rssDisclaimer(source?: string): string {\n return source === \"rss\"\n ? \"\\n\\n> **RSS mode** — scores, comment counts, and upvote ratios are unavailable. Set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET for full data.\"\n : \"\"\n}\n\nfunction formatPostSummary(post: FormattedPostInfo, index: number, source?: string): string {\n const statsLines =\n source === \"rss\"\n ? []\n : [\n `- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)`,\n `- Comments: ${post.stats.comments.toLocaleString()}`,\n ]\n\n return [\n `### ${index + 1}. ${post.title}`,\n `- Author: u/${post.author}`,\n ...statsLines,\n `- Posted: ${post.metadata.posted}`,\n `- Link: ${post.links.shortLink}`,\n ].join(\"\\n\")\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\") {\n console.error(\n \"[Warning] REDDIT_AUTH_MODE=anonymous is deprecated; it is now an alias for auto without credentials.\",\n )\n }\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] RSS fallback mode — no OAuth credentials detected.\")\n console.error(\"[Setup] Only browse_subreddit and get_top_posts are available (~1 req/min, no engagement metrics).\")\n console.error(\"[Setup] Set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET for full API access (100 req/min).\")\n console.error(\"[Setup] See: https://www.reddit.com/wiki/api\")\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; requires OAuth credentials. 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 without user credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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 without credentials via RSS fallback (titles and links only, no scores or comment counts). Returns a page of posts 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 postSummaries = posts\n .map(formatPostInfo)\n .map((post, index) => formatPostSummary(post, index, page.source))\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)}${rssDisclaimer(page.source)}`\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 without credentials via RSS fallback (titles and links only, no scores or comment counts). `time_filter` applies only to the top and controversial sorts. Returns a page of posts 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 postSummaries = posts\n .map(formatPostInfo)\n .map((post, index) => formatPostSummary(post, index, page.source))\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)}${rssDisclaimer(page.source)}`\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; requires OAuth credentials. 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; requires OAuth credentials. 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 without credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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/rss-client.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-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","import { XMLParser } from \"fast-xml-parser\"\nimport type { Either } from \"functype\"\nimport { Try } from \"functype\"\n\nimport type { Page, RedditPost } from \"../types\"\nimport { normalizeSubreddit } from \"../utils/reddit-identifiers\"\nimport type { RedditError } from \"./errors\"\nimport { classifyRedditError, HttpError } from \"./errors\"\n\ntype AtomContent = string | { readonly \"#text\"?: string; readonly \"@_type\"?: string }\n\ntype AtomEntry = {\n readonly author?: { readonly name?: string; readonly uri?: string }\n readonly category?: { readonly \"@_term\"?: string; readonly \"@_label\"?: string }\n readonly content?: AtomContent\n readonly id?: string\n readonly link?: { readonly \"@_href\"?: string }\n readonly updated?: string\n readonly published?: string\n readonly title?: string\n}\n\nfunction contentText(content: AtomContent | undefined): string {\n if (content === undefined) return \"\"\n if (typeof content === \"string\") return content\n return content[\"#text\"] ?? \"\"\n}\n\ntype AtomFeed = {\n readonly feed?: {\n readonly entry?: AtomEntry | readonly AtomEntry[]\n readonly subtitle?: string\n readonly title?: string\n }\n}\n\nconst parser = new XMLParser({\n ignoreAttributes: false,\n removeNSPrefix: true,\n parseTagValue: false,\n})\n\nexport function parseAtomFeed(xml: string): readonly AtomEntry[] {\n const result = parser.parse(xml) as AtomFeed\n const entries = result.feed?.entry\n if (!entries) return []\n return (Array.isArray(entries) ? entries : [entries]) as readonly AtomEntry[]\n}\n\nfunction decodeHtmlEntities(text: string): string {\n return text\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/&#32;/g, \" \")\n .replace(/&amp;/g, \"&\")\n}\n\nfunction extractLinkUrl(contentHtml: string): string | undefined {\n const match = contentHtml.match(/href=\"([^\"]+)\">\\[link\\]/)\n return match?.[1] ? decodeHtmlEntities(match[1]) : undefined\n}\n\nfunction extractSelfText(contentHtml: string): string {\n const match = contentHtml.match(/<!-- SC_OFF -->(.*?)<!-- SC_ON -->/s)\n if (!match) return \"\"\n return decodeHtmlEntities(match[1].replace(/<[^>]+>/g, \"\")).trim()\n}\n\nexport function atomEntryToRedditPost(entry: AtomEntry): RedditPost {\n const permalink = entry.link?.[\"@_href\"] ?? \"\"\n const contentHtml = contentText(entry.content)\n const externalUrl = extractLinkUrl(contentHtml)\n const isSelf = !externalUrl || externalUrl.includes(\"/comments/\")\n const author = (entry.author?.name ?? \"\").replace(/^\\/u\\//, \"\")\n const subreddit = entry.category?.[\"@_term\"] ?? \"\"\n const thingId = entry.id ?? \"\"\n const postId = thingId.replace(/^t3_/, \"\")\n\n return {\n id: postId,\n title: entry.title ?? \"\",\n author,\n subreddit,\n selftext: extractSelfText(contentHtml),\n url: isSelf ? permalink : (externalUrl ?? permalink),\n score: 0,\n upvoteRatio: 0,\n numComments: 0,\n createdUtc: Math.floor(new Date(entry.published ?? entry.updated ?? \"\").getTime() / 1000),\n over18: false,\n edited: false,\n isSelf,\n permalink: permalink.replace(\"https://www.reddit.com\", \"\"),\n }\n}\n\ntype CacheEntry = { readonly page: Page<RedditPost>; readonly expiresAt: number }\n\nconst RSS_CACHE_TTL_MS = 60_000\n\nexport class RssClient {\n private readonly userAgent: string\n private readonly cache = new Map<string, CacheEntry>()\n\n constructor(userAgent: string) {\n this.userAgent = userAgent\n }\n\n async fetchSubredditPosts(\n subreddit: string,\n sort: string,\n timeFilter?: string,\n limit?: number,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const sub = normalizeSubreddit(subreddit)\n const basePath = sub === \"\" ? \"\" : `/r/${sub}`\n const sortPath = sort === \"hot\" ? \"\" : `/${sort}`\n const params = new URLSearchParams()\n if (timeFilter && (sort === \"top\" || sort === \"controversial\")) {\n params.set(\"t\", timeFilter)\n }\n if (limit !== undefined) {\n params.set(\"limit\", String(limit))\n }\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const qs = params.size > 0 ? `?${params}` : \"\"\n const url = `https://www.reddit.com${basePath}${sortPath}/.rss${qs}`\n\n const cached = this.cache.get(url)\n if (cached && Date.now() < cached.expiresAt) {\n return cached.page\n }\n\n const response = await fetch(url, {\n headers: { \"User-Agent\": this.userAgent },\n })\n\n if (response.status === 429) {\n throw new HttpError(429, \"RSS rate limit exceeded (~1 req/min for unauthenticated feeds). Try again shortly.\")\n }\n\n if (!response.ok) {\n throw new HttpError(response.status, `RSS feed request failed: ${response.status} ${response.statusText}`)\n }\n\n const xml = await response.text()\n const entries = parseAtomFeed(xml)\n const allItems = entries.map(atomEntryToRedditPost)\n const cap = limit ?? 25\n const items = allItems.length > cap ? allItems.slice(0, cap) : allItems\n const hasMore = allItems.length > items.length\n const afterCursor = hasMore ? `t3_${items[items.length - 1]!.id}` : undefined\n const page: Page<RedditPost> = { items, source: \"rss\" as const, ...(afterCursor ? { after: afterCursor } : {}) }\n\n this.cache.set(url, { page, expiresAt: Date.now() + RSS_CACHE_TTL_MS })\n return page\n })\n\n return attempt.toEither((error) => classifyRedditError(error, \"RSS fetch\"))\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 NotAuthenticatedError,\n NotFoundError,\n ValidationError,\n} from \"./errors\"\nimport { ResponseCache } from \"./response-cache\"\nimport { RssClient } from \"./rss-client\"\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\n// /search.json?type=sr and type=user return t5 (subreddit) and t2 (account) children rather than\n// t3 posts. Surface them through the RedditPost shape searchReddit already returns, so the search\n// formatter renders them without a breaking change; score carries subscribers/karma respectively.\nfunction parseSubredditSearchData(sub: RedditApiSubredditResponse[\"data\"]): RedditPost {\n return {\n id: String(sub.id ?? sub.display_name),\n title: `r/${sub.display_name} (${sub.subscribers.toLocaleString(\"en-US\")} subscribers)${sub.title ? ` — ${sub.title}` : \"\"}`,\n author: \"\",\n subreddit: sub.display_name,\n selftext: sub.public_description,\n url: `https://reddit.com${sub.url}`,\n score: sub.subscribers,\n upvoteRatio: 1,\n numComments: 0,\n createdUtc: sub.created_utc,\n over18: sub.over18,\n edited: false,\n isSelf: false,\n permalink: sub.url,\n }\n}\n\nfunction parseUserSearchData(user: RedditApiUserResponse[\"data\"]): RedditPost {\n const karma = user.total_karma ?? user.link_karma + user.comment_karma\n return {\n id: user.id,\n title: `u/${user.name} (${karma.toLocaleString(\"en-US\")} karma)`,\n author: user.name,\n subreddit: \"\",\n url: `https://reddit.com/user/${user.name}`,\n score: karma,\n upvoteRatio: 1,\n numComments: 0,\n createdUtc: user.created_utc,\n over18: false,\n edited: false,\n isSelf: false,\n permalink: `/user/${user.name}`,\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 private readonly rssClient: RssClient\n readonly usesRss: boolean\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 this.usesRss = this.authMode === \"anonymous\" || (this.authMode === \"auto\" && !this.hasCredentials)\n this.rssClient = new RssClient(this.userAgent)\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.usesRss) {\n throw new NotAuthenticatedError(\n \"Write operations require OAuth credentials (REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET) \" +\n \"in addition to REDDIT_USERNAME and REDDIT_PASSWORD.\",\n )\n }\n if (this.username === undefined || this.password === undefined) {\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 private requiresOAuthError(tool: string): Either<RedditError, never> {\n return Left(\n new NotAuthenticatedError(\n `${tool} requires OAuth credentials (REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET). ` +\n \"RSS fallback only supports browse_subreddit and get_top_posts.\",\n ),\n )\n }\n\n async getUser(username: string): Promise<Either<RedditError, RedditUser>> {\n if (this.usesRss) return this.requiresOAuthError(\"get_user_info\")\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.usesRss) return this.requiresOAuthError(\"get_my_overview\")\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.usesRss) return this.requiresOAuthError(\"get_my_saved\")\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.usesRss) return this.requiresOAuthError(\"get_me\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_subreddit_info\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_subreddit_rules\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_post_flairs\")\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 if (this.usesRss) {\n return this.rssClient.fetchSubredditPosts(subreddit, \"top\", timeFilter, limit, after)\n }\n\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 if (this.usesRss) {\n return this.rssClient.fetchSubredditPosts(subreddit, sort, timeFilter, limit, after)\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 if (this.usesRss) return this.requiresOAuthError(\"get_reddit_post\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_trending_subreddits\")\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 // Toggle a post's or comment's saved state via /api/save or /api/unsave. Both endpoints take\n // the same fullname id regardless of thing kind, so save/unsave share one helper (unlike\n // delete/edit, which need separate t1/t3 defaults per tool).\n private async setSaved(thingId: string, saved: boolean, category?: string): Promise<Either<RedditError, boolean>> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n this.validateWriteAccess()\n\n const fullThingId = normalizeFullname(thingId, \"t3\")\n\n const params = new URLSearchParams()\n params.append(\"id\", fullThingId)\n if (saved && category !== undefined) {\n params.append(\"category\", category)\n }\n\n const response = (\n await this.makeRequest(saved ? \"/api/save\" : \"/api/unsave\", {\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 ${saved ? \"save\" : \"unsave\"}: HTTP ${response.status}`)\n }\n\n return true\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async saveContent(thingId: string, category?: string): Promise<Either<RedditError, boolean>> {\n return this.setSaved(thingId, true, category)\n }\n\n async unsaveContent(thingId: string): Promise<Either<RedditError, boolean>> {\n return this.setSaved(thingId, false)\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 if (this.usesRss) return this.requiresOAuthError(\"search_reddit\")\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<Record<string, unknown>>\n\n const items = json.data.children.flatMap((child): readonly RedditPost[] => {\n if (child.kind === \"t3\") return [parsePostData(child.data as RedditApiPostData)]\n if (child.kind === \"t5\") return [parseSubredditSearchData(child.data as RedditApiSubredditResponse[\"data\"])]\n if (child.kind === \"t2\") return [parseUserSearchData(child.data as RedditApiUserResponse[\"data\"])]\n return []\n })\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 if (this.usesRss) return this.requiresOAuthError(\"get_post_comments\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_more_comments\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_user_posts\")\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 if (this.usesRss) return this.requiresOAuthError(\"get_user_comments\")\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 FormattedPostInfo,\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\nfunction rssDisclaimer(source?: string): string {\n return source === \"rss\"\n ? \"\\n\\n> **RSS mode** — scores, comment counts, and upvote ratios are unavailable. Set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET for full data.\"\n : \"\"\n}\n\nfunction formatPostSummary(post: FormattedPostInfo, index: number, source?: string): string {\n const statsLines =\n source === \"rss\"\n ? []\n : [\n `- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)`,\n `- Comments: ${post.stats.comments.toLocaleString()}`,\n ]\n\n return [\n `### ${index + 1}. ${post.title}`,\n `- Author: u/${post.author}`,\n ...statsLines,\n `- Posted: ${post.metadata.posted}`,\n `- Link: ${post.links.shortLink}`,\n ].join(\"\\n\")\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\") {\n console.error(\n \"[Warning] REDDIT_AUTH_MODE=anonymous is deprecated; it is now an alias for auto without credentials.\",\n )\n }\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] RSS fallback mode — no OAuth credentials detected.\")\n console.error(\"[Setup] Only browse_subreddit and get_top_posts are available (~1 req/min, no engagement metrics).\")\n console.error(\"[Setup] Set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET for full API access (100 req/min).\")\n console.error(\"[Setup] See: https://www.reddit.com/wiki/api\")\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- Save or unsave 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; requires OAuth credentials. 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 without user credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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 without credentials via RSS fallback (titles and links only, no scores or comment counts). Returns a page of posts 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 postSummaries = posts\n .map(formatPostInfo)\n .map((post, index) => formatPostSummary(post, index, page.source))\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)}${rssDisclaimer(page.source)}`\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 without credentials via RSS fallback (titles and links only, no scores or comment counts). `time_filter` applies only to the top and controversial sorts. Returns a page of posts 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 postSummaries = posts\n .map(formatPostInfo)\n .map((post, index) => formatPostSummary(post, index, page.source))\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)}${rssDisclaimer(page.source)}`\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; requires OAuth credentials. 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; requires OAuth credentials. 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 without credentials. 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; requires OAuth credentials. 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; requires OAuth credentials. 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\nserver.addTool({\n name: \"save_content\",\n description:\n \"Save a post or comment to your account (a private bookmark, listed by get_my_saved). Mutating but idempotent — saving an already-saved item is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD; works on any post/comment you can see, not just your own. Accepts a full thing id (t3_ for a post, t1_ for a comment) — a bare id is treated as a post. Use unsave_content to undo.\",\n annotations: {\n title: \"Save Post or Comment\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The post or comment to save: a full thing id 't3_<id>' (post) or 't1_<id>' (comment). A bare id is treated as a post.\",\n ),\n category: z\n .string()\n .optional()\n .describe(\"Optional save category to file this under (Reddit Premium feature; ignored on other accounts).\"),\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.saveContent(args.thing_id, args.category)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to save content: ${err.message}`)\n },\n () => `# Content Saved Successfully\n\n${args.thing_id} has been saved to your account. Use get_my_saved to view your saved items.`,\n )\n },\n})\n\nserver.addTool({\n name: \"unsave_content\",\n description:\n \"Remove a post or comment from your saved items (undoes save_content). Mutating but idempotent — unsaving an item that isn't saved is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD. Accepts a full thing id (t3_ for a post, t1_ for a comment) — a bare id is treated as a post.\",\n annotations: {\n title: \"Unsave Post or Comment\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The post or comment to unsave: a full thing id 't3_<id>' (post) or 't1_<id>' (comment). A bare id is treated as a post.\",\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.unsaveContent(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to unsave content: ${err.message}`)\n },\n () => `# Content Unsaved Successfully\n\n${args.thing_id} has been removed from your saved items.`,\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; requires OAuth credentials. 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; requires OAuth credentials. 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reddit-mcp-server",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "A Model Context Protocol (MCP) server for Reddit with full read AND write operations - create posts, reply, edit, and delete content.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",