reddit-mcp-server 1.0.4 → 1.0.6
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/.claude/settings.local.json +13 -1
- package/LICENSE +2 -2
- package/ROADMAP.md +77 -0
- package/dist/bin.js +652 -68
- package/dist/index.js +649 -67
- package/eslint.config.js +2 -0
- package/package.json +10 -7
- package/src/bin.ts +3 -1
- package/src/client/__tests__/reddit-client.test.ts +478 -0
- package/src/client/reddit-client.ts +351 -53
- package/src/index.ts +189 -7
- package/src/tools/__tests__/comment-tools.test.ts +119 -0
- package/src/tools/__tests__/search-tools.test.ts +100 -0
- package/src/tools/__tests__/user-tools.test.ts +258 -0
- package/src/tools/comment-tools.ts +61 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/search-tools.ts +67 -0
- package/src/tools/user-tools.ts +99 -0
- package/src/types.ts +2 -0
- package/src/utils/formatters.ts +17 -0
- package/vitest.config.ts +20 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios, { AxiosInstance } from "axios"
|
|
2
1
|
import { RedditClientConfig, RedditUser, RedditPost, RedditComment, RedditSubreddit } from "../types"
|
|
3
2
|
|
|
4
3
|
export class RedditClient {
|
|
@@ -9,7 +8,7 @@ export class RedditClient {
|
|
|
9
8
|
private password?: string
|
|
10
9
|
private accessToken?: string
|
|
11
10
|
private tokenExpiry: number = 0
|
|
12
|
-
private
|
|
11
|
+
private baseUrl: string = "https://oauth.reddit.com"
|
|
13
12
|
private authenticated: boolean = false
|
|
14
13
|
|
|
15
14
|
constructor(config: RedditClientConfig) {
|
|
@@ -18,27 +17,40 @@ export class RedditClient {
|
|
|
18
17
|
this.userAgent = config.userAgent
|
|
19
18
|
this.username = config.username
|
|
20
19
|
this.password = config.password
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private async makeRequest(path: string, options: RequestInit = {}): Promise<Response> {
|
|
23
|
+
// Check if we need to refresh token
|
|
24
|
+
if (Date.now() >= this.tokenExpiry || !this.authenticated) {
|
|
25
|
+
await this.authenticate()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const url = `${this.baseUrl}${path}`
|
|
29
|
+
const headers = {
|
|
30
|
+
"User-Agent": this.userAgent,
|
|
31
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
32
|
+
...options.headers,
|
|
33
|
+
}
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
headers
|
|
25
|
-
"User-Agent": this.userAgent,
|
|
26
|
-
},
|
|
35
|
+
const response = await fetch(url, {
|
|
36
|
+
...options,
|
|
37
|
+
headers,
|
|
27
38
|
})
|
|
28
39
|
|
|
29
|
-
//
|
|
30
|
-
this.
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// If unauthorized, try to refresh token and retry once
|
|
41
|
+
if (response.status === 401 && this.authenticated) {
|
|
42
|
+
await this.authenticate()
|
|
43
|
+
const retryHeaders = {
|
|
44
|
+
...headers,
|
|
45
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
46
|
+
}
|
|
47
|
+
return fetch(url, {
|
|
48
|
+
...options,
|
|
49
|
+
headers: retryHeaders,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return response
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
async authenticate(): Promise<void> {
|
|
@@ -61,21 +73,25 @@ export class RedditClient {
|
|
|
61
73
|
authData.append("grant_type", "client_credentials")
|
|
62
74
|
}
|
|
63
75
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
password: this.clientSecret,
|
|
68
|
-
},
|
|
76
|
+
const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64")
|
|
77
|
+
const response = await fetch(authUrl, {
|
|
78
|
+
method: "POST",
|
|
69
79
|
headers: {
|
|
70
80
|
"User-Agent": this.userAgent,
|
|
71
81
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
82
|
+
Authorization: `Basic ${credentials}`,
|
|
72
83
|
},
|
|
84
|
+
body: authData.toString(),
|
|
73
85
|
})
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`Authentication failed: ${response.status}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const data = (await response.json()) as { access_token: string; expires_in: number }
|
|
92
|
+
this.accessToken = data.access_token
|
|
93
|
+
this.tokenExpiry = now + data.expires_in * 1000
|
|
77
94
|
this.authenticated = true
|
|
78
|
-
this.api.defaults.headers.common["Authorization"] = `Bearer ${this.accessToken}`
|
|
79
95
|
|
|
80
96
|
// Successfully authenticated with Reddit API
|
|
81
97
|
} catch {
|
|
@@ -99,8 +115,13 @@ export class RedditClient {
|
|
|
99
115
|
async getUser(username: string): Promise<RedditUser> {
|
|
100
116
|
await this.authenticate()
|
|
101
117
|
try {
|
|
102
|
-
const response = await this.
|
|
103
|
-
|
|
118
|
+
const response = await this.makeRequest(`/user/${username}/about.json`)
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
throw new Error(`HTTP ${response.status}`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const json = (await response.json()) as { data: any }
|
|
124
|
+
const data = json.data
|
|
104
125
|
|
|
105
126
|
return {
|
|
106
127
|
name: data.name,
|
|
@@ -123,8 +144,13 @@ export class RedditClient {
|
|
|
123
144
|
async getSubredditInfo(subredditName: string): Promise<RedditSubreddit> {
|
|
124
145
|
await this.authenticate()
|
|
125
146
|
try {
|
|
126
|
-
const response = await this.
|
|
127
|
-
|
|
147
|
+
const response = await this.makeRequest(`/r/${subredditName}/about.json`)
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`HTTP ${response.status}`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const json = (await response.json()) as { data: any }
|
|
153
|
+
const data = json.data
|
|
128
154
|
|
|
129
155
|
return {
|
|
130
156
|
displayName: data.display_name,
|
|
@@ -148,14 +174,19 @@ export class RedditClient {
|
|
|
148
174
|
await this.authenticate()
|
|
149
175
|
try {
|
|
150
176
|
const endpoint = subreddit ? `/r/${subreddit}/top.json` : "/top.json"
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
limit,
|
|
155
|
-
},
|
|
177
|
+
const params = new URLSearchParams({
|
|
178
|
+
t: timeFilter,
|
|
179
|
+
limit: limit.toString(),
|
|
156
180
|
})
|
|
157
181
|
|
|
158
|
-
|
|
182
|
+
const response = await this.makeRequest(`${endpoint}?${params}`)
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw new Error(`HTTP ${response.status}`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const json = (await response.json()) as { data: { children: any[] } }
|
|
188
|
+
|
|
189
|
+
return json.data.children.map((child: any) => {
|
|
159
190
|
const post = child.data
|
|
160
191
|
return {
|
|
161
192
|
id: post.id,
|
|
@@ -186,19 +217,24 @@ export class RedditClient {
|
|
|
186
217
|
await this.authenticate()
|
|
187
218
|
try {
|
|
188
219
|
const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`
|
|
220
|
+
const response = await this.makeRequest(endpoint)
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
throw new Error(`HTTP ${response.status}`)
|
|
224
|
+
}
|
|
189
225
|
|
|
190
|
-
const
|
|
226
|
+
const json = (await response.json()) as any
|
|
191
227
|
|
|
192
228
|
let post
|
|
193
229
|
if (subreddit) {
|
|
194
230
|
// When using the comments endpoint
|
|
195
|
-
post =
|
|
231
|
+
post = json[0].data.children[0].data
|
|
196
232
|
} else {
|
|
197
233
|
// When using the info endpoint
|
|
198
|
-
if (!
|
|
234
|
+
if (!json.data.children.length) {
|
|
199
235
|
throw new Error(`Post with ID ${postId} not found`)
|
|
200
236
|
}
|
|
201
|
-
post =
|
|
237
|
+
post = json.data.children[0].data
|
|
202
238
|
}
|
|
203
239
|
|
|
204
240
|
return {
|
|
@@ -228,11 +264,15 @@ export class RedditClient {
|
|
|
228
264
|
async getTrendingSubreddits(limit: number = 5): Promise<string[]> {
|
|
229
265
|
await this.authenticate()
|
|
230
266
|
try {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
267
|
+
const params = new URLSearchParams({ limit: limit.toString() })
|
|
268
|
+
const response = await this.makeRequest(`/subreddits/popular.json?${params}`)
|
|
269
|
+
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
throw new Error(`HTTP ${response.status}`)
|
|
272
|
+
}
|
|
234
273
|
|
|
235
|
-
|
|
274
|
+
const json = (await response.json()) as { data: { children: any[] } }
|
|
275
|
+
return json.data.children.map((child: any) => child.data.display_name)
|
|
236
276
|
} catch {
|
|
237
277
|
// Failed to get trending subreddits
|
|
238
278
|
throw new Error("Failed to get trending subreddits")
|
|
@@ -254,15 +294,22 @@ export class RedditClient {
|
|
|
254
294
|
params.append("title", title)
|
|
255
295
|
params.append(isSelf ? "text" : "url", content)
|
|
256
296
|
|
|
257
|
-
const response = await this.
|
|
297
|
+
const response = await this.makeRequest("/api/submit", {
|
|
298
|
+
method: "POST",
|
|
258
299
|
headers: {
|
|
259
300
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
260
301
|
},
|
|
302
|
+
body: params.toString(),
|
|
261
303
|
})
|
|
262
304
|
|
|
263
|
-
if (response.
|
|
305
|
+
if (!response.ok) {
|
|
306
|
+
throw new Error(`HTTP ${response.status}`)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const json = (await response.json()) as { success: boolean; data: { id: string } }
|
|
310
|
+
if (json.success) {
|
|
264
311
|
// Get the newly created post
|
|
265
|
-
const postId =
|
|
312
|
+
const postId = json.data.id
|
|
266
313
|
return await this.getPost(postId)
|
|
267
314
|
} else {
|
|
268
315
|
throw new Error("Failed to create post")
|
|
@@ -276,8 +323,13 @@ export class RedditClient {
|
|
|
276
323
|
async checkPostExists(postId: string): Promise<boolean> {
|
|
277
324
|
await this.authenticate()
|
|
278
325
|
try {
|
|
279
|
-
const response = await this.
|
|
280
|
-
|
|
326
|
+
const response = await this.makeRequest(`/api/info.json?id=t3_${postId}`)
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
return false
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const json = (await response.json()) as { data: { children: any[] } }
|
|
332
|
+
return json.data.children.length > 0
|
|
281
333
|
} catch {
|
|
282
334
|
return false
|
|
283
335
|
}
|
|
@@ -299,14 +351,20 @@ export class RedditClient {
|
|
|
299
351
|
params.append("thing_id", `t3_${postId}`)
|
|
300
352
|
params.append("text", content)
|
|
301
353
|
|
|
302
|
-
const response = await this.
|
|
354
|
+
const response = await this.makeRequest("/api/comment", {
|
|
355
|
+
method: "POST",
|
|
303
356
|
headers: {
|
|
304
357
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
305
358
|
},
|
|
359
|
+
body: params.toString(),
|
|
306
360
|
})
|
|
307
361
|
|
|
362
|
+
if (!response.ok) {
|
|
363
|
+
throw new Error(`HTTP ${response.status}`)
|
|
364
|
+
}
|
|
365
|
+
|
|
308
366
|
// Extract comment data from response
|
|
309
|
-
const commentData = response.
|
|
367
|
+
const commentData = (await response.json()) as any
|
|
310
368
|
return {
|
|
311
369
|
id: commentData.id,
|
|
312
370
|
author: this.username,
|
|
@@ -325,6 +383,246 @@ export class RedditClient {
|
|
|
325
383
|
throw new Error(`Failed to reply to post ${postId}`)
|
|
326
384
|
}
|
|
327
385
|
}
|
|
386
|
+
|
|
387
|
+
async searchReddit(
|
|
388
|
+
query: string,
|
|
389
|
+
options: {
|
|
390
|
+
subreddit?: string
|
|
391
|
+
sort?: string
|
|
392
|
+
timeFilter?: string
|
|
393
|
+
limit?: number
|
|
394
|
+
type?: string
|
|
395
|
+
} = {},
|
|
396
|
+
): Promise<RedditPost[]> {
|
|
397
|
+
await this.authenticate()
|
|
398
|
+
try {
|
|
399
|
+
const { subreddit, sort = "relevance", timeFilter = "all", limit = 25, type = "link" } = options
|
|
400
|
+
const endpoint = subreddit ? `/r/${subreddit}/search.json` : "/search.json"
|
|
401
|
+
|
|
402
|
+
const params = new URLSearchParams({
|
|
403
|
+
q: query,
|
|
404
|
+
sort,
|
|
405
|
+
t: timeFilter,
|
|
406
|
+
limit: limit.toString(),
|
|
407
|
+
type,
|
|
408
|
+
...(subreddit && { restrict_sr: "true" }),
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
const response = await this.makeRequest(`${endpoint}?${params}`)
|
|
412
|
+
if (!response.ok) {
|
|
413
|
+
throw new Error(`HTTP ${response.status}`)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const json = (await response.json()) as { data: { children: any[] } }
|
|
417
|
+
|
|
418
|
+
return json.data.children
|
|
419
|
+
.filter((child: any) => child.kind === "t3") // Only posts
|
|
420
|
+
.map((child: any) => {
|
|
421
|
+
const post = child.data
|
|
422
|
+
return {
|
|
423
|
+
id: post.id,
|
|
424
|
+
title: post.title,
|
|
425
|
+
author: post.author,
|
|
426
|
+
subreddit: post.subreddit,
|
|
427
|
+
selftext: post.selftext || "",
|
|
428
|
+
url: post.url,
|
|
429
|
+
score: post.score,
|
|
430
|
+
upvoteRatio: post.upvote_ratio,
|
|
431
|
+
numComments: post.num_comments,
|
|
432
|
+
createdUtc: post.created_utc,
|
|
433
|
+
over18: post.over_18,
|
|
434
|
+
spoiler: post.spoiler,
|
|
435
|
+
edited: !!post.edited,
|
|
436
|
+
isSelf: post.is_self,
|
|
437
|
+
linkFlairText: post.link_flair_text,
|
|
438
|
+
permalink: post.permalink,
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
} catch {
|
|
442
|
+
throw new Error(`Failed to search Reddit for: ${query}`)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async getPostComments(
|
|
447
|
+
postId: string,
|
|
448
|
+
subreddit: string,
|
|
449
|
+
options: {
|
|
450
|
+
sort?: string
|
|
451
|
+
limit?: number
|
|
452
|
+
} = {},
|
|
453
|
+
): Promise<{ post: RedditPost; comments: RedditComment[] }> {
|
|
454
|
+
await this.authenticate()
|
|
455
|
+
try {
|
|
456
|
+
const { sort = "best", limit = 100 } = options
|
|
457
|
+
const params = new URLSearchParams({
|
|
458
|
+
sort,
|
|
459
|
+
limit: limit.toString(),
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const response = await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`)
|
|
463
|
+
if (!response.ok) {
|
|
464
|
+
throw new Error(`HTTP ${response.status}`)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const json = (await response.json()) as any[]
|
|
468
|
+
|
|
469
|
+
// First element is the post, second is the comments
|
|
470
|
+
const postData = json[0].data.children[0].data
|
|
471
|
+
const post: RedditPost = {
|
|
472
|
+
id: postData.id,
|
|
473
|
+
title: postData.title,
|
|
474
|
+
author: postData.author,
|
|
475
|
+
subreddit: postData.subreddit,
|
|
476
|
+
selftext: postData.selftext || "",
|
|
477
|
+
url: postData.url,
|
|
478
|
+
score: postData.score,
|
|
479
|
+
upvoteRatio: postData.upvote_ratio,
|
|
480
|
+
numComments: postData.num_comments,
|
|
481
|
+
createdUtc: postData.created_utc,
|
|
482
|
+
over18: postData.over_18,
|
|
483
|
+
spoiler: postData.spoiler,
|
|
484
|
+
edited: !!postData.edited,
|
|
485
|
+
isSelf: postData.is_self,
|
|
486
|
+
linkFlairText: postData.link_flair_text,
|
|
487
|
+
permalink: postData.permalink,
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const comments: RedditComment[] = []
|
|
491
|
+
const parseComments = (commentData: any[], depth: number = 0) => {
|
|
492
|
+
for (const item of commentData) {
|
|
493
|
+
if (item.kind === "t1" && item.data.body) {
|
|
494
|
+
comments.push({
|
|
495
|
+
id: item.data.id,
|
|
496
|
+
author: item.data.author,
|
|
497
|
+
body: item.data.body,
|
|
498
|
+
score: item.data.score,
|
|
499
|
+
controversiality: item.data.controversiality,
|
|
500
|
+
subreddit: item.data.subreddit,
|
|
501
|
+
submissionTitle: post.title,
|
|
502
|
+
createdUtc: item.data.created_utc,
|
|
503
|
+
edited: !!item.data.edited,
|
|
504
|
+
isSubmitter: item.data.is_submitter,
|
|
505
|
+
permalink: item.data.permalink,
|
|
506
|
+
depth,
|
|
507
|
+
parentId: item.data.parent_id,
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
// Parse replies recursively
|
|
511
|
+
if (item.data.replies && item.data.replies.data && item.data.replies.data.children) {
|
|
512
|
+
parseComments(item.data.replies.data.children, depth + 1)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (json[1] && json[1].data && json[1].data.children) {
|
|
519
|
+
parseComments(json[1].data.children)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return { post, comments }
|
|
523
|
+
} catch {
|
|
524
|
+
throw new Error(`Failed to get comments for post ${postId}`)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async getUserPosts(
|
|
529
|
+
username: string,
|
|
530
|
+
options: {
|
|
531
|
+
sort?: string
|
|
532
|
+
timeFilter?: string
|
|
533
|
+
limit?: number
|
|
534
|
+
} = {},
|
|
535
|
+
): Promise<RedditPost[]> {
|
|
536
|
+
await this.authenticate()
|
|
537
|
+
try {
|
|
538
|
+
const { sort = "new", timeFilter = "all", limit = 25 } = options
|
|
539
|
+
const params = new URLSearchParams({
|
|
540
|
+
sort,
|
|
541
|
+
t: timeFilter,
|
|
542
|
+
limit: limit.toString(),
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
const response = await this.makeRequest(`/user/${username}/submitted.json?${params}`)
|
|
546
|
+
if (!response.ok) {
|
|
547
|
+
throw new Error(`HTTP ${response.status}`)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const json = (await response.json()) as { data: { children: any[] } }
|
|
551
|
+
|
|
552
|
+
return json.data.children
|
|
553
|
+
.filter((child: any) => child.kind === "t3")
|
|
554
|
+
.map((child: any) => {
|
|
555
|
+
const post = child.data
|
|
556
|
+
return {
|
|
557
|
+
id: post.id,
|
|
558
|
+
title: post.title,
|
|
559
|
+
author: post.author,
|
|
560
|
+
subreddit: post.subreddit,
|
|
561
|
+
selftext: post.selftext || "",
|
|
562
|
+
url: post.url,
|
|
563
|
+
score: post.score,
|
|
564
|
+
upvoteRatio: post.upvote_ratio,
|
|
565
|
+
numComments: post.num_comments,
|
|
566
|
+
createdUtc: post.created_utc,
|
|
567
|
+
over18: post.over_18,
|
|
568
|
+
spoiler: post.spoiler,
|
|
569
|
+
edited: !!post.edited,
|
|
570
|
+
isSelf: post.is_self,
|
|
571
|
+
linkFlairText: post.link_flair_text,
|
|
572
|
+
permalink: post.permalink,
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
} catch {
|
|
576
|
+
throw new Error(`Failed to get posts for user ${username}`)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async getUserComments(
|
|
581
|
+
username: string,
|
|
582
|
+
options: {
|
|
583
|
+
sort?: string
|
|
584
|
+
timeFilter?: string
|
|
585
|
+
limit?: number
|
|
586
|
+
} = {},
|
|
587
|
+
): Promise<RedditComment[]> {
|
|
588
|
+
await this.authenticate()
|
|
589
|
+
try {
|
|
590
|
+
const { sort = "new", timeFilter = "all", limit = 25 } = options
|
|
591
|
+
const params = new URLSearchParams({
|
|
592
|
+
sort,
|
|
593
|
+
t: timeFilter,
|
|
594
|
+
limit: limit.toString(),
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
const response = await this.makeRequest(`/user/${username}/comments.json?${params}`)
|
|
598
|
+
if (!response.ok) {
|
|
599
|
+
throw new Error(`HTTP ${response.status}`)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const json = (await response.json()) as { data: { children: any[] } }
|
|
603
|
+
|
|
604
|
+
return json.data.children
|
|
605
|
+
.filter((child: any) => child.kind === "t1")
|
|
606
|
+
.map((child: any) => {
|
|
607
|
+
const comment = child.data
|
|
608
|
+
return {
|
|
609
|
+
id: comment.id,
|
|
610
|
+
author: comment.author,
|
|
611
|
+
body: comment.body,
|
|
612
|
+
score: comment.score,
|
|
613
|
+
controversiality: comment.controversiality,
|
|
614
|
+
subreddit: comment.subreddit,
|
|
615
|
+
submissionTitle: comment.link_title || "",
|
|
616
|
+
createdUtc: comment.created_utc,
|
|
617
|
+
edited: !!comment.edited,
|
|
618
|
+
isSubmitter: comment.is_submitter,
|
|
619
|
+
permalink: comment.permalink,
|
|
620
|
+
}
|
|
621
|
+
})
|
|
622
|
+
} catch {
|
|
623
|
+
throw new Error(`Failed to get comments for user ${username}`)
|
|
624
|
+
}
|
|
625
|
+
}
|
|
328
626
|
}
|
|
329
627
|
|
|
330
628
|
// Create and export singleton instance
|