reddit-mcp-server 1.0.0

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/src/types.ts ADDED
@@ -0,0 +1,146 @@
1
+ export interface RedditClientConfig {
2
+ clientId: string
3
+ clientSecret: string
4
+ userAgent: string
5
+ username?: string
6
+ password?: string
7
+ }
8
+
9
+ export interface RedditUser {
10
+ name: string
11
+ id: string
12
+ commentKarma: number
13
+ linkKarma: number
14
+ totalKarma: number
15
+ isMod: boolean
16
+ isGold: boolean
17
+ isEmployee: boolean
18
+ createdUtc: number
19
+ profileUrl: string
20
+ }
21
+
22
+ export interface RedditPost {
23
+ id: string
24
+ title: string
25
+ author: string
26
+ subreddit: string
27
+ selftext?: string
28
+ url?: string
29
+ score: number
30
+ upvoteRatio: number
31
+ numComments: number
32
+ createdUtc: number
33
+ over18: boolean
34
+ spoiler?: boolean
35
+ edited: boolean
36
+ isSelf: boolean
37
+ linkFlairText?: string
38
+ permalink: string
39
+ }
40
+
41
+ export interface RedditComment {
42
+ id: string
43
+ author: string
44
+ body: string
45
+ score: number
46
+ controversiality: number
47
+ subreddit: string
48
+ submissionTitle: string
49
+ createdUtc: number
50
+ edited: boolean
51
+ isSubmitter: boolean
52
+ permalink: string
53
+ }
54
+
55
+ export interface RedditSubreddit {
56
+ displayName: string
57
+ title: string
58
+ description: string
59
+ publicDescription: string
60
+ subscribers: number
61
+ activeUserCount?: number
62
+ createdUtc: number
63
+ over18: boolean
64
+ subredditType?: string
65
+ url: string
66
+ }
67
+
68
+ export interface FormattedUserInfo {
69
+ username: string
70
+ karma: {
71
+ commentKarma: number
72
+ postKarma: number
73
+ totalKarma: number
74
+ }
75
+ accountStatus: string[]
76
+ accountCreated: string
77
+ profileUrl: string
78
+ activityAnalysis: string
79
+ recommendations: string
80
+ }
81
+
82
+ export interface FormattedPostInfo {
83
+ title: string
84
+ type: string
85
+ content: string
86
+ author: string
87
+ subreddit: string
88
+ stats: {
89
+ score: number
90
+ upvoteRatio: number
91
+ comments: number
92
+ }
93
+ metadata: {
94
+ posted: string
95
+ flags: string[]
96
+ flair: string
97
+ }
98
+ links: {
99
+ fullPost: string
100
+ shortLink: string
101
+ }
102
+ engagementAnalysis: string
103
+ bestTimeToEngage: string
104
+ }
105
+
106
+ export interface FormattedSubredditInfo {
107
+ name: string
108
+ title: string
109
+ stats: {
110
+ subscribers: number
111
+ activeUsers: number | string
112
+ }
113
+ description: {
114
+ short: string
115
+ full: string
116
+ }
117
+ metadata: {
118
+ created: string
119
+ flags: string[]
120
+ }
121
+ links: {
122
+ subreddit: string
123
+ wiki: string
124
+ }
125
+ communityAnalysis: string
126
+ engagementTips: string
127
+ }
128
+
129
+ export interface FormattedCommentInfo {
130
+ author: string
131
+ content: string
132
+ stats: {
133
+ score: number
134
+ controversiality: number | string
135
+ }
136
+ context: {
137
+ subreddit: string
138
+ thread: string
139
+ }
140
+ metadata: {
141
+ posted: string
142
+ flags: string[]
143
+ }
144
+ link: string
145
+ commentAnalysis: string
146
+ }
@@ -0,0 +1,299 @@
1
+ import {
2
+ RedditUser,
3
+ RedditPost,
4
+ RedditSubreddit,
5
+ RedditComment,
6
+ FormattedUserInfo,
7
+ FormattedPostInfo,
8
+ FormattedSubredditInfo,
9
+ FormattedCommentInfo,
10
+ } from "../types"
11
+
12
+ export function formatTimestamp(timestamp: number): string {
13
+ try {
14
+ const date = new Date(timestamp * 1000)
15
+ return date
16
+ .toISOString()
17
+ .replace("T", " ")
18
+ .replace(/\.\d+Z$/, " UTC")
19
+ } catch {
20
+ return String(timestamp)
21
+ }
22
+ }
23
+
24
+ export function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {
25
+ const insights: string[] = []
26
+
27
+ // Analyze karma ratio
28
+ if (karmaRatio > 5) {
29
+ insights.push("Primarily a commenter, highly engaged in discussions")
30
+ } else if (karmaRatio < 0.2) {
31
+ insights.push("Content creator, focuses on sharing posts")
32
+ } else {
33
+ insights.push("Balanced participation in both posting and commenting")
34
+ }
35
+
36
+ // Analyze account age and status
37
+ if (accountAgeDays < 30) {
38
+ insights.push("New user, still exploring Reddit")
39
+ } else if (accountAgeDays > 365 * 5) {
40
+ insights.push("Long-time Redditor with extensive platform experience")
41
+ }
42
+
43
+ if (isMod) {
44
+ insights.push("Community leader who helps maintain subreddit quality")
45
+ }
46
+
47
+ return insights.join("\n - ")
48
+ }
49
+
50
+ export function analyzePostEngagement(score: number, ratio: number, numComments: number): string {
51
+ const insights: string[] = []
52
+
53
+ // Analyze score and ratio
54
+ if (score > 1000 && ratio > 0.95) {
55
+ insights.push("Highly successful post with strong community approval")
56
+ } else if (score > 100 && ratio > 0.8) {
57
+ insights.push("Well-received post with good engagement")
58
+ } else if (ratio < 0.5) {
59
+ insights.push("Controversial post that sparked debate")
60
+ }
61
+
62
+ // Analyze comment activity
63
+ if (numComments > 100) {
64
+ insights.push("Generated significant discussion")
65
+ } else if (numComments > score * 0.5) {
66
+ insights.push("Highly discussable content with active comment section")
67
+ } else if (numComments === 0) {
68
+ insights.push("Yet to receive community interaction")
69
+ }
70
+
71
+ return insights.join("\n - ")
72
+ }
73
+
74
+ export function analyzeSubredditHealth(subscribers: number, activeUsers: number | undefined, ageDays: number): string {
75
+ const insights: string[] = []
76
+
77
+ // Analyze size and activity
78
+ if (subscribers > 1000000) {
79
+ insights.push("Major subreddit with massive following")
80
+ } else if (subscribers > 100000) {
81
+ insights.push("Well-established community")
82
+ } else if (subscribers < 1000) {
83
+ insights.push("Niche community, potential for growth")
84
+ }
85
+
86
+ if (activeUsers) {
87
+ // If we have active users data
88
+ const activityRatio = activeUsers / subscribers
89
+ if (activityRatio > 0.1) {
90
+ insights.push("Highly active community with strong engagement")
91
+ } else if (activityRatio < 0.01) {
92
+ insights.push("Could benefit from more community engagement initiatives")
93
+ }
94
+ }
95
+
96
+ // Analyze age and maturity
97
+ if (ageDays > 365 * 5) {
98
+ insights.push("Mature subreddit with established culture")
99
+ } else if (ageDays < 90) {
100
+ insights.push("New subreddit still forming its community")
101
+ }
102
+
103
+ return insights.join("\n - ")
104
+ }
105
+
106
+ export function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {
107
+ const recommendations: string[] = []
108
+
109
+ if (karmaRatio > 5) {
110
+ recommendations.push("Consider creating more posts to share your expertise")
111
+ } else if (karmaRatio < 0.2) {
112
+ recommendations.push("Engage more in discussions to build community connections")
113
+ }
114
+
115
+ if (accountAgeDays < 30) {
116
+ recommendations.push("Explore popular subreddits in your areas of interest")
117
+ recommendations.push("Read community guidelines before posting")
118
+ }
119
+
120
+ if (isMod) {
121
+ recommendations.push("Share moderation insights with other community leaders")
122
+ }
123
+
124
+ if (!recommendations.length) {
125
+ recommendations.push("Maintain your balanced engagement across Reddit")
126
+ }
127
+
128
+ return recommendations.join("\n - ")
129
+ }
130
+
131
+ export function getBestEngagementTime(createdUtc: number): string {
132
+ const postHour = new Date(createdUtc * 1000).getHours()
133
+
134
+ // Simple time zone analysis
135
+ if (14 <= postHour && postHour <= 18) {
136
+ // Peak Reddit hours
137
+ return "Posted during peak engagement hours (2 PM - 6 PM), good timing!"
138
+ } else if (23 <= postHour || postHour <= 5) {
139
+ return "Consider posting during more active hours (morning to evening)"
140
+ } else {
141
+ return "Posted during moderate activity hours, timing could be optimized"
142
+ }
143
+ }
144
+
145
+ export function getSubredditEngagementTips(subreddit: RedditSubreddit): string {
146
+ const tips: string[] = []
147
+
148
+ if (subreddit.subscribers > 1000000) {
149
+ tips.push("Post during peak hours for maximum visibility")
150
+ tips.push("Ensure content is highly polished due to high competition")
151
+ } else if (subreddit.subscribers < 1000) {
152
+ tips.push("Engage actively to help grow the community")
153
+ tips.push("Consider cross-posting to related larger subreddits")
154
+ }
155
+
156
+ if (subreddit.activeUserCount) {
157
+ const activityRatio = subreddit.activeUserCount / subreddit.subscribers
158
+ if (activityRatio > 0.1) {
159
+ tips.push("Quick responses recommended due to high activity")
160
+ }
161
+ }
162
+
163
+ return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence"
164
+ }
165
+
166
+ export function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {
167
+ const insights: string[] = []
168
+
169
+ if (score > 100) {
170
+ insights.push("Highly upvoted comment with significant community agreement")
171
+ } else if (score < 0) {
172
+ insights.push("Controversial or contested viewpoint")
173
+ }
174
+
175
+ if (isEdited) {
176
+ insights.push("Refined for clarity or accuracy")
177
+ }
178
+
179
+ if (isOp) {
180
+ insights.push("Author's perspective adds context to original post")
181
+ }
182
+
183
+ return insights.length ? insights.join("\n - ") : "Standard engagement with discussion"
184
+ }
185
+
186
+ export function formatUserInfo(user: RedditUser): FormattedUserInfo {
187
+ const status: string[] = []
188
+ if (user.isMod) status.push("Moderator")
189
+ if (user.isGold) status.push("Reddit Gold Member")
190
+ if (user.isEmployee) status.push("Reddit Employee")
191
+
192
+ const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600) // age in days
193
+ const karmaRatio = user.commentKarma / (user.linkKarma || 1)
194
+
195
+ return {
196
+ username: user.name,
197
+ karma: {
198
+ commentKarma: user.commentKarma,
199
+ postKarma: user.linkKarma,
200
+ totalKarma: user.totalKarma,
201
+ },
202
+ accountStatus: status.length ? status : ["Regular User"],
203
+ accountCreated: formatTimestamp(user.createdUtc),
204
+ profileUrl: user.profileUrl,
205
+ activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),
206
+ recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),
207
+ }
208
+ }
209
+
210
+ export function formatPostInfo(post: RedditPost): FormattedPostInfo {
211
+ const contentType = post.isSelf ? "Text Post" : "Link Post"
212
+ const content = post.isSelf ? post.selftext || "" : post.url || ""
213
+
214
+ const flags: string[] = []
215
+ if (post.over18) flags.push("NSFW")
216
+ if (post.spoiler) flags.push("Spoiler")
217
+ if (post.edited) flags.push("Edited")
218
+
219
+ return {
220
+ title: post.title,
221
+ type: contentType,
222
+ content: content.length > 300 ? content.substring(0, 297) + "..." : content,
223
+ author: post.author,
224
+ subreddit: post.subreddit,
225
+ stats: {
226
+ score: post.score,
227
+ upvoteRatio: post.upvoteRatio,
228
+ comments: post.numComments,
229
+ },
230
+ metadata: {
231
+ posted: formatTimestamp(post.createdUtc),
232
+ flags: flags,
233
+ flair: post.linkFlairText || "None",
234
+ },
235
+ links: {
236
+ fullPost: `https://reddit.com${post.permalink}`,
237
+ shortLink: `https://redd.it/${post.id}`,
238
+ },
239
+ engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),
240
+ bestTimeToEngage: getBestEngagementTime(post.createdUtc),
241
+ }
242
+ }
243
+
244
+ export function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {
245
+ const flags: string[] = []
246
+ if (subreddit.over18) flags.push("NSFW")
247
+ if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`)
248
+
249
+ const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)
250
+
251
+ return {
252
+ name: subreddit.displayName,
253
+ title: subreddit.title,
254
+ stats: {
255
+ subscribers: subreddit.subscribers,
256
+ activeUsers: subreddit.activeUserCount !== undefined ? subreddit.activeUserCount : "Unknown",
257
+ },
258
+ description: {
259
+ short: subreddit.publicDescription,
260
+ full:
261
+ subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description,
262
+ },
263
+ metadata: {
264
+ created: formatTimestamp(subreddit.createdUtc),
265
+ flags: flags.length ? flags : ["None"],
266
+ },
267
+ links: {
268
+ subreddit: `https://reddit.com${subreddit.url}`,
269
+ wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,
270
+ },
271
+ communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),
272
+ engagementTips: getSubredditEngagementTips(subreddit),
273
+ }
274
+ }
275
+
276
+ export function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {
277
+ const flags: string[] = []
278
+ if (comment.edited) flags.push("Edited")
279
+ if (comment.isSubmitter) flags.push("OP")
280
+
281
+ return {
282
+ author: comment.author,
283
+ content: comment.body.length > 300 ? comment.body.substring(0, 297) + "..." : comment.body,
284
+ stats: {
285
+ score: comment.score,
286
+ controversiality: comment.controversiality,
287
+ },
288
+ context: {
289
+ subreddit: comment.subreddit,
290
+ thread: comment.submissionTitle,
291
+ },
292
+ metadata: {
293
+ posted: formatTimestamp(comment.createdUtc),
294
+ flags: flags.length ? flags : ["None"],
295
+ },
296
+ link: `https://reddit.com${comment.permalink}`,
297
+ commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),
298
+ }
299
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./build",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true
12
+ },
13
+ "include": [
14
+ "src/**/*"
15
+ ],
16
+ "exclude": [
17
+ "node_modules"
18
+ ]
19
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs'],
6
+ dts: true,
7
+ clean: true,
8
+ shims: true,
9
+ banner: {
10
+ js: '#!/usr/bin/env node',
11
+ },
12
+ });