selfhostable, read-only reddit client
1class Geddit {
2 constructor() {
3 this.host = "https://www.reddit.com";
4 this.parameters = {
5 limit: 25,
6 include_over_18: true,
7 };
8 this.search_params = {
9 limit: 25,
10 include_over_18: true,
11 type: "sr,link,user",
12 };
13 this.headers = {
14 "User-Agent":
15 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
16 };
17 }
18
19 async getSubmissions(sort = "hot", subreddit = null, options = {}) {
20 const params = {
21 limit: 20,
22 include_over_18: true,
23 };
24
25 const subredditStr = subreddit ? `/r/${subreddit}` : "";
26
27 return await fetch(
28 `${
29 this.host + subredditStr
30 }/${sort}.json?${new URLSearchParams(Object.assign(params, options))}`,
31 { headers: this.headers },
32 )
33 .then((res) => res.json())
34 .then((json) => json.data)
35 .then((data) => ({
36 after: data.after,
37 posts: data.children,
38 }))
39 .catch((err) => null);
40 }
41
42 async getDomainHot(domain, options = this.parameters) {
43 return await fetch(
44 `${this.host}/domain/${domain}/hot.json?${new URLSearchParams(options)}`,
45 { headers: this.headers },
46 )
47 .then((res) => res.json())
48 .then((json) => json.data)
49 .then((data) => ({
50 after: data.after,
51 posts: data.children,
52 }))
53 .catch((err) => null);
54 }
55
56 async getDomainBest(domain, options = this.parameters) {
57 return await fetch(
58 `${this.host}/domain/${domain}/best.json?${new URLSearchParams(options)}`,
59 { headers: this.headers },
60 )
61 .then((res) => res.json())
62 .then((json) => json.data)
63 .then((data) => ({
64 after: data.after,
65 posts: data.children,
66 }))
67 .catch((err) => null);
68 }
69
70 async getDomainTop(domain, options = this.parameters) {
71 return await fetch(
72 `${this.host}/domain/${domain}/top.json?${new URLSearchParams(options)}`,
73 { headers: this.headers },
74 )
75 .then((res) => res.json())
76 .then((json) => json.data)
77 .then((data) => ({
78 after: data.after,
79 posts: data.children,
80 }))
81 .catch((_) => null);
82 }
83
84 async getDomainNew(domain, options = this.parameters) {
85 return await fetch(
86 `${this.host}/domain/${domain}/new.json?${new URLSearchParams(options)}`,
87 { headers: this.headers },
88 )
89 .then((res) => res.json())
90 .then((json) => json.data)
91 .then((data) => ({
92 after: data.after,
93 posts: data.children,
94 }))
95 .catch((err) => null);
96 }
97
98 async getDomainRising(domain, options = this.parameters) {
99 return await fetch(
100 `${this.host}/domain/${domain}/rising.json?${new URLSearchParams(options)}`,
101 { headers: this.headers },
102 )
103 .then((res) => res.json())
104 .then((json) => json.data)
105 .then((data) => ({
106 after: data.after,
107 posts: data.children,
108 }))
109 .catch((err) => null);
110 }
111
112 async getDomainControversial(domain, options = this.parameters) {
113 return await fetch(
114 `${this.host}/domain/${domain}/controversial.json?${new URLSearchParams(options)}`,
115 { headers: this.headers },
116 )
117 .then((res) => res.json())
118 .then((json) => json.data)
119 .then((data) => ({
120 after: data.after,
121 posts: data.children,
122 }))
123 .catch((err) => null);
124 }
125
126 async getSubreddit(subreddit) {
127 return await fetch(`${this.host}/r/${subreddit}/about.json`, {
128 headers: this.headers,
129 })
130 .then((res) => res.json())
131 .then((json) => json.data)
132 .catch((err) => null);
133 }
134
135 async getSubredditRules(subreddit) {
136 return await fetch(`${this.host}/r/${subreddit}/about/rules.json`, {
137 headers: this.headers,
138 })
139 .then((res) => res.json())
140 .then((json) => json.data)
141 .catch((err) => null);
142 }
143
144 async getSubredditModerators(subreddit) {
145 return await fetch(`${this.host}/r/${subreddit}/about/moderators.json`, {
146 headers: this.headers,
147 })
148 .then((res) => res.json())
149 .then((json) => json.data)
150 .then((data) => ({
151 users: data.children,
152 }))
153 .catch((err) => null);
154 }
155
156 async getSubredditWikiPages(subreddit) {
157 return await fetch(`${this.host}/r/${subreddit}/wiki/pages.json`, {
158 headers: this.headers,
159 })
160 .then((res) => res.json())
161 .then((json) => json.data)
162 .catch((err) => null);
163 }
164
165 async getSubredditWikiPage(subreddit, page) {
166 return await fetch(`${this.host}/r/${subreddit}/wiki/${page}.json`, {
167 headers: this.headers,
168 })
169 .then((res) => res.json())
170 .then((json) => json.data)
171 .catch((err) => null);
172 }
173
174 async getSubredditWikiPageRevisions(subreddit, page) {
175 return await fetch(
176 `${this.host}/r/${subreddit}/wiki/revisions${page}.json`,
177 { headers: this.headers },
178 )
179 .then((res) => res.json())
180 .then((json) => json.data.children)
181 .catch((err) => null);
182 }
183
184 async getPopularSubreddits(options = this.parameters) {
185 return await fetch(
186 `${this.host}/subreddits/popular.json?${new URLSearchParams(options)}`,
187 { headers: this.headers },
188 )
189 .then((res) => res.json())
190 .then((json) => json.data)
191 .then((data) => ({
192 after: data.after,
193 subreddits: data.children,
194 }))
195 .catch((err) => null);
196 }
197
198 async getNewSubreddits(options = this.parameters) {
199 return await fetch(
200 `${this.host}/subreddits/new.json?${new URLSearchParams(options)}`,
201 { headers: this.headers },
202 )
203 .then((res) => res.json())
204 .then((json) => json.data)
205 .then((data) => ({
206 after: data.after,
207 subreddits: data.children,
208 }))
209 .catch((err) => null);
210 }
211
212 async getPremiumSubreddits(options = this.parameters) {
213 return await fetch(
214 `${this.host}/subreddits/premium.json?${new URLSearchParams(options)}`,
215 { headers: this.headers },
216 )
217 .then((res) => res.json())
218 .then((json) => json.data)
219 .then((data) => ({
220 after: data.after,
221 subreddits: data.children,
222 }))
223 .catch((err) => null);
224 }
225
226 async getDefaultSubreddits(options = this.parameters) {
227 return await fetch(
228 `${this.host}/subreddits/default.json?${new URLSearchParams(options)}`,
229 { headers: this.headers },
230 )
231 .then((res) => res.json())
232 .then((json) => json.data)
233 .then((data) => ({
234 after: data.after,
235 subreddits: data.children,
236 }))
237 .catch((err) => null);
238 }
239
240 async getPopularUsers(options = this.parameters) {
241 return await fetch(
242 `${this.host}/users/popular.json?${new URLSearchParams(options)}`,
243 { headers: this.headers },
244 )
245 .then((res) => res.json())
246 .then((json) => json.data)
247 .then((data) => ({
248 after: data.after,
249 users: data.children,
250 }))
251 .catch((err) => null);
252 }
253
254 async getNewUsers(options = this.parameters) {
255 return await fetch(
256 `${this.host}/users/new.json?${new URLSearchParams(options)}`,
257 { headers: this.headers },
258 )
259 .then((res) => res.json())
260 .then((json) => json.data)
261 .then((data) => ({
262 after: data.after,
263 users: data.children,
264 }))
265 .catch((err) => null);
266 }
267
268 async searchSubmissions(query, options = {}) {
269 options.q = query;
270 options.type = "link";
271
272 return await fetch(
273 `${this.host}/search.json?${new URLSearchParams(options)}`,
274 { headers: this.headers },
275 )
276 .then((res) => res.json())
277 .then((json) => json.data)
278 .then((data) => ({
279 after: data.after,
280 items: data.children,
281 }))
282 .catch((err) => null);
283 }
284
285 async searchSubreddits(query, options = {}) {
286 options.q = query;
287
288 const params = {
289 limit: 25,
290 include_over_18: false,
291 };
292
293 return await fetch(
294 `${this.host}/subreddits/search.json?${new URLSearchParams(Object.assign(params, options))}`,
295 { headers: this.headers },
296 )
297 .then((res) => res.json())
298 .then((json) => json.data)
299 .then((data) => ({
300 after: data.after,
301 items: data.children,
302 }))
303 .catch((err) => null);
304 }
305
306 async searchUsers(query, options = {}) {
307 options.q = query;
308
309 const params = {
310 limit: 25,
311 include_over_18: true,
312 };
313
314 return await fetch(
315 `${this.host}/users/search.json?${new URLSearchParams(Object.assign(params, options))}`,
316 { headers: this.headers },
317 )
318 .then((res) => res.json())
319 .then((json) => json.data)
320 .then((data) => ({
321 after: data.after,
322 items: data.children,
323 }))
324 .catch((err) => null);
325 }
326
327 async searchAll(query, subreddit = null, options = {}) {
328 options.q = query;
329 const subredditStr = subreddit ? `/r/${subreddit}` : "";
330
331 const params = {
332 limit: 25,
333 include_over_18: true,
334 type: "sr,link,user",
335 };
336
337 return await fetch(
338 `${
339 this.host + subredditStr
340 }/search.json?${new URLSearchParams(Object.assign(params, options))}`,
341 { headers: this.headers },
342 )
343 .then((res) => res.json())
344 .then((json) =>
345 Array.isArray(json)
346 ? {
347 after: json[1].data.after,
348 items: json[0].data.children.concat(json[1].data.children),
349 }
350 : {
351 after: json.data.after,
352 items: json.data.children,
353 },
354 )
355 .catch((err) => null);
356 }
357
358 async getSubmission(id) {
359 return await fetch(`${this.host}/by_id/${id}.json`, {
360 headers: this.headers,
361 })
362 .then((res) => res.json())
363 .then((json) => json.data.children[0].data)
364 .catch((err) => null);
365 }
366
367 async getSubmissionComments(id, options = this.parameters) {
368 return await fetch(
369 `${this.host}/comments/${id}.json?${new URLSearchParams(options)}`,
370 { headers: this.headers },
371 )
372 .then((res) => res.json())
373 .then((json) => ({
374 submission: json[0].data.children[0],
375 comments: json[1].data.children,
376 }))
377 .catch((err) => null);
378 }
379
380 async getSingleCommentThread(parent_id, child_id, options = this.parameters) {
381 return await fetch(
382 `${this.host}/comments/${parent_id}/comment/${child_id}.json?${new URLSearchParams(options)}`,
383 { headers: this.headers },
384 )
385 .then((res) => res.json())
386 .then((json) => ({
387 submission: json[0].data.children[0],
388 comments: json[1].data.children,
389 }))
390 .catch((err) => null);
391 }
392
393 async getSubredditComments(subreddit, options = this.parameters) {
394 return await fetch(
395 `${this.host}/r/${subreddit}/comments.json?${new URLSearchParams(options)}`,
396 { headers: this.headers },
397 )
398 .then((res) => res.json())
399 .then((json) => json.data.children)
400 .catch((err) => null);
401 }
402
403 async getUser(username) {
404 return await fetch(`${this.host}/user/${username}/about.json`, {
405 headers: this.headers,
406 })
407 .then((res) => res.json())
408 .then((json) => json.data)
409 .catch((err) => null);
410 }
411
412 async getUserOverview(username, options = this.parameters) {
413 return await fetch(
414 `${this.host}/user/${username}/overview.json?${new URLSearchParams(options)}`,
415 { headers: this.headers },
416 )
417 .then((res) => res.json())
418 .then((json) => json.data)
419 .then((data) => ({
420 after: data.after,
421 items: data.children,
422 }))
423 .catch((err) => null);
424 }
425
426 async getUserComments(username, options = this.parameters) {
427 return await fetch(
428 `${this.host}/user/${username}/comments.json?${new URLSearchParams(options)}`,
429 { headers: this.headers },
430 )
431 .then((res) => res.json())
432 .then((json) => json.data)
433 .then((data) => ({
434 after: data.after,
435 items: data.children,
436 }))
437 .catch((err) => null);
438 }
439
440 async getUserSubmissions(username, options = this.parameters) {
441 return await fetch(
442 `${this.host}/user/${username}/submitted.json?${new URLSearchParams(options)}`,
443 { headers: this.headers },
444 )
445 .then((res) => res.json())
446 .then((json) => json.data)
447 .then((data) => ({
448 after: data.after,
449 items: data.children,
450 }))
451 .catch((err) => null);
452 }
453
454 async getLiveThread(id) {
455 return await fetch(`${this.host}/live/${id}/about.json`, {
456 headers: this.headers,
457 })
458 .then((res) => res.json())
459 .then((json) => json.data)
460 .catch((err) => null);
461 }
462
463 async getLiveThreadUpdates(id, options = this.parameters) {
464 return await fetch(
465 `${this.host}/live/${id}.json?${new URLSearchParams(options)}`,
466 { headers: this.headers },
467 )
468 .then((res) => res.json())
469 .then((json) => json.data.children)
470 .catch((err) => null);
471 }
472
473 async getLiveThreadContributors(id, options = this.parameters) {
474 return await fetch(
475 `${this.host}/live/${id}/contributors.json?${new URLSearchParams(options)}`,
476 { headers: this.headers },
477 )
478 .then((res) => res.json())
479 .then((json) => json.data.children)
480 .catch((err) => null);
481 }
482
483 async getLiveThreadDiscussions(id, options = this.parameters) {
484 return await fetch(
485 `${this.host}/live/${id}/discussions.json?${new URLSearchParams(options)}`,
486 { headers: this.headers },
487 )
488 .then((res) => res.json())
489 .then((json) => json.data.children)
490 .catch((err) => null);
491 }
492
493 async getLiveThreadsNow(options = this.parameters) {
494 return await fetch(
495 `${this.host}/live/happening_now.json?${new URLSearchParams(options)}`,
496 { headers: this.headers },
497 )
498 .then((res) => res.json())
499 .then((json) => json.data.children)
500 .catch((err) => null);
501 }
502}
503
504export { Geddit };