reddit-mcp-server 1.1.2 → 1.2.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/dist/index.js CHANGED
@@ -1,912 +1,923 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (let key of __getOwnPropNames(from))
11
- if (!__hasOwnProp.call(to, key) && key !== except)
12
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
- }
14
- return to;
15
- };
16
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
- // If the importer is in node compatibility mode or this is not an ESM
18
- // file that has been converted to a CommonJS file using a Babel-
19
- // compatible transform (i.e. "__esModule" has not been set), then set
20
- // "default" to the CommonJS "module.exports" for node compatibility.
21
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
- mod
23
- ));
24
-
25
- // src/index.ts
26
- var import_fastmcp = require("fastmcp");
27
- var import_zod = require("zod");
1
+ const require_chunk = require('./chunk-kSYXY2_d.js');
2
+ let fastmcp = require("fastmcp");
3
+ let zod = require("zod");
4
+ let crypto = require("crypto");
5
+ crypto = require_chunk.__toESM(crypto);
6
+ let dotenv = require("dotenv");
7
+ dotenv = require_chunk.__toESM(dotenv);
28
8
 
29
- // src/client/reddit-client.ts
9
+ //#region src/client/reddit-client.ts
30
10
  var RedditClient = class {
31
- clientId;
32
- clientSecret;
33
- userAgent;
34
- username;
35
- password;
36
- accessToken;
37
- tokenExpiry = 0;
38
- baseUrl = "https://oauth.reddit.com";
39
- authenticated = false;
40
- constructor(config) {
41
- this.clientId = config.clientId;
42
- this.clientSecret = config.clientSecret;
43
- this.userAgent = config.userAgent;
44
- this.username = config.username;
45
- this.password = config.password;
46
- }
47
- async makeRequest(path, options = {}) {
48
- if (Date.now() >= this.tokenExpiry || !this.authenticated) {
49
- await this.authenticate();
50
- }
51
- const url = `${this.baseUrl}${path}`;
52
- const headers = {
53
- "User-Agent": this.userAgent,
54
- Authorization: `Bearer ${this.accessToken}`,
55
- ...options.headers
56
- };
57
- const response = await fetch(url, {
58
- ...options,
59
- headers
60
- });
61
- if (response.status === 401 && this.authenticated) {
62
- await this.authenticate();
63
- const retryHeaders = {
64
- ...headers,
65
- Authorization: `Bearer ${this.accessToken}`
66
- };
67
- return fetch(url, {
68
- ...options,
69
- headers: retryHeaders
70
- });
71
- }
72
- return response;
73
- }
74
- async authenticate() {
75
- try {
76
- const now = Date.now();
77
- if (this.accessToken && now < this.tokenExpiry) {
78
- return;
79
- }
80
- const authUrl = "https://www.reddit.com/api/v1/access_token";
81
- const authData = new URLSearchParams();
82
- const isUserAuth = !!(this.username && this.password);
83
- if (isUserAuth) {
84
- authData.append("grant_type", "password");
85
- authData.append("username", this.username);
86
- authData.append("password", this.password);
87
- } else {
88
- authData.append("grant_type", "client_credentials");
89
- }
90
- const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64");
91
- const response = await fetch(authUrl, {
92
- method: "POST",
93
- headers: {
94
- "User-Agent": this.userAgent,
95
- "Content-Type": "application/x-www-form-urlencoded",
96
- Authorization: `Basic ${credentials}`
97
- },
98
- body: authData.toString()
99
- });
100
- if (!response.ok) {
101
- const statusText = response.statusText || "Unknown Error";
102
- throw new Error(`Authentication failed: ${response.status} ${statusText}`);
103
- }
104
- const data = await response.json();
105
- this.accessToken = data.access_token;
106
- this.tokenExpiry = now + data.expires_in * 1e3;
107
- this.authenticated = true;
108
- } catch (error) {
109
- if (error instanceof Error) {
110
- throw error;
111
- }
112
- throw new Error("Failed to authenticate with Reddit API");
113
- }
114
- }
115
- async checkAuthentication() {
116
- if (!this.authenticated) {
117
- try {
118
- await this.authenticate();
119
- return true;
120
- } catch {
121
- return false;
122
- }
123
- }
124
- return true;
125
- }
126
- async getUser(username) {
127
- await this.authenticate();
128
- try {
129
- const response = await this.makeRequest(`/user/${username}/about.json`);
130
- if (!response.ok) {
131
- throw new Error(`HTTP ${response.status}`);
132
- }
133
- const json = await response.json();
134
- const data = json.data;
135
- return {
136
- name: data.name,
137
- id: data.id,
138
- commentKarma: data.comment_karma,
139
- linkKarma: data.link_karma,
140
- totalKarma: data.total_karma || data.comment_karma + data.link_karma,
141
- isMod: data.is_mod,
142
- isGold: data.is_gold,
143
- isEmployee: data.is_employee,
144
- createdUtc: data.created_utc,
145
- profileUrl: `https://reddit.com/user/${data.name}`
146
- };
147
- } catch {
148
- throw new Error(`Failed to get user info for ${username}`);
149
- }
150
- }
151
- async getSubredditInfo(subredditName) {
152
- await this.authenticate();
153
- try {
154
- const response = await this.makeRequest(`/r/${subredditName}/about.json`);
155
- if (!response.ok) {
156
- throw new Error(`HTTP ${response.status}`);
157
- }
158
- const json = await response.json();
159
- const data = json.data;
160
- return {
161
- displayName: data.display_name,
162
- title: data.title,
163
- description: data.description || "",
164
- publicDescription: data.public_description || "",
165
- subscribers: data.subscribers,
166
- activeUserCount: data.active_user_count ?? void 0,
167
- createdUtc: data.created_utc,
168
- over18: data.over18,
169
- subredditType: data.subreddit_type,
170
- url: data.url
171
- };
172
- } catch {
173
- throw new Error(`Failed to get subreddit info for ${subredditName}`);
174
- }
175
- }
176
- async getTopPosts(subreddit, timeFilter = "week", limit = 10) {
177
- await this.authenticate();
178
- try {
179
- const endpoint = subreddit ? `/r/${subreddit}/top.json` : "/top.json";
180
- const params = new URLSearchParams({
181
- t: timeFilter,
182
- limit: limit.toString()
183
- });
184
- const response = await this.makeRequest(`${endpoint}?${params}`);
185
- if (!response.ok) {
186
- throw new Error(`HTTP ${response.status}`);
187
- }
188
- const json = await response.json();
189
- return json.data.children.map((child) => {
190
- const post = child.data;
191
- return {
192
- id: post.id,
193
- title: post.title,
194
- author: post.author,
195
- subreddit: post.subreddit,
196
- selftext: post.selftext,
197
- url: post.url,
198
- score: post.score,
199
- upvoteRatio: post.upvote_ratio,
200
- numComments: post.num_comments,
201
- createdUtc: post.created_utc,
202
- over18: post.over_18,
203
- spoiler: post.spoiler,
204
- edited: !!post.edited,
205
- isSelf: post.is_self,
206
- linkFlairText: post.link_flair_text ?? void 0,
207
- permalink: post.permalink
208
- };
209
- });
210
- } catch {
211
- throw new Error(`Failed to get top posts for ${subreddit || "home"}`);
212
- }
213
- }
214
- async getPost(postId, subreddit) {
215
- await this.authenticate();
216
- try {
217
- const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`;
218
- const response = await this.makeRequest(endpoint);
219
- if (!response.ok) {
220
- throw new Error(`HTTP ${response.status}`);
221
- }
222
- const json = await response.json();
223
- let post;
224
- if (subreddit) {
225
- post = json[0].data.children[0].data;
226
- } else {
227
- if (!json.data.children.length) {
228
- throw new Error(`Post with ID ${postId} not found`);
229
- }
230
- post = json.data.children[0].data;
231
- }
232
- return {
233
- id: post.id,
234
- title: post.title,
235
- author: post.author,
236
- subreddit: post.subreddit,
237
- selftext: post.selftext,
238
- url: post.url,
239
- score: post.score,
240
- upvoteRatio: post.upvote_ratio,
241
- numComments: post.num_comments,
242
- createdUtc: post.created_utc,
243
- over18: post.over_18,
244
- spoiler: post.spoiler,
245
- edited: !!post.edited,
246
- isSelf: post.is_self,
247
- linkFlairText: post.link_flair_text,
248
- permalink: post.permalink
249
- };
250
- } catch {
251
- throw new Error(`Failed to get post with ID ${postId}`);
252
- }
253
- }
254
- async getTrendingSubreddits(limit = 5) {
255
- await this.authenticate();
256
- try {
257
- const params = new URLSearchParams({ limit: limit.toString() });
258
- const response = await this.makeRequest(`/subreddits/popular.json?${params}`);
259
- if (!response.ok) {
260
- throw new Error(`HTTP ${response.status}`);
261
- }
262
- const json = await response.json();
263
- return json.data.children.map((child) => child.data.display_name);
264
- } catch {
265
- throw new Error("Failed to get trending subreddits");
266
- }
267
- }
268
- async createPost(subreddit, title, content, isSelf = true) {
269
- var _a, _b, _c, _d, _e, _f;
270
- await this.authenticate();
271
- if (!this.username || !this.password) {
272
- throw new Error("User authentication required for posting");
273
- }
274
- try {
275
- const kind = isSelf ? "self" : "link";
276
- const params = new URLSearchParams();
277
- params.append("sr", subreddit);
278
- params.append("kind", kind);
279
- params.append("title", title);
280
- params.append(isSelf ? "text" : "url", content);
281
- params.append("api_type", "json");
282
- const response = await this.makeRequest("/api/submit", {
283
- method: "POST",
284
- headers: {
285
- "Content-Type": "application/x-www-form-urlencoded"
286
- },
287
- body: params.toString()
288
- });
289
- if (!response.ok) {
290
- const errorText = await response.text();
291
- console.error(`[Reddit API] Create post failed: ${response.status} ${response.statusText}`);
292
- console.error(`[Reddit API] Error response: ${errorText}`);
293
- throw new Error(`HTTP ${response.status}: ${errorText}`);
294
- }
295
- const json = await response.json();
296
- console.error(`[Reddit API] Create post response:`, JSON.stringify(json, null, 2));
297
- if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
298
- const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
299
- console.error(`[Reddit API] Post creation errors: ${errors}`);
300
- throw new Error(`Reddit API errors: ${errors}`);
301
- }
302
- const postId = ((_c = (_b = json.json) == null ? void 0 : _b.data) == null ? void 0 : _c.id) || ((_f = (_e = (_d = json.json) == null ? void 0 : _d.data) == null ? void 0 : _e.name) == null ? void 0 : _f.replace("t3_", ""));
303
- if (!postId) {
304
- console.error(`[Reddit API] No post ID in response`);
305
- throw new Error("No post ID returned from Reddit");
306
- }
307
- console.error(`[Reddit API] Post created with ID: ${postId}`);
308
- return await this.getPost(postId, subreddit);
309
- } catch (error) {
310
- console.error(`[Reddit API] Create post exception:`, error);
311
- if (error instanceof Error && error.message.includes("HTTP")) {
312
- throw error;
313
- }
314
- throw new Error(
315
- `Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`
316
- );
317
- }
318
- }
319
- async checkPostExists(postId) {
320
- await this.authenticate();
321
- try {
322
- const response = await this.makeRequest(`/api/info.json?id=t3_${postId}`);
323
- if (!response.ok) {
324
- return false;
325
- }
326
- const json = await response.json();
327
- return json.data.children.length > 0;
328
- } catch {
329
- return false;
330
- }
331
- }
332
- async replyToPost(postId, content) {
333
- await this.authenticate();
334
- if (!this.username || !this.password) {
335
- throw new Error("User authentication required for posting replies");
336
- }
337
- try {
338
- if (!await this.checkPostExists(postId)) {
339
- throw new Error(`Post with ID ${postId} does not exist or is not accessible`);
340
- }
341
- const params = new URLSearchParams();
342
- params.append("thing_id", `t3_${postId}`);
343
- params.append("text", content);
344
- params.append("api_type", "json");
345
- const response = await this.makeRequest("/api/comment", {
346
- method: "POST",
347
- headers: {
348
- "Content-Type": "application/x-www-form-urlencoded"
349
- },
350
- body: params.toString()
351
- });
352
- if (!response.ok) {
353
- const errorText = await response.text();
354
- console.error(`[Reddit API] Reply to post failed: ${response.status} ${response.statusText}`);
355
- console.error(`[Reddit API] Error response: ${errorText}`);
356
- throw new Error(`HTTP ${response.status}: ${errorText}`);
357
- }
358
- const json = await response.json();
359
- console.error(`[Reddit API] Reply response:`, JSON.stringify(json, null, 2));
360
- if (json.json && json.json.data && json.json.data.things) {
361
- const commentData = json.json.data.things[0].data;
362
- return {
363
- id: commentData.id,
364
- author: this.username,
365
- body: content,
366
- score: 1,
367
- controversiality: 0,
368
- subreddit: commentData.subreddit,
369
- submissionTitle: commentData.link_title || "",
370
- createdUtc: Date.now() / 1e3,
371
- edited: false,
372
- isSubmitter: false,
373
- permalink: commentData.permalink
374
- };
375
- } else if (json.json && json.json.errors && json.json.errors.length > 0) {
376
- const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
377
- console.error(`[Reddit API] Reply errors: ${errors}`);
378
- throw new Error(`Reddit API errors: ${errors}`);
379
- } else {
380
- console.error(`[Reddit API] Unexpected reply response format`);
381
- throw new Error("Failed to parse reply response");
382
- }
383
- } catch (error) {
384
- console.error(`[Reddit API] Reply to post exception:`, error);
385
- if (error instanceof Error && error.message.includes("HTTP")) {
386
- throw error;
387
- }
388
- throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`);
389
- }
390
- }
391
- async deletePost(thingId) {
392
- await this.authenticate();
393
- if (!this.username || !this.password) {
394
- throw new Error("User authentication required for deleting content");
395
- }
396
- try {
397
- const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
398
- const params = new URLSearchParams();
399
- params.append("id", fullThingId);
400
- const response = await this.makeRequest("/api/del", {
401
- method: "POST",
402
- headers: {
403
- "Content-Type": "application/x-www-form-urlencoded"
404
- },
405
- body: params.toString()
406
- });
407
- if (!response.ok) {
408
- const errorText = await response.text();
409
- console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`);
410
- console.error(`[Reddit API] Error response: ${errorText}`);
411
- throw new Error(`HTTP ${response.status}: ${errorText}`);
412
- }
413
- console.error(`[Reddit API] Successfully deleted ${fullThingId}`);
414
- return true;
415
- } catch (error) {
416
- console.error(`[Reddit API] Delete exception:`, error);
417
- if (error instanceof Error && error.message.includes("HTTP")) {
418
- throw error;
419
- }
420
- throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
421
- }
422
- }
423
- async deleteComment(thingId) {
424
- const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
425
- return this.deletePost(fullThingId);
426
- }
427
- async editPost(thingId, newText) {
428
- var _a;
429
- await this.authenticate();
430
- if (!this.username || !this.password) {
431
- throw new Error("User authentication required for editing content");
432
- }
433
- try {
434
- const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
435
- const params = new URLSearchParams();
436
- params.append("thing_id", fullThingId);
437
- params.append("text", newText);
438
- params.append("api_type", "json");
439
- const response = await this.makeRequest("/api/editusertext", {
440
- method: "POST",
441
- headers: {
442
- "Content-Type": "application/x-www-form-urlencoded"
443
- },
444
- body: params.toString()
445
- });
446
- if (!response.ok) {
447
- const errorText = await response.text();
448
- console.error(`[Reddit API] Edit failed: ${response.status} ${response.statusText}`);
449
- console.error(`[Reddit API] Error response: ${errorText}`);
450
- throw new Error(`HTTP ${response.status}: ${errorText}`);
451
- }
452
- const json = await response.json();
453
- console.error(`[Reddit API] Edit response:`, JSON.stringify(json, null, 2));
454
- if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
455
- const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
456
- console.error(`[Reddit API] Edit errors: ${errors}`);
457
- throw new Error(`Reddit API errors: ${errors}`);
458
- }
459
- console.error(`[Reddit API] Successfully edited ${fullThingId}`);
460
- return true;
461
- } catch (error) {
462
- console.error(`[Reddit API] Edit exception:`, error);
463
- if (error instanceof Error && error.message.includes("HTTP")) {
464
- throw error;
465
- }
466
- throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
467
- }
468
- }
469
- async editComment(thingId, newText) {
470
- const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
471
- return this.editPost(fullThingId, newText);
472
- }
473
- async searchReddit(query, options = {}) {
474
- await this.authenticate();
475
- try {
476
- const { subreddit, sort = "relevance", timeFilter = "all", limit = 25, type = "link" } = options;
477
- const endpoint = subreddit ? `/r/${subreddit}/search.json` : "/search.json";
478
- const params = new URLSearchParams({
479
- q: query,
480
- sort,
481
- t: timeFilter,
482
- limit: limit.toString(),
483
- type,
484
- ...subreddit && { restrict_sr: "true" }
485
- });
486
- const response = await this.makeRequest(`${endpoint}?${params}`);
487
- if (!response.ok) {
488
- throw new Error(`HTTP ${response.status}`);
489
- }
490
- const json = await response.json();
491
- return json.data.children.filter((child) => child.kind === "t3").map((child) => {
492
- const post = child.data;
493
- return {
494
- id: post.id,
495
- title: post.title,
496
- author: post.author,
497
- subreddit: post.subreddit,
498
- selftext: post.selftext || "",
499
- url: post.url,
500
- score: post.score,
501
- upvoteRatio: post.upvote_ratio,
502
- numComments: post.num_comments,
503
- createdUtc: post.created_utc,
504
- over18: post.over_18,
505
- spoiler: post.spoiler,
506
- edited: !!post.edited,
507
- isSelf: post.is_self,
508
- linkFlairText: post.link_flair_text ?? void 0,
509
- permalink: post.permalink
510
- };
511
- });
512
- } catch {
513
- throw new Error(`Failed to search Reddit for: ${query}`);
514
- }
515
- }
516
- async getPostComments(postId, subreddit, options = {}) {
517
- await this.authenticate();
518
- try {
519
- const { sort = "best", limit = 100 } = options;
520
- const params = new URLSearchParams({
521
- sort,
522
- limit: limit.toString()
523
- });
524
- const response = await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`);
525
- if (!response.ok) {
526
- throw new Error(`HTTP ${response.status}`);
527
- }
528
- const json = await response.json();
529
- const postData = json[0].data.children[0].data;
530
- const post = {
531
- id: postData.id,
532
- title: postData.title,
533
- author: postData.author,
534
- subreddit: postData.subreddit,
535
- selftext: postData.selftext || "",
536
- url: postData.url,
537
- score: postData.score,
538
- upvoteRatio: postData.upvote_ratio,
539
- numComments: postData.num_comments,
540
- createdUtc: postData.created_utc,
541
- over18: postData.over_18,
542
- spoiler: postData.spoiler,
543
- edited: !!postData.edited,
544
- isSelf: postData.is_self,
545
- linkFlairText: postData.link_flair_text,
546
- permalink: postData.permalink
547
- };
548
- const comments = [];
549
- const parseComments = (commentData, depth = 0) => {
550
- for (const item of commentData) {
551
- if (item.kind === "t1" && item.data.body) {
552
- comments.push({
553
- id: item.data.id,
554
- author: item.data.author,
555
- body: item.data.body,
556
- score: item.data.score,
557
- controversiality: item.data.controversiality,
558
- subreddit: item.data.subreddit,
559
- submissionTitle: post.title,
560
- createdUtc: item.data.created_utc,
561
- edited: !!item.data.edited,
562
- isSubmitter: item.data.is_submitter,
563
- permalink: item.data.permalink,
564
- depth,
565
- parentId: item.data.parent_id
566
- });
567
- if (item.data.replies && item.data.replies.data && item.data.replies.data.children) {
568
- parseComments(item.data.replies.data.children, depth + 1);
569
- }
570
- }
571
- }
572
- };
573
- if (json[1] && json[1].data && json[1].data.children) {
574
- parseComments(json[1].data.children);
575
- }
576
- return { post, comments };
577
- } catch {
578
- throw new Error(`Failed to get comments for post ${postId}`);
579
- }
580
- }
581
- async getUserPosts(username, options = {}) {
582
- await this.authenticate();
583
- try {
584
- const { sort = "new", timeFilter = "all", limit = 25 } = options;
585
- const params = new URLSearchParams({
586
- sort,
587
- t: timeFilter,
588
- limit: limit.toString()
589
- });
590
- const response = await this.makeRequest(`/user/${username}/submitted.json?${params}`);
591
- if (!response.ok) {
592
- throw new Error(`HTTP ${response.status}`);
593
- }
594
- const json = await response.json();
595
- return json.data.children.filter((child) => child.kind === "t3").map((child) => {
596
- const post = child.data;
597
- return {
598
- id: post.id,
599
- title: post.title,
600
- author: post.author,
601
- subreddit: post.subreddit,
602
- selftext: post.selftext || "",
603
- url: post.url,
604
- score: post.score,
605
- upvoteRatio: post.upvote_ratio,
606
- numComments: post.num_comments,
607
- createdUtc: post.created_utc,
608
- over18: post.over_18,
609
- spoiler: post.spoiler,
610
- edited: !!post.edited,
611
- isSelf: post.is_self,
612
- linkFlairText: post.link_flair_text ?? void 0,
613
- permalink: post.permalink
614
- };
615
- });
616
- } catch {
617
- throw new Error(`Failed to get posts for user ${username}`);
618
- }
619
- }
620
- async getUserComments(username, options = {}) {
621
- await this.authenticate();
622
- try {
623
- const { sort = "new", timeFilter = "all", limit = 25 } = options;
624
- const params = new URLSearchParams({
625
- sort,
626
- t: timeFilter,
627
- limit: limit.toString()
628
- });
629
- const response = await this.makeRequest(`/user/${username}/comments.json?${params}`);
630
- if (!response.ok) {
631
- throw new Error(`HTTP ${response.status}`);
632
- }
633
- const json = await response.json();
634
- return json.data.children.filter((child) => child.kind === "t1").map((child) => {
635
- const comment = child.data;
636
- return {
637
- id: comment.id,
638
- author: comment.author,
639
- body: comment.body,
640
- score: comment.score,
641
- controversiality: comment.controversiality,
642
- subreddit: comment.subreddit,
643
- submissionTitle: comment.link_title || "",
644
- createdUtc: comment.created_utc,
645
- edited: !!comment.edited,
646
- isSubmitter: comment.is_submitter,
647
- permalink: comment.permalink
648
- };
649
- });
650
- } catch {
651
- throw new Error(`Failed to get comments for user ${username}`);
652
- }
653
- }
11
+ clientId;
12
+ clientSecret;
13
+ userAgent;
14
+ username;
15
+ password;
16
+ accessToken;
17
+ tokenExpiry = 0;
18
+ baseUrl;
19
+ authenticated = false;
20
+ authMode;
21
+ hasCredentials;
22
+ safeMode;
23
+ lastWriteTime = 0;
24
+ recentContentHashes = /* @__PURE__ */ new Set();
25
+ constructor(config) {
26
+ this.clientId = config.clientId;
27
+ this.clientSecret = config.clientSecret;
28
+ this.userAgent = config.userAgent;
29
+ this.username = config.username;
30
+ this.password = config.password;
31
+ this.authMode = config.authMode || "auto";
32
+ this.hasCredentials = !!(this.clientId && this.clientSecret);
33
+ this.baseUrl = this.determineBaseUrl();
34
+ this.safeMode = config.safeMode || {
35
+ enabled: false,
36
+ mode: "off",
37
+ writeDelayMs: 0,
38
+ duplicateCheck: false,
39
+ maxRecentHashes: 10
40
+ };
41
+ }
42
+ determineBaseUrl() {
43
+ switch (this.authMode) {
44
+ case "authenticated": return "https://oauth.reddit.com";
45
+ case "anonymous": return "https://www.reddit.com";
46
+ case "auto": return this.hasCredentials ? "https://oauth.reddit.com" : "https://www.reddit.com";
47
+ }
48
+ }
49
+ async makeRequest(path, options = {}) {
50
+ const requiresAuth = this.authMode === "authenticated" || this.authMode === "auto" && this.hasCredentials;
51
+ if (requiresAuth && (Date.now() >= this.tokenExpiry || !this.authenticated)) await this.authenticate();
52
+ const url = `${this.baseUrl}${path}`;
53
+ const headers = {
54
+ "User-Agent": this.userAgent,
55
+ ...options.headers
56
+ };
57
+ if (requiresAuth && this.accessToken) headers.Authorization = `Bearer ${this.accessToken}`;
58
+ const response = await fetch(url, {
59
+ ...options,
60
+ headers
61
+ });
62
+ if (response.status === 401 && this.authenticated) {
63
+ await this.authenticate();
64
+ const retryHeaders = {
65
+ ...headers,
66
+ Authorization: `Bearer ${this.accessToken}`
67
+ };
68
+ return fetch(url, {
69
+ ...options,
70
+ headers: retryHeaders
71
+ });
72
+ }
73
+ return response;
74
+ }
75
+ async authenticate() {
76
+ if (this.authMode === "anonymous") {
77
+ this.authenticated = false;
78
+ return;
79
+ }
80
+ if (this.authMode === "authenticated" && !this.hasCredentials) throw new Error("Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET");
81
+ if (this.authMode === "auto" && !this.hasCredentials) {
82
+ this.authenticated = false;
83
+ return;
84
+ }
85
+ try {
86
+ const now = Date.now();
87
+ if (this.accessToken && now < this.tokenExpiry) return;
88
+ const authUrl = "https://www.reddit.com/api/v1/access_token";
89
+ const authData = new URLSearchParams();
90
+ const username = this.username;
91
+ const password = this.password;
92
+ if (!!(username && password)) {
93
+ authData.append("grant_type", "password");
94
+ authData.append("username", username);
95
+ authData.append("password", password);
96
+ } else authData.append("grant_type", "client_credentials");
97
+ const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64");
98
+ const response = await fetch(authUrl, {
99
+ method: "POST",
100
+ headers: {
101
+ "User-Agent": this.userAgent,
102
+ "Content-Type": "application/x-www-form-urlencoded",
103
+ Authorization: `Basic ${credentials}`
104
+ },
105
+ body: authData.toString()
106
+ });
107
+ if (!response.ok) {
108
+ const statusText = response.statusText || "Unknown Error";
109
+ throw new Error(`Authentication failed: ${response.status} ${statusText}`);
110
+ }
111
+ const data = await response.json();
112
+ this.accessToken = data.access_token;
113
+ this.tokenExpiry = now + data.expires_in * 1e3;
114
+ this.authenticated = true;
115
+ } catch (error) {
116
+ if (error instanceof Error) throw error;
117
+ throw new Error("Failed to authenticate with Reddit API");
118
+ }
119
+ }
120
+ async checkAuthentication() {
121
+ if (!this.authenticated) try {
122
+ await this.authenticate();
123
+ return true;
124
+ } catch {
125
+ return false;
126
+ }
127
+ return true;
128
+ }
129
+ validateWriteAccess() {
130
+ if (!this.username || !this.password) {
131
+ if (this.authMode === "anonymous") throw new Error("Write operations not available in anonymous mode. Set REDDIT_USERNAME, REDDIT_PASSWORD and use 'auto' or 'authenticated' mode.");
132
+ throw new Error("Write operations require REDDIT_USERNAME and REDDIT_PASSWORD");
133
+ }
134
+ }
135
+ async enforceWriteRateLimit() {
136
+ if (!this.safeMode.enabled || this.safeMode.writeDelayMs <= 0) return;
137
+ const elapsed = Date.now() - this.lastWriteTime;
138
+ if (elapsed < this.safeMode.writeDelayMs) {
139
+ const waitTime = this.safeMode.writeDelayMs - elapsed;
140
+ console.error(`[SafeMode] Rate limit: waiting ${waitTime}ms before write operation`);
141
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
142
+ }
143
+ this.lastWriteTime = Date.now();
144
+ }
145
+ hashContent(content) {
146
+ return crypto.default.createHash("md5").update(content.trim().toLowerCase()).digest("hex");
147
+ }
148
+ checkDuplicateContent(content) {
149
+ if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) return;
150
+ const hash = this.hashContent(content);
151
+ if (this.recentContentHashes.has(hash)) throw new Error("Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. Please modify your content and try again.");
152
+ this.recentContentHashes.add(hash);
153
+ if (this.recentContentHashes.size > this.safeMode.maxRecentHashes) {
154
+ const first = this.recentContentHashes.values().next().value;
155
+ if (first) this.recentContentHashes.delete(first);
156
+ }
157
+ }
158
+ async getUser(username) {
159
+ try {
160
+ const response = await this.makeRequest(`/user/${username}/about.json`);
161
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
162
+ const data = (await response.json()).data;
163
+ return {
164
+ name: data.name,
165
+ id: data.id,
166
+ commentKarma: data.comment_karma,
167
+ linkKarma: data.link_karma,
168
+ totalKarma: data.total_karma || data.comment_karma + data.link_karma,
169
+ isMod: data.is_mod,
170
+ isGold: data.is_gold,
171
+ isEmployee: data.is_employee,
172
+ createdUtc: data.created_utc,
173
+ profileUrl: `https://reddit.com/user/${data.name}`
174
+ };
175
+ } catch {
176
+ throw new Error(`Failed to get user info for ${username}`);
177
+ }
178
+ }
179
+ async getSubredditInfo(subredditName) {
180
+ try {
181
+ const response = await this.makeRequest(`/r/${subredditName}/about.json`);
182
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
183
+ const data = (await response.json()).data;
184
+ return {
185
+ displayName: data.display_name,
186
+ title: data.title,
187
+ description: data.description || "",
188
+ publicDescription: data.public_description || "",
189
+ subscribers: data.subscribers,
190
+ activeUserCount: data.active_user_count ?? void 0,
191
+ createdUtc: data.created_utc,
192
+ over18: data.over18,
193
+ subredditType: data.subreddit_type,
194
+ url: data.url
195
+ };
196
+ } catch {
197
+ throw new Error(`Failed to get subreddit info for ${subredditName}`);
198
+ }
199
+ }
200
+ async getTopPosts(subreddit, timeFilter = "week", limit = 10) {
201
+ try {
202
+ const endpoint = subreddit ? `/r/${subreddit}/top.json` : "/top.json";
203
+ const params = new URLSearchParams({
204
+ t: timeFilter,
205
+ limit: limit.toString()
206
+ });
207
+ const response = await this.makeRequest(`${endpoint}?${params}`);
208
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
209
+ return (await response.json()).data.children.map((child) => {
210
+ const post = child.data;
211
+ return {
212
+ id: post.id,
213
+ title: post.title,
214
+ author: post.author,
215
+ subreddit: post.subreddit,
216
+ selftext: post.selftext,
217
+ url: post.url,
218
+ score: post.score,
219
+ upvoteRatio: post.upvote_ratio,
220
+ numComments: post.num_comments,
221
+ createdUtc: post.created_utc,
222
+ over18: post.over_18,
223
+ spoiler: post.spoiler,
224
+ edited: !!post.edited,
225
+ isSelf: post.is_self,
226
+ linkFlairText: post.link_flair_text ?? void 0,
227
+ permalink: post.permalink
228
+ };
229
+ });
230
+ } catch {
231
+ throw new Error(`Failed to get top posts for ${subreddit || "home"}`);
232
+ }
233
+ }
234
+ async getPost(postId, subreddit) {
235
+ try {
236
+ const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`;
237
+ const response = await this.makeRequest(endpoint);
238
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
239
+ let post;
240
+ if (subreddit) post = (await response.json())[0].data.children[0].data;
241
+ else {
242
+ const json = await response.json();
243
+ if (!json.data.children.length) throw new Error(`Post with ID ${postId} not found`);
244
+ post = json.data.children[0].data;
245
+ }
246
+ return {
247
+ id: post.id,
248
+ title: post.title,
249
+ author: post.author,
250
+ subreddit: post.subreddit,
251
+ selftext: post.selftext,
252
+ url: post.url,
253
+ score: post.score,
254
+ upvoteRatio: post.upvote_ratio,
255
+ numComments: post.num_comments,
256
+ createdUtc: post.created_utc,
257
+ over18: post.over_18,
258
+ spoiler: post.spoiler,
259
+ edited: !!post.edited,
260
+ isSelf: post.is_self,
261
+ linkFlairText: post.link_flair_text ?? void 0,
262
+ permalink: post.permalink
263
+ };
264
+ } catch {
265
+ throw new Error(`Failed to get post with ID ${postId}`);
266
+ }
267
+ }
268
+ async getTrendingSubreddits(limit = 5) {
269
+ try {
270
+ const params = new URLSearchParams({ limit: limit.toString() });
271
+ const response = await this.makeRequest(`/subreddits/popular.json?${params}`);
272
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
273
+ return (await response.json()).data.children.map((child) => child.data.display_name);
274
+ } catch {
275
+ throw new Error("Failed to get trending subreddits");
276
+ }
277
+ }
278
+ async createPost(subreddit, title, content, isSelf = true) {
279
+ this.validateWriteAccess();
280
+ await this.enforceWriteRateLimit();
281
+ this.checkDuplicateContent(title + content);
282
+ try {
283
+ var _json$json, _json$json2, _json$json3;
284
+ const kind = isSelf ? "self" : "link";
285
+ const params = new URLSearchParams();
286
+ params.append("sr", subreddit);
287
+ params.append("kind", kind);
288
+ params.append("title", title);
289
+ params.append(isSelf ? "text" : "url", content);
290
+ params.append("api_type", "json");
291
+ const response = await this.makeRequest("/api/submit", {
292
+ method: "POST",
293
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
294
+ body: params.toString()
295
+ });
296
+ if (!response.ok) {
297
+ const errorText = await response.text();
298
+ console.error(`[Reddit API] Create post failed: ${response.status} ${response.statusText}`);
299
+ console.error(`[Reddit API] Error response: ${errorText}`);
300
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
301
+ }
302
+ const json = await response.json();
303
+ console.error(`[Reddit API] Create post response:`, JSON.stringify(json, null, 2));
304
+ if (((_json$json = json.json) === null || _json$json === void 0 ? void 0 : _json$json.errors) && json.json.errors.length > 0) {
305
+ const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
306
+ console.error(`[Reddit API] Post creation errors: ${errors}`);
307
+ throw new Error(`Reddit API errors: ${errors}`);
308
+ }
309
+ const postId = ((_json$json2 = json.json) === null || _json$json2 === void 0 || (_json$json2 = _json$json2.data) === null || _json$json2 === void 0 ? void 0 : _json$json2.id) || ((_json$json3 = json.json) === null || _json$json3 === void 0 || (_json$json3 = _json$json3.data) === null || _json$json3 === void 0 || (_json$json3 = _json$json3.name) === null || _json$json3 === void 0 ? void 0 : _json$json3.replace("t3_", ""));
310
+ if (!postId) {
311
+ console.error(`[Reddit API] No post ID in response`);
312
+ throw new Error("No post ID returned from Reddit");
313
+ }
314
+ console.error(`[Reddit API] Post created with ID: ${postId}`);
315
+ return await this.getPost(postId, subreddit);
316
+ } catch (error) {
317
+ console.error(`[Reddit API] Create post exception:`, error);
318
+ if (error instanceof Error && error.message.includes("HTTP")) throw error;
319
+ throw new Error(`Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`);
320
+ }
321
+ }
322
+ async checkPostExists(postId) {
323
+ try {
324
+ const response = await this.makeRequest(`/api/info.json?id=t3_${postId}`);
325
+ if (!response.ok) return false;
326
+ return (await response.json()).data.children.length > 0;
327
+ } catch {
328
+ return false;
329
+ }
330
+ }
331
+ async replyToPost(postId, content) {
332
+ this.validateWriteAccess();
333
+ await this.enforceWriteRateLimit();
334
+ this.checkDuplicateContent(content);
335
+ try {
336
+ var _json$json4, _json$json5;
337
+ if (!await this.checkPostExists(postId)) throw new Error(`Post with ID ${postId} does not exist or is not accessible`);
338
+ const params = new URLSearchParams();
339
+ params.append("thing_id", `t3_${postId}`);
340
+ params.append("text", content);
341
+ params.append("api_type", "json");
342
+ const response = await this.makeRequest("/api/comment", {
343
+ method: "POST",
344
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
345
+ body: params.toString()
346
+ });
347
+ if (!response.ok) {
348
+ const errorText = await response.text();
349
+ console.error(`[Reddit API] Reply to post failed: ${response.status} ${response.statusText}`);
350
+ console.error(`[Reddit API] Error response: ${errorText}`);
351
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
352
+ }
353
+ const json = await response.json();
354
+ console.error(`[Reddit API] Reply response:`, JSON.stringify(json, null, 2));
355
+ if (((_json$json4 = json.json) === null || _json$json4 === void 0 || (_json$json4 = _json$json4.data) === null || _json$json4 === void 0 ? void 0 : _json$json4.things) && json.json.data.things.length > 0) {
356
+ const commentData = json.json.data.things[0].data;
357
+ const author = this.username ?? "[unknown]";
358
+ return {
359
+ id: commentData.id,
360
+ author,
361
+ body: content,
362
+ score: 1,
363
+ controversiality: 0,
364
+ subreddit: commentData.subreddit,
365
+ submissionTitle: commentData.link_title ?? "",
366
+ createdUtc: Date.now() / 1e3,
367
+ edited: false,
368
+ isSubmitter: false,
369
+ permalink: commentData.permalink
370
+ };
371
+ } else if (((_json$json5 = json.json) === null || _json$json5 === void 0 ? void 0 : _json$json5.errors) && json.json.errors.length > 0) {
372
+ const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
373
+ console.error(`[Reddit API] Reply errors: ${errors}`);
374
+ throw new Error(`Reddit API errors: ${errors}`);
375
+ } else {
376
+ console.error(`[Reddit API] Unexpected reply response format`);
377
+ throw new Error("Failed to parse reply response");
378
+ }
379
+ } catch (error) {
380
+ console.error(`[Reddit API] Reply to post exception:`, error);
381
+ if (error instanceof Error && error.message.includes("HTTP")) throw error;
382
+ throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`);
383
+ }
384
+ }
385
+ async deletePost(thingId) {
386
+ this.validateWriteAccess();
387
+ try {
388
+ const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
389
+ const params = new URLSearchParams();
390
+ params.append("id", fullThingId);
391
+ const response = await this.makeRequest("/api/del", {
392
+ method: "POST",
393
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
394
+ body: params.toString()
395
+ });
396
+ if (!response.ok) {
397
+ const errorText = await response.text();
398
+ console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`);
399
+ console.error(`[Reddit API] Error response: ${errorText}`);
400
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
401
+ }
402
+ console.error(`[Reddit API] Successfully deleted ${fullThingId}`);
403
+ return true;
404
+ } catch (error) {
405
+ console.error(`[Reddit API] Delete exception:`, error);
406
+ if (error instanceof Error && error.message.includes("HTTP")) throw error;
407
+ throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
408
+ }
409
+ }
410
+ async deleteComment(thingId) {
411
+ const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
412
+ return this.deletePost(fullThingId);
413
+ }
414
+ async editPost(thingId, newText) {
415
+ this.validateWriteAccess();
416
+ await this.enforceWriteRateLimit();
417
+ this.checkDuplicateContent(newText);
418
+ try {
419
+ var _json$json6;
420
+ const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
421
+ const params = new URLSearchParams();
422
+ params.append("thing_id", fullThingId);
423
+ params.append("text", newText);
424
+ params.append("api_type", "json");
425
+ const response = await this.makeRequest("/api/editusertext", {
426
+ method: "POST",
427
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
428
+ body: params.toString()
429
+ });
430
+ if (!response.ok) {
431
+ const errorText = await response.text();
432
+ console.error(`[Reddit API] Edit failed: ${response.status} ${response.statusText}`);
433
+ console.error(`[Reddit API] Error response: ${errorText}`);
434
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
435
+ }
436
+ const json = await response.json();
437
+ console.error(`[Reddit API] Edit response:`, JSON.stringify(json, null, 2));
438
+ if (((_json$json6 = json.json) === null || _json$json6 === void 0 ? void 0 : _json$json6.errors) && json.json.errors.length > 0) {
439
+ const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
440
+ console.error(`[Reddit API] Edit errors: ${errors}`);
441
+ throw new Error(`Reddit API errors: ${errors}`);
442
+ }
443
+ console.error(`[Reddit API] Successfully edited ${fullThingId}`);
444
+ return true;
445
+ } catch (error) {
446
+ console.error(`[Reddit API] Edit exception:`, error);
447
+ if (error instanceof Error && error.message.includes("HTTP")) throw error;
448
+ throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
449
+ }
450
+ }
451
+ async editComment(thingId, newText) {
452
+ const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
453
+ return this.editPost(fullThingId, newText);
454
+ }
455
+ async searchReddit(query, options = {}) {
456
+ try {
457
+ const { subreddit, sort = "relevance", timeFilter = "all", limit = 25, type = "link" } = options;
458
+ const endpoint = subreddit ? `/r/${subreddit}/search.json` : "/search.json";
459
+ const params = new URLSearchParams({
460
+ q: query,
461
+ sort,
462
+ t: timeFilter,
463
+ limit: limit.toString(),
464
+ type,
465
+ ...subreddit && { restrict_sr: "true" }
466
+ });
467
+ const response = await this.makeRequest(`${endpoint}?${params}`);
468
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
469
+ return (await response.json()).data.children.filter((child) => child.kind === "t3").map((child) => {
470
+ const post = child.data;
471
+ return {
472
+ id: post.id,
473
+ title: post.title,
474
+ author: post.author,
475
+ subreddit: post.subreddit,
476
+ selftext: post.selftext || "",
477
+ url: post.url,
478
+ score: post.score,
479
+ upvoteRatio: post.upvote_ratio,
480
+ numComments: post.num_comments,
481
+ createdUtc: post.created_utc,
482
+ over18: post.over_18,
483
+ spoiler: post.spoiler,
484
+ edited: !!post.edited,
485
+ isSelf: post.is_self,
486
+ linkFlairText: post.link_flair_text ?? void 0,
487
+ permalink: post.permalink
488
+ };
489
+ });
490
+ } catch {
491
+ throw new Error(`Failed to search Reddit for: ${query}`);
492
+ }
493
+ }
494
+ async getPostComments(postId, subreddit, options = {}) {
495
+ try {
496
+ var _json$;
497
+ const { sort = "best", limit = 100 } = options;
498
+ const params = new URLSearchParams({
499
+ sort,
500
+ limit: limit.toString()
501
+ });
502
+ const response = await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`);
503
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
504
+ const json = await response.json();
505
+ const postData = json[0].data.children[0].data;
506
+ const post = {
507
+ id: postData.id,
508
+ title: postData.title,
509
+ author: postData.author,
510
+ subreddit: postData.subreddit,
511
+ selftext: postData.selftext || "",
512
+ url: postData.url,
513
+ score: postData.score,
514
+ upvoteRatio: postData.upvote_ratio,
515
+ numComments: postData.num_comments,
516
+ createdUtc: postData.created_utc,
517
+ over18: postData.over_18,
518
+ spoiler: postData.spoiler,
519
+ edited: !!postData.edited,
520
+ isSelf: postData.is_self,
521
+ linkFlairText: postData.link_flair_text ?? void 0,
522
+ permalink: postData.permalink
523
+ };
524
+ const comments = [];
525
+ const parseComments = (commentData, depth = 0) => {
526
+ for (const item of commentData) if (item.kind === "t1" && item.data.body) {
527
+ var _replies$data;
528
+ comments.push({
529
+ id: item.data.id,
530
+ author: item.data.author,
531
+ body: item.data.body,
532
+ score: item.data.score,
533
+ controversiality: item.data.controversiality,
534
+ subreddit: item.data.subreddit,
535
+ submissionTitle: post.title,
536
+ createdUtc: item.data.created_utc,
537
+ edited: !!item.data.edited,
538
+ isSubmitter: item.data.is_submitter,
539
+ permalink: item.data.permalink,
540
+ depth,
541
+ parentId: item.data.parent_id
542
+ });
543
+ const replies = item.data.replies;
544
+ if (replies && typeof replies !== "string" && ((_replies$data = replies.data) === null || _replies$data === void 0 ? void 0 : _replies$data.children)) parseComments(replies.data.children, depth + 1);
545
+ }
546
+ };
547
+ if ((_json$ = json[1]) === null || _json$ === void 0 || (_json$ = _json$.data) === null || _json$ === void 0 ? void 0 : _json$.children) parseComments(json[1].data.children);
548
+ return {
549
+ post,
550
+ comments
551
+ };
552
+ } catch {
553
+ throw new Error(`Failed to get comments for post ${postId}`);
554
+ }
555
+ }
556
+ async getUserPosts(username, options = {}) {
557
+ try {
558
+ const { sort = "new", timeFilter = "all", limit = 25 } = options;
559
+ const params = new URLSearchParams({
560
+ sort,
561
+ t: timeFilter,
562
+ limit: limit.toString()
563
+ });
564
+ const response = await this.makeRequest(`/user/${username}/submitted.json?${params}`);
565
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
566
+ return (await response.json()).data.children.filter((child) => child.kind === "t3").map((child) => {
567
+ const post = child.data;
568
+ return {
569
+ id: post.id,
570
+ title: post.title,
571
+ author: post.author,
572
+ subreddit: post.subreddit,
573
+ selftext: post.selftext || "",
574
+ url: post.url,
575
+ score: post.score,
576
+ upvoteRatio: post.upvote_ratio,
577
+ numComments: post.num_comments,
578
+ createdUtc: post.created_utc,
579
+ over18: post.over_18,
580
+ spoiler: post.spoiler,
581
+ edited: !!post.edited,
582
+ isSelf: post.is_self,
583
+ linkFlairText: post.link_flair_text ?? void 0,
584
+ permalink: post.permalink
585
+ };
586
+ });
587
+ } catch {
588
+ throw new Error(`Failed to get posts for user ${username}`);
589
+ }
590
+ }
591
+ async getUserComments(username, options = {}) {
592
+ try {
593
+ const { sort = "new", timeFilter = "all", limit = 25 } = options;
594
+ const params = new URLSearchParams({
595
+ sort,
596
+ t: timeFilter,
597
+ limit: limit.toString()
598
+ });
599
+ const response = await this.makeRequest(`/user/${username}/comments.json?${params}`);
600
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
601
+ return (await response.json()).data.children.filter((child) => child.kind === "t1").map((child) => {
602
+ const comment = child.data;
603
+ return {
604
+ id: comment.id,
605
+ author: comment.author,
606
+ body: comment.body ?? "",
607
+ score: comment.score,
608
+ controversiality: comment.controversiality,
609
+ subreddit: comment.subreddit,
610
+ submissionTitle: comment.link_title ?? "",
611
+ createdUtc: comment.created_utc,
612
+ edited: !!comment.edited,
613
+ isSubmitter: comment.is_submitter,
614
+ permalink: comment.permalink
615
+ };
616
+ });
617
+ } catch {
618
+ throw new Error(`Failed to get comments for user ${username}`);
619
+ }
620
+ }
654
621
  };
655
- var redditClient = null;
622
+ let redditClient = null;
656
623
  function initializeRedditClient(config) {
657
- redditClient = new RedditClient(config);
658
- return redditClient;
624
+ redditClient = new RedditClient(config);
625
+ return redditClient;
659
626
  }
660
627
  function getRedditClient() {
661
- return redditClient;
628
+ return redditClient;
662
629
  }
663
630
 
664
- // src/utils/formatters.ts
631
+ //#endregion
632
+ //#region src/utils/formatters.ts
665
633
  function formatTimestamp(timestamp) {
666
- try {
667
- const date = new Date(timestamp * 1e3);
668
- return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
669
- } catch {
670
- return String(timestamp);
671
- }
634
+ try {
635
+ return (/* @__PURE__ */ new Date(timestamp * 1e3)).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
636
+ } catch {
637
+ return String(timestamp);
638
+ }
672
639
  }
673
640
  function analyzeUserActivity(karmaRatio, isMod, accountAgeDays) {
674
- const insights = [];
675
- if (karmaRatio > 5) {
676
- insights.push("Primarily a commenter, highly engaged in discussions");
677
- } else if (karmaRatio < 0.2) {
678
- insights.push("Content creator, focuses on sharing posts");
679
- } else {
680
- insights.push("Balanced participation in both posting and commenting");
681
- }
682
- if (accountAgeDays < 30) {
683
- insights.push("New user, still exploring Reddit");
684
- } else if (accountAgeDays > 365 * 5) {
685
- insights.push("Long-time Redditor with extensive platform experience");
686
- }
687
- if (isMod) {
688
- insights.push("Community leader who helps maintain subreddit quality");
689
- }
690
- return insights.join("\n - ");
641
+ const insights = [];
642
+ if (karmaRatio > 5) insights.push("Primarily a commenter, highly engaged in discussions");
643
+ else if (karmaRatio < .2) insights.push("Content creator, focuses on sharing posts");
644
+ else insights.push("Balanced participation in both posting and commenting");
645
+ if (accountAgeDays < 30) insights.push("New user, still exploring Reddit");
646
+ else if (accountAgeDays > 365 * 5) insights.push("Long-time Redditor with extensive platform experience");
647
+ if (isMod) insights.push("Community leader who helps maintain subreddit quality");
648
+ return insights.join("\n - ");
691
649
  }
692
650
  function analyzePostEngagement(score, ratio, numComments) {
693
- const insights = [];
694
- if (score > 1e3 && ratio > 0.95) {
695
- insights.push("Highly successful post with strong community approval");
696
- } else if (score > 100 && ratio > 0.8) {
697
- insights.push("Well-received post with good engagement");
698
- } else if (ratio < 0.5) {
699
- insights.push("Controversial post that sparked debate");
700
- }
701
- if (numComments > 100) {
702
- insights.push("Generated significant discussion");
703
- } else if (numComments > score * 0.5) {
704
- insights.push("Highly discussable content with active comment section");
705
- } else if (numComments === 0) {
706
- insights.push("Yet to receive community interaction");
707
- }
708
- return insights.join("\n - ");
651
+ const insights = [];
652
+ if (score > 1e3 && ratio > .95) insights.push("Highly successful post with strong community approval");
653
+ else if (score > 100 && ratio > .8) insights.push("Well-received post with good engagement");
654
+ else if (ratio < .5) insights.push("Controversial post that sparked debate");
655
+ if (numComments > 100) insights.push("Generated significant discussion");
656
+ else if (numComments > score * .5) insights.push("Highly discussable content with active comment section");
657
+ else if (numComments === 0) insights.push("Yet to receive community interaction");
658
+ return insights.join("\n - ");
709
659
  }
710
660
  function analyzeSubredditHealth(subscribers, activeUsers, ageDays) {
711
- const insights = [];
712
- if (subscribers > 1e6) {
713
- insights.push("Major subreddit with massive following");
714
- } else if (subscribers > 1e5) {
715
- insights.push("Well-established community");
716
- } else if (subscribers < 1e3) {
717
- insights.push("Niche community, potential for growth");
718
- }
719
- if (activeUsers) {
720
- const activityRatio = activeUsers / subscribers;
721
- if (activityRatio > 0.1) {
722
- insights.push("Highly active community with strong engagement");
723
- } else if (activityRatio < 0.01) {
724
- insights.push("Could benefit from more community engagement initiatives");
725
- }
726
- }
727
- if (ageDays > 365 * 5) {
728
- insights.push("Mature subreddit with established culture");
729
- } else if (ageDays < 90) {
730
- insights.push("New subreddit still forming its community");
731
- }
732
- return insights.join("\n - ");
661
+ const insights = [];
662
+ if (subscribers > 1e6) insights.push("Major subreddit with massive following");
663
+ else if (subscribers > 1e5) insights.push("Well-established community");
664
+ else if (subscribers < 1e3) insights.push("Niche community, potential for growth");
665
+ if (activeUsers) {
666
+ const activityRatio = activeUsers / subscribers;
667
+ if (activityRatio > .1) insights.push("Highly active community with strong engagement");
668
+ else if (activityRatio < .01) insights.push("Could benefit from more community engagement initiatives");
669
+ }
670
+ if (ageDays > 365 * 5) insights.push("Mature subreddit with established culture");
671
+ else if (ageDays < 90) insights.push("New subreddit still forming its community");
672
+ return insights.join("\n - ");
733
673
  }
734
674
  function getUserRecommendations(karmaRatio, isMod, accountAgeDays) {
735
- const recommendations = [];
736
- if (karmaRatio > 5) {
737
- recommendations.push("Consider creating more posts to share your expertise");
738
- } else if (karmaRatio < 0.2) {
739
- recommendations.push("Engage more in discussions to build community connections");
740
- }
741
- if (accountAgeDays < 30) {
742
- recommendations.push("Explore popular subreddits in your areas of interest");
743
- recommendations.push("Read community guidelines before posting");
744
- }
745
- if (isMod) {
746
- recommendations.push("Share moderation insights with other community leaders");
747
- }
748
- if (!recommendations.length) {
749
- recommendations.push("Maintain your balanced engagement across Reddit");
750
- }
751
- return recommendations.join("\n - ");
675
+ const recommendations = [];
676
+ if (karmaRatio > 5) recommendations.push("Consider creating more posts to share your expertise");
677
+ else if (karmaRatio < .2) recommendations.push("Engage more in discussions to build community connections");
678
+ if (accountAgeDays < 30) {
679
+ recommendations.push("Explore popular subreddits in your areas of interest");
680
+ recommendations.push("Read community guidelines before posting");
681
+ }
682
+ if (isMod) recommendations.push("Share moderation insights with other community leaders");
683
+ if (!recommendations.length) recommendations.push("Maintain your balanced engagement across Reddit");
684
+ return recommendations.join("\n - ");
752
685
  }
753
686
  function getBestEngagementTime(createdUtc) {
754
- const postHour = new Date(createdUtc * 1e3).getHours();
755
- if (14 <= postHour && postHour <= 18) {
756
- return "Posted during peak engagement hours (2 PM - 6 PM), good timing!";
757
- } else if (23 <= postHour || postHour <= 5) {
758
- return "Consider posting during more active hours (morning to evening)";
759
- } else {
760
- return "Posted during moderate activity hours, timing could be optimized";
761
- }
687
+ const postHour = (/* @__PURE__ */ new Date(createdUtc * 1e3)).getHours();
688
+ if (14 <= postHour && postHour <= 18) return "Posted during peak engagement hours (2 PM - 6 PM), good timing!";
689
+ else if (23 <= postHour || postHour <= 5) return "Consider posting during more active hours (morning to evening)";
690
+ else return "Posted during moderate activity hours, timing could be optimized";
762
691
  }
763
692
  function getSubredditEngagementTips(subreddit) {
764
- const tips = [];
765
- if (subreddit.subscribers > 1e6) {
766
- tips.push("Post during peak hours for maximum visibility");
767
- tips.push("Ensure content is highly polished due to high competition");
768
- } else if (subreddit.subscribers < 1e3) {
769
- tips.push("Engage actively to help grow the community");
770
- tips.push("Consider cross-posting to related larger subreddits");
771
- }
772
- if (subreddit.activeUserCount) {
773
- const activityRatio = subreddit.activeUserCount / subreddit.subscribers;
774
- if (activityRatio > 0.1) {
775
- tips.push("Quick responses recommended due to high activity");
776
- }
777
- }
778
- return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence";
693
+ const tips = [];
694
+ if (subreddit.subscribers > 1e6) {
695
+ tips.push("Post during peak hours for maximum visibility");
696
+ tips.push("Ensure content is highly polished due to high competition");
697
+ } else if (subreddit.subscribers < 1e3) {
698
+ tips.push("Engage actively to help grow the community");
699
+ tips.push("Consider cross-posting to related larger subreddits");
700
+ }
701
+ if (subreddit.activeUserCount) {
702
+ if (subreddit.activeUserCount / subreddit.subscribers > .1) tips.push("Quick responses recommended due to high activity");
703
+ }
704
+ return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence";
779
705
  }
780
706
  function formatUserInfo(user) {
781
- const status = [];
782
- if (user.isMod) status.push("Moderator");
783
- if (user.isGold) status.push("Reddit Gold Member");
784
- if (user.isEmployee) status.push("Reddit Employee");
785
- const accountAgeDays = (Date.now() / 1e3 - user.createdUtc) / (24 * 3600);
786
- const karmaRatio = user.commentKarma / (user.linkKarma || 1);
787
- return {
788
- username: user.name,
789
- karma: {
790
- commentKarma: user.commentKarma,
791
- postKarma: user.linkKarma,
792
- totalKarma: user.totalKarma
793
- },
794
- accountStatus: status.length ? status : ["Regular User"],
795
- accountCreated: formatTimestamp(user.createdUtc),
796
- profileUrl: user.profileUrl,
797
- activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),
798
- recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays)
799
- };
707
+ const status = [];
708
+ if (user.isMod) status.push("Moderator");
709
+ if (user.isGold) status.push("Reddit Gold Member");
710
+ if (user.isEmployee) status.push("Reddit Employee");
711
+ const accountAgeDays = (Date.now() / 1e3 - user.createdUtc) / (24 * 3600);
712
+ const karmaRatio = user.commentKarma / (user.linkKarma || 1);
713
+ return {
714
+ username: user.name,
715
+ karma: {
716
+ commentKarma: user.commentKarma,
717
+ postKarma: user.linkKarma,
718
+ totalKarma: user.totalKarma
719
+ },
720
+ accountStatus: status.length ? status : ["Regular User"],
721
+ accountCreated: formatTimestamp(user.createdUtc),
722
+ profileUrl: user.profileUrl,
723
+ activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),
724
+ recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays)
725
+ };
800
726
  }
801
727
  function formatPostInfo(post) {
802
- const contentType = post.isSelf ? "Text Post" : "Link Post";
803
- const content = post.isSelf ? post.selftext || "" : post.url || "";
804
- const flags = [];
805
- if (post.over18) flags.push("NSFW");
806
- if (post.spoiler) flags.push("Spoiler");
807
- if (post.edited) flags.push("Edited");
808
- return {
809
- title: post.title,
810
- type: contentType,
811
- content: content.length > 300 ? content.substring(0, 297) + "..." : content,
812
- author: post.author,
813
- subreddit: post.subreddit,
814
- stats: {
815
- score: post.score,
816
- upvoteRatio: post.upvoteRatio,
817
- comments: post.numComments
818
- },
819
- metadata: {
820
- posted: formatTimestamp(post.createdUtc),
821
- flags,
822
- flair: post.linkFlairText || "None"
823
- },
824
- links: {
825
- fullPost: `https://reddit.com${post.permalink}`,
826
- shortLink: `https://redd.it/${post.id}`
827
- },
828
- engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),
829
- bestTimeToEngage: getBestEngagementTime(post.createdUtc)
830
- };
728
+ const contentType = post.isSelf ? "Text Post" : "Link Post";
729
+ const content = post.isSelf ? post.selftext || "" : post.url || "";
730
+ const flags = [];
731
+ if (post.over18) flags.push("NSFW");
732
+ if (post.spoiler) flags.push("Spoiler");
733
+ if (post.edited) flags.push("Edited");
734
+ return {
735
+ title: post.title,
736
+ type: contentType,
737
+ content: content.length > 300 ? content.substring(0, 297) + "..." : content,
738
+ author: post.author,
739
+ subreddit: post.subreddit,
740
+ stats: {
741
+ score: post.score,
742
+ upvoteRatio: post.upvoteRatio,
743
+ comments: post.numComments
744
+ },
745
+ metadata: {
746
+ posted: formatTimestamp(post.createdUtc),
747
+ flags,
748
+ flair: post.linkFlairText || "None"
749
+ },
750
+ links: {
751
+ fullPost: `https://reddit.com${post.permalink}`,
752
+ shortLink: `https://redd.it/${post.id}`
753
+ },
754
+ engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),
755
+ bestTimeToEngage: getBestEngagementTime(post.createdUtc)
756
+ };
831
757
  }
832
758
  function formatSubredditInfo(subreddit) {
833
- const flags = [];
834
- if (subreddit.over18) flags.push("NSFW");
835
- if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`);
836
- const ageDays = (Date.now() / 1e3 - subreddit.createdUtc) / (24 * 3600);
837
- return {
838
- name: subreddit.displayName,
839
- title: subreddit.title,
840
- stats: {
841
- subscribers: subreddit.subscribers,
842
- activeUsers: subreddit.activeUserCount !== void 0 ? subreddit.activeUserCount : "Unknown"
843
- },
844
- description: {
845
- short: subreddit.publicDescription,
846
- full: subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description
847
- },
848
- metadata: {
849
- created: formatTimestamp(subreddit.createdUtc),
850
- flags: flags.length ? flags : ["None"]
851
- },
852
- links: {
853
- subreddit: `https://reddit.com${subreddit.url}`,
854
- wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`
855
- },
856
- communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),
857
- engagementTips: getSubredditEngagementTips(subreddit)
858
- };
759
+ const flags = [];
760
+ if (subreddit.over18) flags.push("NSFW");
761
+ if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`);
762
+ const ageDays = (Date.now() / 1e3 - subreddit.createdUtc) / (24 * 3600);
763
+ return {
764
+ name: subreddit.displayName,
765
+ title: subreddit.title,
766
+ stats: {
767
+ subscribers: subreddit.subscribers,
768
+ activeUsers: subreddit.activeUserCount !== void 0 ? subreddit.activeUserCount : "Unknown"
769
+ },
770
+ description: {
771
+ short: subreddit.publicDescription,
772
+ full: subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description
773
+ },
774
+ metadata: {
775
+ created: formatTimestamp(subreddit.createdUtc),
776
+ flags: flags.length ? flags : ["None"]
777
+ },
778
+ links: {
779
+ subreddit: `https://reddit.com${subreddit.url}`,
780
+ wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`
781
+ },
782
+ communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),
783
+ engagementTips: getSubredditEngagementTips(subreddit)
784
+ };
859
785
  }
860
786
 
861
- // src/index.ts
862
- var import_dotenv = __toESM(require("dotenv"));
863
- import_dotenv.default.config();
787
+ //#endregion
788
+ //#region src/index.ts
789
+ dotenv.default.config();
790
+ const VERSION = "1.2.1";
791
+ function validateUserAgent(userAgent, username) {
792
+ if (!/^[\w-]+:[\w-]+:[\d.]+ \(by \/u\/\w+\)$/.test(userAgent)) {
793
+ console.error("[Warning] User-Agent does not follow Reddit's recommended format");
794
+ console.error("[Warning] Recommended: 'platform:app_id:version (by /u/username)'");
795
+ console.error("[Warning] Non-standard User-Agents may increase ban risk");
796
+ if (username) console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`);
797
+ }
798
+ }
799
+ function buildUserAgent(customAgent, username) {
800
+ if (customAgent) {
801
+ validateUserAgent(customAgent, username);
802
+ return customAgent;
803
+ }
804
+ if (username) {
805
+ const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`;
806
+ console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`);
807
+ return autoAgent;
808
+ }
809
+ const fallbackAgent = `RedditMCPServer/${VERSION}`;
810
+ validateUserAgent(fallbackAgent);
811
+ return fallbackAgent;
812
+ }
813
+ function buildSafeModeConfig(safeMode) {
814
+ switch (safeMode) {
815
+ case "off": return {
816
+ enabled: false,
817
+ mode: "off",
818
+ writeDelayMs: 0,
819
+ duplicateCheck: false,
820
+ maxRecentHashes: 10
821
+ };
822
+ case "standard": return {
823
+ enabled: true,
824
+ mode: "standard",
825
+ writeDelayMs: 2e3,
826
+ duplicateCheck: true,
827
+ maxRecentHashes: 10
828
+ };
829
+ case "strict": return {
830
+ enabled: true,
831
+ mode: "strict",
832
+ writeDelayMs: 5e3,
833
+ duplicateCheck: true,
834
+ maxRecentHashes: 20
835
+ };
836
+ }
837
+ }
864
838
  async function setupRedditClient() {
865
- const clientId = process.env.REDDIT_CLIENT_ID;
866
- const clientSecret = process.env.REDDIT_CLIENT_SECRET;
867
- const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/1.1.0";
868
- const username = process.env.REDDIT_USERNAME;
869
- const password = process.env.REDDIT_PASSWORD;
870
- if (!clientId || !clientSecret) {
871
- console.error(
872
- "[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
873
- );
874
- process.exit(1);
875
- }
876
- try {
877
- const client = initializeRedditClient({
878
- clientId,
879
- clientSecret,
880
- userAgent,
881
- username,
882
- password
883
- });
884
- console.error("[Setup] Reddit client initialized");
885
- console.error("[Setup] Testing Reddit API connection...");
886
- const isConnected = await client.checkAuthentication();
887
- if (!isConnected) {
888
- console.error("[Error] \u2717 Failed to connect to Reddit API");
889
- console.error("[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET");
890
- process.exit(1);
891
- }
892
- console.error("[Setup] \u2713 Reddit API connection successful");
893
- if (username && password) {
894
- console.error(`[Setup] \u2713 User authenticated as: ${username}`);
895
- console.error("[Setup] Write operations enabled (posting, replying, editing, deleting)");
896
- } else {
897
- console.error("[Setup] Running in read-only mode (client credentials only)");
898
- console.error("[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD");
899
- }
900
- } catch (error) {
901
- console.error("[Error] \u2717 Reddit API connection failed:", error instanceof Error ? error.message : error);
902
- console.error("[Error] Please verify your Reddit API credentials");
903
- process.exit(1);
904
- }
839
+ const clientId = process.env.REDDIT_CLIENT_ID;
840
+ const clientSecret = process.env.REDDIT_CLIENT_SECRET;
841
+ const customUserAgent = process.env.REDDIT_USER_AGENT;
842
+ const username = process.env.REDDIT_USERNAME;
843
+ const password = process.env.REDDIT_PASSWORD;
844
+ const authMode = process.env.REDDIT_AUTH_MODE || "auto";
845
+ const safeMode = process.env.REDDIT_SAFE_MODE || "off";
846
+ if (![
847
+ "auto",
848
+ "authenticated",
849
+ "anonymous"
850
+ ].includes(authMode)) {
851
+ console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`);
852
+ console.error("[Error] Valid options are: auto, authenticated, anonymous");
853
+ process.exit(1);
854
+ }
855
+ if (![
856
+ "off",
857
+ "standard",
858
+ "strict"
859
+ ].includes(safeMode)) {
860
+ console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`);
861
+ console.error("[Error] Valid options are: off, standard, strict");
862
+ process.exit(1);
863
+ }
864
+ if (authMode === "authenticated" && (!clientId || !clientSecret)) {
865
+ console.error("[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET");
866
+ process.exit(1);
867
+ }
868
+ const hasCredentials = !!(clientId && clientSecret);
869
+ const userAgent = buildUserAgent(customUserAgent, username);
870
+ const safeModeConfig = buildSafeModeConfig(safeMode);
871
+ try {
872
+ const client = initializeRedditClient({
873
+ clientId: clientId || "",
874
+ clientSecret: clientSecret || "",
875
+ userAgent,
876
+ username,
877
+ password,
878
+ authMode,
879
+ safeMode: safeModeConfig
880
+ });
881
+ console.error("[Setup] Reddit client initialized");
882
+ console.error(`[Setup] Authentication mode: ${authMode}`);
883
+ if (authMode === "anonymous" || !hasCredentials) {
884
+ console.error("[Setup] Using anonymous Reddit API (~10 req/min)");
885
+ console.error("[Setup] No authentication required - ready to use!");
886
+ } else {
887
+ console.error("[Setup] Testing Reddit API connection...");
888
+ if (!await client.checkAuthentication()) {
889
+ console.error("[Error] ✗ Failed to connect to Reddit API");
890
+ console.error("[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET");
891
+ process.exit(1);
892
+ }
893
+ console.error("[Setup] ✓ Reddit API connection successful");
894
+ console.error("[Setup] Using OAuth Reddit API (60-100 req/min)");
895
+ }
896
+ if (username && password) {
897
+ console.error(`[Setup] ✓ User authenticated as: ${username}`);
898
+ console.error("[Setup] Write operations enabled (posting, replying, editing, deleting)");
899
+ } else {
900
+ console.error("[Setup] Read-only mode (no user credentials)");
901
+ console.error("[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD");
902
+ }
903
+ if (safeModeConfig.enabled) {
904
+ console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`);
905
+ console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`);
906
+ console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`);
907
+ } else {
908
+ console.error("[Setup] Safe mode: off (no write operation safeguards)");
909
+ console.error("[Setup] Consider enabling REDDIT_SAFE_MODE=standard for spam protection");
910
+ }
911
+ } catch (error) {
912
+ console.error("[Error] ✗ Reddit API connection failed:", error instanceof Error ? error.message : error);
913
+ console.error("[Error] Please verify your Reddit API credentials");
914
+ process.exit(1);
915
+ }
905
916
  }
906
- var server = new import_fastmcp.FastMCP({
907
- name: "reddit-mcp-server",
908
- version: "1.1.0",
909
- instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
917
+ const server = new fastmcp.FastMCP({
918
+ name: "reddit-mcp-server",
919
+ version: VERSION,
920
+ instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
910
921
 
911
922
  Available capabilities:
912
923
  - Fetch Reddit posts, comments, and user information
@@ -918,76 +929,57 @@ Available capabilities:
918
929
  - Analyze engagement metrics and community insights
919
930
 
920
931
  For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
921
- // Optional OAuth configuration for HTTP transport
922
- ...process.env.OAUTH_ENABLED === "true" && {
923
- authenticate: async (request) => {
924
- const authHeader = request.headers.authorization;
925
- const expectedToken = process.env.OAUTH_TOKEN;
926
- if (!expectedToken) {
927
- const token2 = Array.from(
928
- { length: 32 },
929
- () => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random() * 62))
930
- ).join("");
931
- console.log(`[Auth] Generated OAuth token: ${token2}`);
932
- throw new Response(
933
- JSON.stringify({
934
- error: "No OAuth token configured",
935
- generatedToken: token2
936
- }),
937
- {
938
- status: 401,
939
- headers: { "Content-Type": "application/json" }
940
- }
941
- );
942
- }
943
- if (!(authHeader == null ? void 0 : authHeader.startsWith("Bearer "))) {
944
- throw new Response(null, {
945
- status: 401,
946
- statusText: "Missing or invalid Authorization header"
947
- });
948
- }
949
- const token = authHeader.slice(7);
950
- if (token !== expectedToken) {
951
- throw new Response(null, {
952
- status: 403,
953
- statusText: "Invalid token"
954
- });
955
- }
956
- return { authenticated: true };
957
- }
958
- }
932
+ ...process.env.OAUTH_ENABLED === "true" && { authenticate: async (request) => {
933
+ const authHeader = request.headers.authorization;
934
+ const expectedToken = process.env.OAUTH_TOKEN;
935
+ if (!expectedToken) {
936
+ const token = Array.from({ length: 32 }, () => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random() * 62))).join("");
937
+ console.log(`[Auth] Generated OAuth token: ${token}`);
938
+ throw new Response(JSON.stringify({
939
+ error: "No OAuth token configured",
940
+ generatedToken: token
941
+ }), {
942
+ status: 401,
943
+ headers: { "Content-Type": "application/json" }
944
+ });
945
+ }
946
+ if (!(authHeader === null || authHeader === void 0 ? void 0 : authHeader.startsWith("Bearer "))) throw new Response(null, {
947
+ status: 401,
948
+ statusText: "Missing or invalid Authorization header"
949
+ });
950
+ if (authHeader.slice(7) !== expectedToken) throw new Response(null, {
951
+ status: 403,
952
+ statusText: "Invalid token"
953
+ });
954
+ return { authenticated: true };
955
+ } }
959
956
  });
960
957
  server.addTool({
961
- name: "test_reddit_mcp_server",
962
- description: "Test the Reddit MCP Server connection and configuration",
963
- parameters: import_zod.z.object({}),
964
- execute: async () => {
965
- const client = getRedditClient();
966
- const hasAuth = client ? "\u2713" : "\u2717";
967
- const hasWriteAccess = process.env.REDDIT_USERNAME && process.env.REDDIT_PASSWORD ? "\u2713" : "\u2717";
968
- return `Reddit MCP Server Status:
969
- - Server: \u2713 Running
970
- - Reddit Client: ${hasAuth} ${client ? "Initialized" : "Not initialized"}
971
- - Write Access: ${hasWriteAccess} ${hasWriteAccess === "\u2713" ? "Available" : "Read-only mode"}
972
- - Version: 1.1.0
958
+ name: "test_reddit_mcp_server",
959
+ description: "Test the Reddit MCP Server connection and configuration",
960
+ parameters: zod.z.object({}),
961
+ execute: async () => {
962
+ const client = getRedditClient();
963
+ const hasAuth = client ? "" : "";
964
+ const hasWriteAccess = process.env.REDDIT_USERNAME && process.env.REDDIT_PASSWORD ? "" : "";
965
+ return `Reddit MCP Server Status:
966
+ - Server: Running
967
+ - Reddit Client: ${hasAuth} ${client ? "Initialized" : "Not initialized"}
968
+ - Write Access: ${hasWriteAccess} ${hasWriteAccess === "" ? "Available" : "Read-only mode"}
969
+ - Version: ${VERSION}
973
970
 
974
971
  Ready to handle Reddit API requests!`;
975
- }
972
+ }
976
973
  });
977
974
  server.addTool({
978
- name: "get_user_info",
979
- description: "Get detailed information about a Reddit user including karma, account status, and activity analysis",
980
- parameters: import_zod.z.object({
981
- username: import_zod.z.string().describe("The Reddit username (without u/ prefix)")
982
- }),
983
- execute: async (args) => {
984
- const client = getRedditClient();
985
- if (!client) {
986
- throw new Error("Reddit client not initialized");
987
- }
988
- const user = await client.getUser(args.username);
989
- const formattedUser = formatUserInfo(user);
990
- return `# User Information: u/${formattedUser.username}
975
+ name: "get_user_info",
976
+ description: "Get detailed information about a Reddit user including karma, account status, and activity analysis",
977
+ parameters: zod.z.object({ username: zod.z.string().describe("The Reddit username (without u/ prefix)") }),
978
+ execute: async (args) => {
979
+ const client = getRedditClient();
980
+ if (!client) throw new Error("Reddit client not initialized");
981
+ const formattedUser = formatUserInfo(await client.getUser(args.username));
982
+ return `# User Information: u/${formattedUser.username}
991
983
 
992
984
  ## Profile Overview
993
985
  - Username: u/${formattedUser.username}
@@ -1004,98 +996,109 @@ server.addTool({
1004
996
 
1005
997
  ## Recommendations
1006
998
  - ${formattedUser.recommendations.replace(/\n {2}- /g, "\n- ")}`;
1007
- }
999
+ }
1008
1000
  });
1009
1001
  server.addTool({
1010
- name: "get_user_posts",
1011
- description: "Get recent posts by a Reddit user with sorting and filtering options",
1012
- parameters: import_zod.z.object({
1013
- username: import_zod.z.string().describe("The Reddit username (without u/ prefix)"),
1014
- sort: import_zod.z.enum(["new", "hot", "top"]).default("new").describe("Sort order for posts"),
1015
- time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter for top posts"),
1016
- limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1017
- }),
1018
- execute: async (args) => {
1019
- const client = getRedditClient();
1020
- if (!client) {
1021
- throw new Error("Reddit client not initialized");
1022
- }
1023
- const posts = await client.getUserPosts(args.username, {
1024
- sort: args.sort,
1025
- timeFilter: args.time_filter,
1026
- limit: args.limit
1027
- });
1028
- if (posts.length === 0) {
1029
- return `No posts found for u/${args.username} with the specified filters.`;
1030
- }
1031
- const postSummaries = posts.map((post, index) => {
1032
- const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
1033
- return `### ${index + 1}. ${post.title} ${flags.join(" ")}
1002
+ name: "get_user_posts",
1003
+ description: "Get recent posts by a Reddit user with sorting and filtering options",
1004
+ parameters: zod.z.object({
1005
+ username: zod.z.string().describe("The Reddit username (without u/ prefix)"),
1006
+ sort: zod.z.enum([
1007
+ "new",
1008
+ "hot",
1009
+ "top"
1010
+ ]).default("new").describe("Sort order for posts"),
1011
+ time_filter: zod.z.enum([
1012
+ "hour",
1013
+ "day",
1014
+ "week",
1015
+ "month",
1016
+ "year",
1017
+ "all"
1018
+ ]).default("all").describe("Time filter for top posts"),
1019
+ limit: zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1020
+ }),
1021
+ execute: async (args) => {
1022
+ const client = getRedditClient();
1023
+ if (!client) throw new Error("Reddit client not initialized");
1024
+ const posts = await client.getUserPosts(args.username, {
1025
+ sort: args.sort,
1026
+ timeFilter: args.time_filter,
1027
+ limit: args.limit
1028
+ });
1029
+ if (posts.length === 0) return `No posts found for u/${args.username} with the specified filters.`;
1030
+ const postSummaries = posts.map((post, index) => {
1031
+ const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
1032
+ return `### ${index + 1}. ${post.title} ${flags.join(" ")}
1034
1033
  - Subreddit: r/${post.subreddit}
1035
1034
  - Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
1036
1035
  - Comments: ${post.numComments.toLocaleString()}
1037
- - Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
1036
+ - Posted: ${(/* @__PURE__ */ new Date(post.createdUtc * 1e3)).toLocaleString()}
1038
1037
  - Link: https://reddit.com${post.permalink}`;
1039
- }).join("\n\n");
1040
- return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})
1038
+ }).join("\n\n");
1039
+ return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})
1041
1040
 
1042
1041
  ${postSummaries}`;
1043
- }
1042
+ }
1044
1043
  });
1045
1044
  server.addTool({
1046
- name: "get_user_comments",
1047
- description: "Get recent comments by a Reddit user with sorting and filtering options",
1048
- parameters: import_zod.z.object({
1049
- username: import_zod.z.string().describe("The Reddit username (without u/ prefix)"),
1050
- sort: import_zod.z.enum(["new", "hot", "top"]).default("new").describe("Sort order for comments"),
1051
- time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter for top comments"),
1052
- limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of comments to retrieve")
1053
- }),
1054
- execute: async (args) => {
1055
- const client = getRedditClient();
1056
- if (!client) {
1057
- throw new Error("Reddit client not initialized");
1058
- }
1059
- const comments = await client.getUserComments(args.username, {
1060
- sort: args.sort,
1061
- timeFilter: args.time_filter,
1062
- limit: args.limit
1063
- });
1064
- if (comments.length === 0) {
1065
- return `No comments found for u/${args.username} with the specified filters.`;
1066
- }
1067
- const commentSummaries = comments.map((comment, index) => {
1068
- const truncatedBody = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body;
1069
- const flags = [...comment.edited ? ["*(edited)*"] : [], ...comment.isSubmitter ? ["**OP**"] : []];
1070
- return `### ${index + 1}. Comment ${flags.join(" ")}
1045
+ name: "get_user_comments",
1046
+ description: "Get recent comments by a Reddit user with sorting and filtering options",
1047
+ parameters: zod.z.object({
1048
+ username: zod.z.string().describe("The Reddit username (without u/ prefix)"),
1049
+ sort: zod.z.enum([
1050
+ "new",
1051
+ "hot",
1052
+ "top"
1053
+ ]).default("new").describe("Sort order for comments"),
1054
+ time_filter: zod.z.enum([
1055
+ "hour",
1056
+ "day",
1057
+ "week",
1058
+ "month",
1059
+ "year",
1060
+ "all"
1061
+ ]).default("all").describe("Time filter for top comments"),
1062
+ limit: zod.z.number().min(1).max(100).default(10).describe("Number of comments to retrieve")
1063
+ }),
1064
+ execute: async (args) => {
1065
+ const client = getRedditClient();
1066
+ if (!client) throw new Error("Reddit client not initialized");
1067
+ const comments = await client.getUserComments(args.username, {
1068
+ sort: args.sort,
1069
+ timeFilter: args.time_filter,
1070
+ limit: args.limit
1071
+ });
1072
+ if (comments.length === 0) return `No comments found for u/${args.username} with the specified filters.`;
1073
+ const commentSummaries = comments.map((comment, index) => {
1074
+ const truncatedBody = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body;
1075
+ const flags = [...comment.edited ? ["*(edited)*"] : [], ...comment.isSubmitter ? ["**OP**"] : []];
1076
+ return `### ${index + 1}. Comment ${flags.join(" ")}
1071
1077
  In r/${comment.subreddit} on "${comment.submissionTitle}"
1072
1078
 
1073
1079
  > ${truncatedBody}
1074
1080
 
1075
1081
  - Score: ${comment.score.toLocaleString()}
1076
- - Posted: ${new Date(comment.createdUtc * 1e3).toLocaleString()}
1082
+ - Posted: ${(/* @__PURE__ */ new Date(comment.createdUtc * 1e3)).toLocaleString()}
1077
1083
  - Link: https://reddit.com${comment.permalink}`;
1078
- }).join("\n\n");
1079
- return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})
1084
+ }).join("\n\n");
1085
+ return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})
1080
1086
 
1081
1087
  ${commentSummaries}`;
1082
- }
1088
+ }
1083
1089
  });
1084
1090
  server.addTool({
1085
- name: "get_reddit_post",
1086
- description: "Get detailed information about a specific Reddit post including content, stats, and engagement analysis",
1087
- parameters: import_zod.z.object({
1088
- subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
1089
- post_id: import_zod.z.string().describe("The Reddit post ID")
1090
- }),
1091
- execute: async (args) => {
1092
- const client = getRedditClient();
1093
- if (!client) {
1094
- throw new Error("Reddit client not initialized");
1095
- }
1096
- const post = await client.getPost(args.post_id, args.subreddit);
1097
- const formattedPost = formatPostInfo(post);
1098
- return `# Post from r/${formattedPost.subreddit}
1091
+ name: "get_reddit_post",
1092
+ description: "Get detailed information about a specific Reddit post including content, stats, and engagement analysis",
1093
+ parameters: zod.z.object({
1094
+ subreddit: zod.z.string().describe("The subreddit name (without r/ prefix)"),
1095
+ post_id: zod.z.string().describe("The Reddit post ID")
1096
+ }),
1097
+ execute: async (args) => {
1098
+ const client = getRedditClient();
1099
+ if (!client) throw new Error("Reddit client not initialized");
1100
+ const formattedPost = formatPostInfo(await client.getPost(args.post_id, args.subreddit));
1101
+ return `# Post from r/${formattedPost.subreddit}
1099
1102
 
1100
1103
  ## Post Details
1101
1104
  - Title: ${formattedPost.title}
@@ -1124,55 +1127,48 @@ ${formattedPost.content}
1124
1127
 
1125
1128
  ## Best Time to Engage
1126
1129
  ${formattedPost.bestTimeToEngage}`;
1127
- }
1130
+ }
1128
1131
  });
1129
1132
  server.addTool({
1130
- name: "get_top_posts",
1131
- description: "Get top posts from a subreddit or from the Reddit home feed",
1132
- parameters: import_zod.z.object({
1133
- subreddit: import_zod.z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
1134
- time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("week").describe("Time period for top posts"),
1135
- limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1136
- }),
1137
- execute: async (args) => {
1138
- const client = getRedditClient();
1139
- if (!client) {
1140
- throw new Error("Reddit client not initialized");
1141
- }
1142
- const posts = await client.getTopPosts(args.subreddit || "", args.time_filter, args.limit);
1143
- if (posts.length === 0) {
1144
- const location2 = args.subreddit ? `r/${args.subreddit}` : "home feed";
1145
- return `No posts found in ${location2} for the specified time period.`;
1146
- }
1147
- const formattedPosts = posts.map(formatPostInfo);
1148
- const postSummaries = formattedPosts.map(
1149
- (post, index) => `### ${index + 1}. ${post.title}
1133
+ name: "get_top_posts",
1134
+ description: "Get top posts from a subreddit or from the Reddit home feed",
1135
+ parameters: zod.z.object({
1136
+ subreddit: zod.z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
1137
+ time_filter: zod.z.enum([
1138
+ "hour",
1139
+ "day",
1140
+ "week",
1141
+ "month",
1142
+ "year",
1143
+ "all"
1144
+ ]).default("week").describe("Time period for top posts"),
1145
+ limit: zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1146
+ }),
1147
+ execute: async (args) => {
1148
+ const client = getRedditClient();
1149
+ if (!client) throw new Error("Reddit client not initialized");
1150
+ const posts = await client.getTopPosts(args.subreddit || "", args.time_filter, args.limit);
1151
+ if (posts.length === 0) return `No posts found in ${args.subreddit ? `r/${args.subreddit}` : "home feed"} for the specified time period.`;
1152
+ const postSummaries = posts.map(formatPostInfo).map((post, index) => `### ${index + 1}. ${post.title}
1150
1153
  - Author: u/${post.author}
1151
1154
  - Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
1152
1155
  - Comments: ${post.stats.comments.toLocaleString()}
1153
1156
  - Posted: ${post.metadata.posted}
1154
- - Link: ${post.links.shortLink}`
1155
- ).join("\n\n");
1156
- const location = args.subreddit ? `r/${args.subreddit}` : "Home Feed";
1157
- return `# Top Posts from ${location} (${args.time_filter})
1157
+ - Link: ${post.links.shortLink}`).join("\n\n");
1158
+ return `# Top Posts from ${args.subreddit ? `r/${args.subreddit}` : "Home Feed"} (${args.time_filter})
1158
1159
 
1159
1160
  ${postSummaries}`;
1160
- }
1161
+ }
1161
1162
  });
1162
1163
  server.addTool({
1163
- name: "get_subreddit_info",
1164
- description: "Get detailed information about a subreddit including description, stats, and community analysis",
1165
- parameters: import_zod.z.object({
1166
- subreddit_name: import_zod.z.string().describe("The subreddit name (without r/ prefix)")
1167
- }),
1168
- execute: async (args) => {
1169
- const client = getRedditClient();
1170
- if (!client) {
1171
- throw new Error("Reddit client not initialized");
1172
- }
1173
- const subreddit = await client.getSubredditInfo(args.subreddit_name);
1174
- const formattedSubreddit = formatSubredditInfo(subreddit);
1175
- return `# Subreddit Information: r/${formattedSubreddit.name}
1164
+ name: "get_subreddit_info",
1165
+ description: "Get detailed information about a subreddit including description, stats, and community analysis",
1166
+ parameters: zod.z.object({ subreddit_name: zod.z.string().describe("The subreddit name (without r/ prefix)") }),
1167
+ execute: async (args) => {
1168
+ const client = getRedditClient();
1169
+ if (!client) throw new Error("Reddit client not initialized");
1170
+ const formattedSubreddit = formatSubredditInfo(await client.getSubredditInfo(args.subreddit_name));
1171
+ return `# Subreddit Information: r/${formattedSubreddit.name}
1176
1172
 
1177
1173
  ## Overview
1178
1174
  - Name: r/${formattedSubreddit.name}
@@ -1199,93 +1195,96 @@ ${formattedSubreddit.description.full}
1199
1195
 
1200
1196
  ## Engagement Tips
1201
1197
  - ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}`;
1202
- }
1198
+ }
1203
1199
  });
1204
1200
  server.addTool({
1205
- name: "get_trending_subreddits",
1206
- description: "Get a list of currently trending subreddits",
1207
- parameters: import_zod.z.object({}),
1208
- execute: async () => {
1209
- const client = getRedditClient();
1210
- if (!client) {
1211
- throw new Error("Reddit client not initialized");
1212
- }
1213
- const trendingSubreddits = await client.getTrendingSubreddits();
1214
- return `# Trending Subreddits
1201
+ name: "get_trending_subreddits",
1202
+ description: "Get a list of currently trending subreddits",
1203
+ parameters: zod.z.object({}),
1204
+ execute: async () => {
1205
+ const client = getRedditClient();
1206
+ if (!client) throw new Error("Reddit client not initialized");
1207
+ return `# Trending Subreddits
1215
1208
 
1216
- ${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}`;
1217
- }
1209
+ ${(await client.getTrendingSubreddits()).map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}`;
1210
+ }
1218
1211
  });
1219
1212
  server.addTool({
1220
- name: "search_reddit",
1221
- description: "Search Reddit for posts and content across subreddits",
1222
- parameters: import_zod.z.object({
1223
- query: import_zod.z.string().describe("Search query"),
1224
- subreddit: import_zod.z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
1225
- sort: import_zod.z.enum(["relevance", "hot", "top", "new", "comments"]).default("relevance").describe("Sort order"),
1226
- time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter"),
1227
- limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of results"),
1228
- type: import_zod.z.enum(["link", "sr", "user"]).default("link").describe("Type of content to search")
1229
- }),
1230
- execute: async (args) => {
1231
- const client = getRedditClient();
1232
- if (!client) {
1233
- throw new Error("Reddit client not initialized");
1234
- }
1235
- if (!args.query || args.query.trim() === "") {
1236
- throw new Error("Search query cannot be empty");
1237
- }
1238
- const posts = await client.searchReddit(args.query, {
1239
- subreddit: args.subreddit,
1240
- sort: args.sort,
1241
- timeFilter: args.time_filter,
1242
- limit: args.limit,
1243
- type: args.type
1244
- });
1245
- if (posts.length === 0) {
1246
- const searchLocation2 = args.subreddit ? ` in r/${args.subreddit}` : "";
1247
- return `No results found for "${args.query}"${searchLocation2}.`;
1248
- }
1249
- const searchResults = posts.map((post, index) => {
1250
- const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
1251
- return `### ${index + 1}. ${post.title} ${flags.join(" ")}
1213
+ name: "search_reddit",
1214
+ description: "Search Reddit for posts and content across subreddits",
1215
+ parameters: zod.z.object({
1216
+ query: zod.z.string().describe("Search query"),
1217
+ subreddit: zod.z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
1218
+ sort: zod.z.enum([
1219
+ "relevance",
1220
+ "hot",
1221
+ "top",
1222
+ "new",
1223
+ "comments"
1224
+ ]).default("relevance").describe("Sort order"),
1225
+ time_filter: zod.z.enum([
1226
+ "hour",
1227
+ "day",
1228
+ "week",
1229
+ "month",
1230
+ "year",
1231
+ "all"
1232
+ ]).default("all").describe("Time filter"),
1233
+ limit: zod.z.number().min(1).max(100).default(10).describe("Number of results"),
1234
+ type: zod.z.enum([
1235
+ "link",
1236
+ "sr",
1237
+ "user"
1238
+ ]).default("link").describe("Type of content to search")
1239
+ }),
1240
+ execute: async (args) => {
1241
+ const client = getRedditClient();
1242
+ if (!client) throw new Error("Reddit client not initialized");
1243
+ if (!args.query || args.query.trim() === "") throw new Error("Search query cannot be empty");
1244
+ const posts = await client.searchReddit(args.query, {
1245
+ subreddit: args.subreddit,
1246
+ sort: args.sort,
1247
+ timeFilter: args.time_filter,
1248
+ limit: args.limit,
1249
+ type: args.type
1250
+ });
1251
+ if (posts.length === 0) {
1252
+ const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : "";
1253
+ return `No results found for "${args.query}"${searchLocation}.`;
1254
+ }
1255
+ const searchResults = posts.map((post, index) => {
1256
+ const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
1257
+ return `### ${index + 1}. ${post.title} ${flags.join(" ")}
1252
1258
  - Subreddit: r/${post.subreddit}
1253
1259
  - Author: u/${post.author}
1254
1260
  - Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
1255
1261
  - Comments: ${post.numComments.toLocaleString()}
1256
- - Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
1262
+ - Posted: ${(/* @__PURE__ */ new Date(post.createdUtc * 1e3)).toLocaleString()}
1257
1263
  - Link: https://reddit.com${post.permalink}`;
1258
- }).join("\n\n");
1259
- const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : "";
1260
- return `# Reddit Search Results for: "${args.query}"${searchLocation}
1264
+ }).join("\n\n");
1265
+ const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : "";
1266
+ return `# Reddit Search Results for: "${args.query}"${searchLocation}
1261
1267
 
1262
1268
  Sorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}
1263
1269
 
1264
1270
  ${searchResults}`;
1265
- }
1271
+ }
1266
1272
  });
1267
1273
  server.addTool({
1268
- name: "create_post",
1269
- description: "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
1270
- parameters: import_zod.z.object({
1271
- subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
1272
- title: import_zod.z.string().describe("The post title"),
1273
- content: import_zod.z.string().describe("The post content (text for self posts, URL for link posts)"),
1274
- is_self: import_zod.z.boolean().default(true).describe("Whether this is a self post (text) or link post")
1275
- }),
1276
- execute: async (args) => {
1277
- const client = getRedditClient();
1278
- if (!client) {
1279
- throw new Error("Reddit client not initialized");
1280
- }
1281
- if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1282
- throw new Error(
1283
- "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1284
- );
1285
- }
1286
- const post = await client.createPost(args.subreddit, args.title, args.content, args.is_self);
1287
- const formattedPost = formatPostInfo(post);
1288
- return `# Post Created Successfully
1274
+ name: "create_post",
1275
+ description: "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. Consider enabling REDDIT_SAFE_MODE=standard for protection.",
1276
+ parameters: zod.z.object({
1277
+ subreddit: zod.z.string().describe("The subreddit name (without r/ prefix)"),
1278
+ title: zod.z.string().describe("The post title"),
1279
+ content: zod.z.string().describe("The post content (text for self posts, URL for link posts)"),
1280
+ is_self: zod.z.boolean().default(true).describe("Whether this is a self post (text) or link post")
1281
+ }),
1282
+ execute: async (args) => {
1283
+ const client = getRedditClient();
1284
+ if (!client) throw new Error("Reddit client not initialized");
1285
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
1286
+ const formattedPost = formatPostInfo(await client.createPost(args.subreddit, args.title, args.content, args.is_self));
1287
+ return `# Post Created Successfully
1289
1288
 
1290
1289
  ## Post Details
1291
1290
  - Title: ${formattedPost.title}
@@ -1294,27 +1293,21 @@ server.addTool({
1294
1293
  - Link: ${formattedPost.links.fullPost}
1295
1294
 
1296
1295
  Your post has been successfully submitted to r/${formattedPost.subreddit}.`;
1297
- }
1296
+ }
1298
1297
  });
1299
1298
  server.addTool({
1300
- name: "reply_to_post",
1301
- description: "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
1302
- parameters: import_zod.z.object({
1303
- post_id: import_zod.z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
1304
- content: import_zod.z.string().describe("The reply content")
1305
- }),
1306
- execute: async (args) => {
1307
- const client = getRedditClient();
1308
- if (!client) {
1309
- throw new Error("Reddit client not initialized");
1310
- }
1311
- if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1312
- throw new Error(
1313
- "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1314
- );
1315
- }
1316
- const comment = await client.replyToPost(args.post_id, args.content);
1317
- return `# Reply Posted Successfully
1299
+ name: "reply_to_post",
1300
+ description: "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.",
1301
+ parameters: zod.z.object({
1302
+ post_id: zod.z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
1303
+ content: zod.z.string().describe("The reply content")
1304
+ }),
1305
+ execute: async (args) => {
1306
+ const client = getRedditClient();
1307
+ if (!client) throw new Error("Reddit client not initialized");
1308
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
1309
+ const comment = await client.replyToPost(args.post_id, args.content);
1310
+ return `# Reply Posted Successfully
1318
1311
 
1319
1312
  ## Comment Details
1320
1313
  - Posted to: ${args.post_id}
@@ -1322,81 +1315,53 @@ server.addTool({
1322
1315
  - Comment ID: ${comment.id}
1323
1316
 
1324
1317
  Your reply has been successfully posted.`;
1325
- }
1318
+ }
1326
1319
  });
1327
1320
  server.addTool({
1328
- name: "delete_post",
1329
- description: "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1330
- parameters: import_zod.z.object({
1331
- thing_id: import_zod.z.string().describe(
1332
- "The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."
1333
- )
1334
- }),
1335
- execute: async (args) => {
1336
- const client = getRedditClient();
1337
- if (!client) {
1338
- throw new Error("Reddit client not initialized");
1339
- }
1340
- if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1341
- throw new Error(
1342
- "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1343
- );
1344
- }
1345
- await client.deletePost(args.thing_id);
1346
- return `# Post Deleted Successfully
1321
+ name: "delete_post",
1322
+ description: "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1323
+ parameters: zod.z.object({ thing_id: zod.z.string().describe("The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.") }),
1324
+ execute: async (args) => {
1325
+ const client = getRedditClient();
1326
+ if (!client) throw new Error("Reddit client not initialized");
1327
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
1328
+ await client.deletePost(args.thing_id);
1329
+ return `# Post Deleted Successfully
1347
1330
 
1348
1331
  The post ${args.thing_id} has been permanently deleted from Reddit.
1349
1332
 
1350
1333
  **Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`;
1351
- }
1334
+ }
1352
1335
  });
1353
1336
  server.addTool({
1354
- name: "delete_comment",
1355
- description: "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1356
- parameters: import_zod.z.object({
1357
- thing_id: import_zod.z.string().describe(
1358
- "The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."
1359
- )
1360
- }),
1361
- execute: async (args) => {
1362
- const client = getRedditClient();
1363
- if (!client) {
1364
- throw new Error("Reddit client not initialized");
1365
- }
1366
- if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1367
- throw new Error(
1368
- "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1369
- );
1370
- }
1371
- await client.deleteComment(args.thing_id);
1372
- return `# Comment Deleted Successfully
1337
+ name: "delete_comment",
1338
+ description: "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1339
+ parameters: zod.z.object({ thing_id: zod.z.string().describe("The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.") }),
1340
+ execute: async (args) => {
1341
+ const client = getRedditClient();
1342
+ if (!client) throw new Error("Reddit client not initialized");
1343
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
1344
+ await client.deleteComment(args.thing_id);
1345
+ return `# Comment Deleted Successfully
1373
1346
 
1374
1347
  The comment ${args.thing_id} has been permanently deleted from Reddit.
1375
1348
 
1376
1349
  **Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`;
1377
- }
1350
+ }
1378
1351
  });
1379
1352
  server.addTool({
1380
- name: "edit_post",
1381
- description: "Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). You can only edit the text content of self posts, not titles or link posts.",
1382
- parameters: import_zod.z.object({
1383
- thing_id: import_zod.z.string().describe(
1384
- "The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."
1385
- ),
1386
- new_text: import_zod.z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
1387
- }),
1388
- execute: async (args) => {
1389
- const client = getRedditClient();
1390
- if (!client) {
1391
- throw new Error("Reddit client not initialized");
1392
- }
1393
- if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1394
- throw new Error(
1395
- "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1396
- );
1397
- }
1398
- await client.editPost(args.thing_id, args.new_text);
1399
- return `# Post Edited Successfully
1353
+ name: "edit_post",
1354
+ description: "Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). You can only edit the text content of self posts, not titles or link posts. WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
1355
+ parameters: zod.z.object({
1356
+ thing_id: zod.z.string().describe("The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."),
1357
+ new_text: zod.z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
1358
+ }),
1359
+ execute: async (args) => {
1360
+ const client = getRedditClient();
1361
+ if (!client) throw new Error("Reddit client not initialized");
1362
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
1363
+ await client.editPost(args.thing_id, args.new_text);
1364
+ return `# Post Edited Successfully
1400
1365
 
1401
1366
  The post ${args.thing_id} has been updated with your new content.
1402
1367
 
@@ -1405,120 +1370,116 @@ The post ${args.thing_id} has been updated with your new content.
1405
1370
  - Post titles cannot be edited
1406
1371
  - Link posts cannot be edited
1407
1372
  - An "edited" marker will appear on your post`;
1408
- }
1373
+ }
1409
1374
  });
1410
1375
  server.addTool({
1411
- name: "edit_comment",
1412
- description: "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). Update the text content of a comment you previously posted.",
1413
- parameters: import_zod.z.object({
1414
- thing_id: import_zod.z.string().describe(
1415
- "The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."
1416
- ),
1417
- new_text: import_zod.z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
1418
- }),
1419
- execute: async (args) => {
1420
- const client = getRedditClient();
1421
- if (!client) {
1422
- throw new Error("Reddit client not initialized");
1423
- }
1424
- if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1425
- throw new Error(
1426
- "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1427
- );
1428
- }
1429
- await client.editComment(args.thing_id, args.new_text);
1430
- return `# Comment Edited Successfully
1376
+ name: "edit_comment",
1377
+ description: "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). Update the text content of a comment you previously posted. WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
1378
+ parameters: zod.z.object({
1379
+ thing_id: zod.z.string().describe("The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."),
1380
+ new_text: zod.z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
1381
+ }),
1382
+ execute: async (args) => {
1383
+ const client = getRedditClient();
1384
+ if (!client) throw new Error("Reddit client not initialized");
1385
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
1386
+ await client.editComment(args.thing_id, args.new_text);
1387
+ return `# Comment Edited Successfully
1431
1388
 
1432
1389
  The comment ${args.thing_id} has been updated with your new content.
1433
1390
 
1434
1391
  **Note**: An "edited" marker will appear on your comment to show it has been modified.`;
1435
- }
1392
+ }
1436
1393
  });
1437
1394
  server.addTool({
1438
- name: "get_post_comments",
1439
- description: "Get comments from a specific Reddit post",
1440
- parameters: import_zod.z.object({
1441
- post_id: import_zod.z.string().describe("The Reddit post ID"),
1442
- subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
1443
- sort: import_zod.z.enum(["best", "top", "new", "controversial", "old", "qa"]).default("best").describe("Comment sort order"),
1444
- limit: import_zod.z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve")
1445
- }),
1446
- execute: async (args) => {
1447
- const client = getRedditClient();
1448
- if (!client) {
1449
- throw new Error("Reddit client not initialized");
1450
- }
1451
- if (!args.post_id || !args.subreddit) {
1452
- throw new Error("post_id and subreddit are required");
1453
- }
1454
- const data = await client.getPostComments(args.post_id, args.subreddit, {
1455
- sort: args.sort,
1456
- limit: args.limit
1457
- });
1458
- const post = data.post;
1459
- const comments = data.comments;
1460
- let response = `# Comments for: ${post.title}
1395
+ name: "get_post_comments",
1396
+ description: "Get comments from a specific Reddit post",
1397
+ parameters: zod.z.object({
1398
+ post_id: zod.z.string().describe("The Reddit post ID"),
1399
+ subreddit: zod.z.string().describe("The subreddit name (without r/ prefix)"),
1400
+ sort: zod.z.enum([
1401
+ "best",
1402
+ "top",
1403
+ "new",
1404
+ "controversial",
1405
+ "old",
1406
+ "qa"
1407
+ ]).default("best").describe("Comment sort order"),
1408
+ limit: zod.z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve")
1409
+ }),
1410
+ execute: async (args) => {
1411
+ const client = getRedditClient();
1412
+ if (!client) throw new Error("Reddit client not initialized");
1413
+ if (!args.post_id || !args.subreddit) throw new Error("post_id and subreddit are required");
1414
+ const data = await client.getPostComments(args.post_id, args.subreddit, {
1415
+ sort: args.sort,
1416
+ limit: args.limit
1417
+ });
1418
+ const post = data.post;
1419
+ const comments = data.comments;
1420
+ let response = `# Comments for: ${post.title}
1461
1421
 
1462
1422
  **Post by u/${post.author} in r/${post.subreddit}**
1463
1423
  - Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}
1464
- - Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
1424
+ - Posted: ${(/* @__PURE__ */ new Date(post.createdUtc * 1e3)).toLocaleString()}
1465
1425
 
1466
1426
  ---
1467
1427
 
1468
1428
  `;
1469
- if (comments.length === 0) {
1470
- response += "No comments found for this post.";
1471
- return response;
1472
- }
1473
- const commentSummaries = comments.map((comment) => {
1474
- const indent = "\u2514\u2500".repeat(Math.min(comment.depth || 0, 3));
1475
- const authorBadge = comment.isSubmitter ? " **[OP]**" : "";
1476
- const editedBadge = comment.edited ? " *(edited)*" : "";
1477
- return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)
1429
+ if (comments.length === 0) {
1430
+ response += "No comments found for this post.";
1431
+ return response;
1432
+ }
1433
+ const commentSummaries = comments.map((comment) => {
1434
+ const indent = "└─".repeat(Math.min(comment.depth || 0, 3));
1435
+ const authorBadge = comment.isSubmitter ? " **[OP]**" : "";
1436
+ const editedBadge = comment.edited ? " *(edited)*" : "";
1437
+ return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)
1478
1438
 
1479
1439
  ${comment.body}
1480
1440
 
1481
1441
  ---`;
1482
- }).join("\n\n");
1483
- response += commentSummaries;
1484
- return response;
1485
- }
1442
+ }).join("\n\n");
1443
+ response += commentSummaries;
1444
+ return response;
1445
+ }
1486
1446
  });
1487
1447
  async function main() {
1488
- try {
1489
- await setupRedditClient();
1490
- const useStdio = process.env.TRANSPORT_TYPE === "stdio";
1491
- const port = parseInt(process.env.PORT || "3000");
1492
- const host = process.env.HOST || "0.0.0.0";
1493
- if (useStdio) {
1494
- console.error("[Setup] Starting in stdio mode (CLI/npx)");
1495
- await server.start({
1496
- transportType: "stdio"
1497
- });
1498
- } else {
1499
- console.error(`[Setup] Starting HTTP server on ${host}:${port}`);
1500
- await server.start({
1501
- transportType: "httpStream",
1502
- httpStream: {
1503
- port,
1504
- host,
1505
- endpoint: "/mcp"
1506
- }
1507
- });
1508
- console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`);
1509
- console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`);
1510
- }
1511
- } catch (error) {
1512
- console.error("[Error] Failed to start server:", error);
1513
- process.exit(1);
1514
- }
1448
+ try {
1449
+ await setupRedditClient();
1450
+ const useHttp = process.env.TRANSPORT_TYPE === "httpStream" || process.env.TRANSPORT_TYPE === "http";
1451
+ const port = parseInt(process.env.PORT || "3000");
1452
+ const host = process.env.HOST || "0.0.0.0";
1453
+ if (useHttp) {
1454
+ console.error(`[Setup] Starting HTTP server on ${host}:${port}`);
1455
+ await server.start({
1456
+ transportType: "httpStream",
1457
+ httpStream: {
1458
+ port,
1459
+ host,
1460
+ endpoint: "/mcp"
1461
+ }
1462
+ });
1463
+ console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`);
1464
+ console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`);
1465
+ } else {
1466
+ console.error("[Setup] Starting in stdio mode");
1467
+ await server.start({ transportType: "stdio" });
1468
+ }
1469
+ } catch (error) {
1470
+ console.error("[Error] Failed to start server:", error);
1471
+ process.exit(1);
1472
+ }
1515
1473
  }
1516
1474
  process.on("SIGINT", async () => {
1517
- console.error("[Shutdown] Shutting down Reddit MCP Server...");
1518
- process.exit(0);
1475
+ console.error("[Shutdown] Shutting down Reddit MCP Server...");
1476
+ process.exit(0);
1519
1477
  });
1520
1478
  process.on("SIGTERM", async () => {
1521
- console.error("[Shutdown] Shutting down Reddit MCP Server...");
1522
- process.exit(0);
1479
+ console.error("[Shutdown] Shutting down Reddit MCP Server...");
1480
+ process.exit(0);
1523
1481
  });
1524
1482
  main().catch(console.error);
1483
+
1484
+ //#endregion
1485
+ //# sourceMappingURL=index.js.map