+179
.gitignore
+179
.gitignore
···
1
+
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2
+
3
+
# Logs
4
+
5
+
logs
6
+
_.log
7
+
npm-debug.log_
8
+
yarn-debug.log*
9
+
yarn-error.log*
10
+
lerna-debug.log*
11
+
.pnpm-debug.log*
12
+
13
+
# Caches
14
+
15
+
.cache
16
+
17
+
# Diagnostic reports (https://nodejs.org/api/report.html)
18
+
19
+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
20
+
21
+
# Runtime data
22
+
23
+
pids
24
+
_.pid
25
+
_.seed
26
+
*.pid.lock
27
+
28
+
# Directory for instrumented libs generated by jscoverage/JSCover
29
+
30
+
lib-cov
31
+
32
+
# Coverage directory used by tools like istanbul
33
+
34
+
coverage
35
+
*.lcov
36
+
37
+
# nyc test coverage
38
+
39
+
.nyc_output
40
+
41
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
42
+
43
+
.grunt
44
+
45
+
# Bower dependency directory (https://bower.io/)
46
+
47
+
bower_components
48
+
49
+
# node-waf configuration
50
+
51
+
.lock-wscript
52
+
53
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
54
+
55
+
build/Release
56
+
57
+
# Dependency directories
58
+
59
+
node_modules/
60
+
jspm_packages/
61
+
62
+
# Snowpack dependency directory (https://snowpack.dev/)
63
+
64
+
web_modules/
65
+
66
+
# TypeScript cache
67
+
68
+
*.tsbuildinfo
69
+
70
+
# Optional npm cache directory
71
+
72
+
.npm
73
+
74
+
# Optional eslint cache
75
+
76
+
.eslintcache
77
+
78
+
# Optional stylelint cache
79
+
80
+
.stylelintcache
81
+
82
+
# Microbundle cache
83
+
84
+
.rpt2_cache/
85
+
.rts2_cache_cjs/
86
+
.rts2_cache_es/
87
+
.rts2_cache_umd/
88
+
89
+
# Optional REPL history
90
+
91
+
.node_repl_history
92
+
93
+
# Output of 'npm pack'
94
+
95
+
*.tgz
96
+
97
+
# Yarn Integrity file
98
+
99
+
.yarn-integrity
100
+
101
+
# dotenv environment variable files
102
+
103
+
.env
104
+
.env.development.local
105
+
.env.test.local
106
+
.env.production.local
107
+
.env.local
108
+
109
+
# parcel-bundler cache (https://parceljs.org/)
110
+
111
+
.parcel-cache
112
+
113
+
# Next.js build output
114
+
115
+
.next
116
+
out
117
+
118
+
# Nuxt.js build / generate output
119
+
120
+
.nuxt
121
+
dist
122
+
123
+
# Gatsby files
124
+
125
+
# Comment in the public line in if your project uses Gatsby and not Next.js
126
+
127
+
# https://nextjs.org/blog/next-9-1#public-directory-support
128
+
129
+
# public
130
+
131
+
# vuepress build output
132
+
133
+
.vuepress/dist
134
+
135
+
# vuepress v2.x temp and cache directory
136
+
137
+
.temp
138
+
139
+
# Docusaurus cache and generated files
140
+
141
+
.docusaurus
142
+
143
+
# Serverless directories
144
+
145
+
.serverless/
146
+
147
+
# FuseBox cache
148
+
149
+
.fusebox/
150
+
151
+
# DynamoDB Local files
152
+
153
+
.dynamodb/
154
+
155
+
# TernJS port file
156
+
157
+
.tern-port
158
+
159
+
# Stores VSCode versions used for testing VSCode extensions
160
+
161
+
.vscode-test
162
+
163
+
# yarn v2
164
+
165
+
.yarn/cache
166
+
.yarn/unplugged
167
+
.yarn/build-state.yml
168
+
.yarn/install-state.gz
169
+
.pnp.*
170
+
171
+
# IntelliJ based IDEs
172
+
.idea
173
+
174
+
# Finder (MacOS) folder config
175
+
.DS_Store
176
+
177
+
reference
178
+
bun.lockb
179
+
result
+9
build.js
+9
build.js
···
1
+
import { execSync } from "bun";
2
+
3
+
try {
4
+
// Precompile Pug templates in the `src` directory to the `dist` directory
5
+
execSync("pug src -o dist");
6
+
console.log("Pug templates compiled successfully.");
7
+
} catch (error) {
8
+
console.error("Failed to compile Pug templates:", error);
9
+
}
+27
flake.lock
+27
flake.lock
···
1
+
{
2
+
"nodes": {
3
+
"nixpkgs": {
4
+
"locked": {
5
+
"lastModified": 1724748588,
6
+
"narHash": "sha256-NlpGA4+AIf1dKNq76ps90rxowlFXUsV9x7vK/mN37JM=",
7
+
"owner": "nixos",
8
+
"repo": "nixpkgs",
9
+
"rev": "a6292e34000dc93d43bccf78338770c1c5ec8a99",
10
+
"type": "github"
11
+
},
12
+
"original": {
13
+
"owner": "nixos",
14
+
"ref": "nixpkgs-unstable",
15
+
"repo": "nixpkgs",
16
+
"type": "github"
17
+
}
18
+
},
19
+
"root": {
20
+
"inputs": {
21
+
"nixpkgs": "nixpkgs"
22
+
}
23
+
}
24
+
},
25
+
"root": "root",
26
+
"version": 7
27
+
}
+99
flake.nix
+99
flake.nix
···
1
+
{
2
+
inputs = {
3
+
4
+
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
5
+
6
+
};
7
+
8
+
outputs =
9
+
{ self
10
+
, nixpkgs
11
+
}:
12
+
let
13
+
supportedSystems = [ "x86_64-linux" ];
14
+
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
15
+
nixpkgsFor = forAllSystems (system:
16
+
import nixpkgs {
17
+
inherit system;
18
+
overlays = [ self.overlays.default ];
19
+
});
20
+
21
+
in
22
+
{
23
+
overlays.default = final: prev: {
24
+
node_modules = with final; stdenv.mkDerivation {
25
+
pname = "readit-node-modules";
26
+
version = "0.0.1";
27
+
impureEnvVars = lib.fetchers.proxyImpureEnvVars
28
+
++ [ "GIT_PROXY_COMMAND" "SOCKS_SERVER" ];
29
+
src = ./.;
30
+
nativeBuildInputs = [ bun ];
31
+
buildInputs = [ nodejs-slim_latest ];
32
+
dontConfigure = true;
33
+
dontFixup = true;
34
+
buildPhase = ''
35
+
bun install --no-progress --frozen-lockfile
36
+
'';
37
+
installPhase = ''
38
+
mkdir -p $out/node_modules
39
+
cp -R ./node_modules/* $out/node_modules
40
+
ls -la $out/node_modules
41
+
'';
42
+
outputHash = "sha256-qFYgRIarDChHQu0ZrUKd/Y61gxaagMWpf2h9xizwGv4=";
43
+
outputHashAlgo = "sha256";
44
+
outputHashMode = "recursive";
45
+
};
46
+
readit = with final; stdenv.mkDerivation {
47
+
pname = "readit";
48
+
version = "0.0.1";
49
+
src = ./.;
50
+
nativeBuildInputs = [ makeBinaryWrapper ];
51
+
buildInputs = [ bun ];
52
+
53
+
buildPhase = ''
54
+
runHook preBuild
55
+
56
+
57
+
runHook postBuild
58
+
'';
59
+
60
+
dontFixup = true;
61
+
62
+
installPhase = ''
63
+
runHook preInstall
64
+
65
+
mkdir -p $out/bin
66
+
67
+
# cp app.js $out/app.js
68
+
cp -R ./* $out
69
+
70
+
# bun is referenced naked in the package.json generated script
71
+
# makeBinaryWrapper ${bun}/bin/bun $out/bin/$pname \
72
+
# --add-flags "run --prefer-offline --no-install $out/app.js"
73
+
74
+
makeBinaryWrapper ${bun}/bin/bun $out/bin/$pname \
75
+
--prefix PATH : ${lib.makeBinPath [ bun ]} \
76
+
--add-flags "run --prefer-offline --no-install $out/src/index.js"
77
+
78
+
'';
79
+
};
80
+
};
81
+
82
+
devShell = forAllSystems (system:
83
+
let
84
+
pkgs = nixpkgsFor."${system}";
85
+
in
86
+
pkgs.mkShell {
87
+
nativeBuildInputs = [
88
+
pkgs.bun
89
+
];
90
+
RUST_BACKTRACE = 1;
91
+
});
92
+
93
+
packages = forAllSystems(system: {
94
+
inherit (nixpkgsFor."${system}") readit node_modules;
95
+
});
96
+
};
97
+
}
98
+
99
+
+17
package.json
+17
package.json
···
1
+
{
2
+
"name": "scarlet",
3
+
"module": "index.ts",
4
+
"type": "module",
5
+
"devDependencies": {
6
+
"@types/bun": "latest",
7
+
"pug-cli": "^1.0.0-alpha6"
8
+
},
9
+
"peerDependencies": {
10
+
"typescript": "^5.0.0"
11
+
},
12
+
"dependencies": {
13
+
"express": "^4.19.2",
14
+
"pug": "^3.0.3",
15
+
"timeago.js": "^4.0.2"
16
+
}
17
+
}
+92
public/styles.css
+92
public/styles.css
···
1
+
main {
2
+
display: flex;
3
+
flex-direction: column;
4
+
gap: 1rem;
5
+
align-items: center;
6
+
}
7
+
8
+
.post, .comments-container, .header {
9
+
padding: 0.3rem;
10
+
flex: 1 1 95%;
11
+
font-size: 1rem;
12
+
width: 95%;
13
+
}
14
+
15
+
@media (min-width: 768px) {
16
+
.post, .comments-container, .header {
17
+
flex: 1 1 65%;
18
+
width: 65%;
19
+
}
20
+
}
21
+
22
+
@media (min-width: 1080px) {
23
+
.post, .comments-container, .header {
24
+
flex: 1 1 50%;
25
+
width: 50%;
26
+
}
27
+
}
28
+
29
+
.comments-container, .self-text {
30
+
text-align: justify;
31
+
}
32
+
33
+
.comment, .more {
34
+
width: 100%;
35
+
border-left: 1px dashed;
36
+
padding: 10px 0px 10px 24px;
37
+
box-sizing: border-box;
38
+
}
39
+
40
+
.more {
41
+
margin-bottom: 0px;
42
+
font-size: 0.7rem;
43
+
color: #777;
44
+
}
45
+
46
+
.first {
47
+
border-left: none;
48
+
padding-left: 0;
49
+
margin: 0;
50
+
margin-top: 12px;
51
+
}
52
+
53
+
.post-container {
54
+
align-self: stretch;
55
+
display: flex;
56
+
}
57
+
58
+
.post-text {
59
+
flex-direction: column;
60
+
align-items: stretch;
61
+
justify-content: space-between;
62
+
}
63
+
64
+
.media-preview {
65
+
padding-left: 10px;
66
+
margin-left: auto;
67
+
}
68
+
69
+
.post-media {
70
+
max-width: 100%;
71
+
}
72
+
73
+
.title-container,.info-container {
74
+
flex: 1;
75
+
margin-top: 10px;
76
+
margin-bottom: 10px;
77
+
}
78
+
79
+
.info-container {
80
+
color: #777;
81
+
font-size: 0.8rem;
82
+
display: flex;
83
+
align-items: center;
84
+
}
85
+
86
+
.info-item {
87
+
margin-right: 10px;
88
+
}
89
+
90
+
hr {
91
+
border 1px solid #000;
92
+
}
+1
readme.txt
+1
readme.txt
···
1
+
nix build .#readit
+392
src/geddit.js
+392
src/geddit.js
···
1
+
class 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
+
}
14
+
15
+
async getSubmissions(sort = null, subreddit = null, options = {}) {
16
+
let params = {
17
+
limit: 25,
18
+
include_over_18: true,
19
+
}
20
+
21
+
sort = sort ? sort : "hot";
22
+
subreddit = subreddit ? "/r/" + subreddit : "";
23
+
24
+
return await fetch(this.host + subreddit + `/${sort}.json?` + new URLSearchParams(Object.assign(params, options)))
25
+
.then(res => res.json())
26
+
.then(json => json.data)
27
+
.then(data => ({
28
+
after: data.after,
29
+
posts: data.children
30
+
}))
31
+
.catch(err => null);
32
+
33
+
}
34
+
35
+
async getDomainHot(domain, options = this.parameters) {
36
+
return await fetch(this.host + "/domain/" + domain + "/hot.json?" + new URLSearchParams(options))
37
+
.then(res => res.json())
38
+
.then(json => json.data)
39
+
.then(data => ({
40
+
after: data.after,
41
+
posts: data.children
42
+
}))
43
+
.catch(err => null);
44
+
}
45
+
46
+
async getDomainBest(domain, options = this.parameters) {
47
+
return await fetch(this.host + "/domain/" + domain + "/best.json?" + new URLSearchParams(options))
48
+
.then(res => res.json())
49
+
.then(json => json.data)
50
+
.then(data => ({
51
+
after: data.after,
52
+
posts: data.children
53
+
}))
54
+
.catch(err => null);
55
+
}
56
+
57
+
async getDomainTop(domain, options = this.parameters) {
58
+
return await fetch(this.host + "/domain/" + domain + "/top.json?" + new URLSearchParams(options))
59
+
.then(res => res.json())
60
+
.then(json => json.data)
61
+
.then(data => ({
62
+
after: data.after,
63
+
posts: data.children
64
+
}))
65
+
.catch(err => null);
66
+
}
67
+
68
+
async getDomainNew(domain, options = this.parameters) {
69
+
return await fetch(this.host + "/domain/" + domain + "/new.json?" + new URLSearchParams(options))
70
+
.then(res => res.json())
71
+
.then(json => json.data)
72
+
.then(data => ({
73
+
after: data.after,
74
+
posts: data.children
75
+
}))
76
+
.catch(err => null);
77
+
}
78
+
79
+
async getDomainRising(domain, options = this.parameters) {
80
+
return await fetch(this.host + "/domain/" + domain + "/rising.json?" + new URLSearchParams(options))
81
+
.then(res => res.json())
82
+
.then(json => json.data)
83
+
.then(data => ({
84
+
after: data.after,
85
+
posts: data.children
86
+
}))
87
+
.catch(err => null);
88
+
}
89
+
90
+
async getDomainControversial(domain, options = this.parameters) {
91
+
return await fetch(this.host + "/domain/" + domain + "/controversial.json?" + new URLSearchParams(options))
92
+
.then(res => res.json())
93
+
.then(json => json.data)
94
+
.then(data => ({
95
+
after: data.after,
96
+
posts: data.children
97
+
}))
98
+
.catch(err => null);
99
+
}
100
+
101
+
async getSubreddit(subreddit) {
102
+
return await fetch(this.host + "/r/" + subreddit + "/about.json")
103
+
.then(res => res.json())
104
+
.then(json => json.data)
105
+
.catch(err => null);
106
+
}
107
+
108
+
async getSubredditRules(subreddit) {
109
+
return await fetch(this.host + "/r/" + subreddit + "/about/rules.json")
110
+
.then(res => res.json())
111
+
.then(json => json.data)
112
+
.catch(err => null);
113
+
}
114
+
115
+
async getSubredditModerators(subreddit) {
116
+
return await fetch(this.host + "/r/" + subreddit + "/about/moderators.json")
117
+
.then(res => res.json())
118
+
.then(json => json.data)
119
+
.then(data = ({
120
+
users: data.children
121
+
}))
122
+
.catch(err => null);
123
+
}
124
+
125
+
async getSubredditWikiPages(subreddit) {
126
+
return await fetch(this.host + "/r/" + subreddit + "/wiki/pages.json")
127
+
.then(res => res.json())
128
+
.then(json => json.data)
129
+
.catch(err => null);
130
+
}
131
+
132
+
async getSubredditWikiPage(subreddit, page) {
133
+
return await fetch(this.host + "/r/" + subreddit + "/wiki/" + page + ".json")
134
+
.then(res => res.json())
135
+
.then(json => json.data)
136
+
.catch(err => null);
137
+
}
138
+
139
+
async getSubredditWikiPageRevisions(subreddit, page) {
140
+
return await fetch(this.host + "/r/" + subreddit + "/wiki/revisions" + page + ".json")
141
+
.then(res => res.json())
142
+
.then(json => json.data.children)
143
+
.catch(err => null);
144
+
}
145
+
146
+
async getPopularSubreddits(options = this.parameters) {
147
+
return await fetch(this.host + "/subreddits/popular.json?" + new URLSearchParams(options))
148
+
.then(res => res.json())
149
+
.then(json => json.data)
150
+
.then(data => ({
151
+
after: data.after,
152
+
subreddits: data.children
153
+
}))
154
+
.catch(err => null);
155
+
}
156
+
157
+
async getNewSubreddits(options = this.parameters) {
158
+
return await fetch(this.host + "/subreddits/new.json?" + new URLSearchParams(options))
159
+
.then(res => res.json())
160
+
.then(json => json.data)
161
+
.then(data => ({
162
+
after: data.after,
163
+
subreddits: data.children
164
+
}))
165
+
.catch(err => null);
166
+
}
167
+
168
+
async getPremiumSubreddits(options = this.parameters) {
169
+
return await fetch(this.host + "/subreddits/premium.json?" + new URLSearchParams(options))
170
+
.then(res => res.json())
171
+
.then(json => json.data)
172
+
.then(data => ({
173
+
after: data.after,
174
+
subreddits: data.children
175
+
}))
176
+
.catch(err => null);
177
+
}
178
+
179
+
async getDefaultSubreddits(options = this.parameters) {
180
+
return await fetch(this.host + "/subreddits/default.json?" + new URLSearchParams(options))
181
+
.then(res => res.json())
182
+
.then(json => json.data)
183
+
.then(data => ({
184
+
after: data.after,
185
+
subreddits: data.children
186
+
}))
187
+
.catch(err => null);
188
+
}
189
+
190
+
async getPopularUsers(options = this.parameters) {
191
+
return await fetch(this.host + "/users/popular.json?" + new URLSearchParams(options))
192
+
.then(res => res.json())
193
+
.then(json => json.data)
194
+
.then(data => ({
195
+
after: data.after,
196
+
users: data.children
197
+
}))
198
+
.catch(err => null);
199
+
}
200
+
201
+
async getNewUsers(options = this.parameters) {
202
+
return await fetch(this.host + "/users/new.json?" + new URLSearchParams(options))
203
+
.then(res => res.json())
204
+
.then(json => json.data)
205
+
.then(data => ({
206
+
after: data.after,
207
+
users: data.children
208
+
}))
209
+
.catch(err => null);
210
+
}
211
+
212
+
async searchSubmissions(query, options = {}) {
213
+
options.q = query;
214
+
options.type = "link";
215
+
216
+
let params = {
217
+
limit: 25,
218
+
include_over_18: true
219
+
}
220
+
221
+
console.log(this.host + "/search.json?" + new URLSearchParams(Object.assign(params, options)));
222
+
223
+
return await fetch(this.host + "/search.json?" + new URLSearchParams(Object.assign(params, options)))
224
+
.then(res => res.json())
225
+
.then(json => json.data)
226
+
.then(data => ({
227
+
after: data.after,
228
+
items: data.children
229
+
}))
230
+
.catch(err => null);
231
+
}
232
+
233
+
async searchSubreddits(query, options = {}) {
234
+
options.q = query;
235
+
236
+
let params = {
237
+
limit: 25,
238
+
include_over_18: true
239
+
}
240
+
241
+
return await fetch(this.host + "/subreddits/search.json?" + new URLSearchParams(Object.assign(params, options)))
242
+
.then(res => res.json())
243
+
.then(json => json.data)
244
+
.then(data => ({
245
+
after: data.after,
246
+
items: data.children
247
+
}))
248
+
.catch(err => null);
249
+
}
250
+
251
+
async searchUsers(query, options = {}) {
252
+
options.q = query;
253
+
254
+
let params = {
255
+
limit: 25,
256
+
include_over_18: true
257
+
}
258
+
259
+
return await fetch(this.host + "/users/search.json?" + new URLSearchParams(Object.assign(params, options)))
260
+
.then(res => res.json())
261
+
.then(json => json.data)
262
+
.then(data => ({
263
+
after: data.after,
264
+
items: data.children
265
+
}))
266
+
.catch(err => null);
267
+
}
268
+
269
+
async searchAll(query, subreddit = null, options = {}) {
270
+
options.q = query;
271
+
subreddit = subreddit ? "/r/" + subreddit : "";
272
+
273
+
let params = {
274
+
limit: 25,
275
+
include_over_18: true,
276
+
type: "sr,link,user",
277
+
}
278
+
279
+
return await fetch(this.host + subreddit + "/search.json?" + new URLSearchParams(Object.assign(params, options)))
280
+
.then(res => res.json())
281
+
.then(json => Array.isArray(json) ? ({
282
+
after: json[1].data.after,
283
+
items: json[0].data.children.concat(json[1].data.children)
284
+
}) : ({
285
+
after: json.data.after,
286
+
items: json.data.children
287
+
}))
288
+
.catch(err => null);
289
+
}
290
+
291
+
async getSubmission(id) {
292
+
return await fetch(this.host + "/by_id/" + id + ".json")
293
+
.then(res => res.json())
294
+
.then(json => json.data.children[0].data)
295
+
.catch(err => null);
296
+
}
297
+
298
+
async getSubmissionComments(id, options = this.parameters) {
299
+
return await fetch(this.host + "/comments/" + id + ".json?" + new URLSearchParams(options))
300
+
.then(res => res.json())
301
+
.then(json => ({
302
+
submission: json[0].data.children[0],
303
+
comments: json[1].data.children
304
+
}))
305
+
.catch(err => null);
306
+
}
307
+
308
+
async getSubredditComments(subreddit, options = this.parameters) {
309
+
return await fetch(this.host + "/r/" + subreddit + "/comments.json?" + new URLSearchParams(options))
310
+
.then(res => res.json())
311
+
.then(json => json.data.children)
312
+
.catch(err => null);
313
+
}
314
+
315
+
async getUser(username) {
316
+
return await fetch(this.host + "/user/" + username + "/about.json")
317
+
.then(res => res.json())
318
+
.then(json => json.data)
319
+
.catch(err => null);
320
+
}
321
+
322
+
async getUserOverview(username, options = this.parameters) {
323
+
return await fetch(this.host + "/user/" + username + "/overview.json?" + new URLSearchParams(options))
324
+
.then(res => res.json())
325
+
.then(json => json.data)
326
+
.then(data => ({
327
+
after: data.after,
328
+
items: data.children
329
+
}))
330
+
.catch(err => null);
331
+
}
332
+
333
+
async getUserComments(username, options = this.parameters) {
334
+
return await fetch(this.host + "/user/" + username + "/comments.json?" + new URLSearchParams(options))
335
+
.then(res => res.json())
336
+
.then(json => json.data)
337
+
.then(data => ({
338
+
after: data.after,
339
+
items: data.children
340
+
}))
341
+
.catch(err => null);
342
+
}
343
+
344
+
async getUserSubmissions(username, options = this.parameters) {
345
+
return await fetch(this.host + "/user/" + username + "/submitted.json?" + new URLSearchParams(options))
346
+
.then(res => res.json())
347
+
.then(json => json.data)
348
+
.then(data => ({
349
+
after: data.after,
350
+
items: data.children
351
+
}))
352
+
.catch(err => null);
353
+
}
354
+
355
+
async getLiveThread(id) {
356
+
return await fetch(this.host + "/live/" + id + "/about.json")
357
+
.then(res => res.json())
358
+
.then(json => json.data)
359
+
.catch(err => null);
360
+
}
361
+
362
+
async getLiveThreadUpdates(id, options = this.parameters) {
363
+
return await fetch(this.host + "/live/" + id + ".json?" + new URLSearchParams(options))
364
+
.then(res => res.json())
365
+
.then(json => json.data.children)
366
+
.catch(err => null);
367
+
}
368
+
369
+
370
+
async getLiveThreadContributors(id, options = this.parameters) {
371
+
return await fetch(this.host + "/live/" + id + "/contributors.json?" + new URLSearchParams(options))
372
+
.then(res => res.json())
373
+
.then(json => json.data.children)
374
+
.catch(err => null);
375
+
}
376
+
377
+
async getLiveThreadDiscussions(id, options = this.parameters) {
378
+
return await fetch(this.host + "/live/" + id + "/discussions.json?" + new URLSearchParams(options))
379
+
.then(res => res.json())
380
+
.then(json => json.data.children)
381
+
.catch(err => null);
382
+
}
383
+
384
+
async getLiveThreadsNow(options = this.parameters) {
385
+
return await fetch(this.host + "/live/happening_now.json?" + new URLSearchParams(options))
386
+
.then(res => res.json())
387
+
.then(json => json.data.children)
388
+
.catch(err => null);
389
+
}
390
+
}
391
+
392
+
export { Geddit }
+17
src/index.js
+17
src/index.js
···
1
+
const express = require('express');
2
+
const path = require('path');
3
+
const routes = require('./routes/index');
4
+
const geddit = require('./geddit.js');
5
+
6
+
const app = express();
7
+
8
+
app.set('views', path.join(__dirname, 'views'));
9
+
app.set('view engine', 'pug');
10
+
11
+
app.use(express.static('public'));
12
+
app.use('/', routes);
13
+
14
+
const server = app.listen(3000, () => {
15
+
console.log(`started on ${server.address().port}`);
16
+
});
17
+
+35
src/routes/index.js
+35
src/routes/index.js
···
1
+
const express = require('express');
2
+
const router = express.Router();
3
+
const geddit = require('../geddit.js');
4
+
const G = new geddit.Geddit();
5
+
const fs = require('fs/promises');
6
+
7
+
8
+
// GET /
9
+
router.get('/', async (req, res) => {
10
+
res.redirect("/r/all")
11
+
});
12
+
13
+
// GET /r/:id
14
+
router.get('/r/:subreddit', async (req, res) => {
15
+
var subreddit = req.params.subreddit;
16
+
17
+
var postsReq = G.getSubmissions(`r/${subreddit}`);
18
+
var aboutReq = G.getSubreddit(`${subreddit}`);
19
+
20
+
var [posts, about] = await Promise.all([postsReq, aboutReq]);
21
+
res.render('index', { subreddit, posts, about });
22
+
});
23
+
24
+
// GET /comments/:id
25
+
router.get('/comments/:id', async (req, res) => {
26
+
var id = req.params.id;
27
+
28
+
response = await G.getSubmissionComments(id);
29
+
var post = response.submission.data;
30
+
var comments = response.comments;
31
+
32
+
res.render('comments', { post, comments });
33
+
});
34
+
35
+
module.exports = router;
+18
src/views/comment.pug
+18
src/views/comment.pug
···
1
+
include utils
2
+
mixin comment(com, isfirst)
3
+
- var data = com.data
4
+
- var kind = com.kind
5
+
if kind == "more"
6
+
div.more #{data.count} more comments
7
+
else
8
+
div(class=`comment ${isfirst?'first':''}`)
9
+
div.comment-body !{data.body}
10
+
div.info-container
11
+
div.info-item by u/#{data.author}
12
+
div.info-item ↑ #{fmtnum(data.ups)}
13
+
div.replies
14
+
if data.replies
15
+
if data.replies.data
16
+
if data.replies.data.children
17
+
each reply in data.replies.data.children
18
+
+comment(reply,false)
+25
src/views/comments.pug
+25
src/views/comments.pug
···
1
+
doctype html
2
+
html
3
+
head
4
+
meta(charset='UTF-8')
5
+
title reddit
6
+
link(rel='stylesheet', href='/styles.css')
7
+
body
8
+
main#content
9
+
div.header
10
+
a(href=`/r/${post.subreddit}`)
11
+
h4 ← r/#{post.subreddit}
12
+
h2 #{post.title}
13
+
if post.post_hint == 'image'
14
+
img(src=post.url).post-media
15
+
else if post.post_hint == 'hosted:video'
16
+
video(src=post.url).post-media
17
+
p.self-text !{post.selftext}
18
+
hr
19
+
20
+
div.comments-container
21
+
each child in comments
22
+
include comment
23
+
+comment(child, true)
24
+
25
+
script(src='https://unpkg.com/htmx.org@1.9.10')
+19
src/views/index.pug
+19
src/views/index.pug
···
1
+
doctype html
2
+
html
3
+
head
4
+
meta(charset='UTF-8')
5
+
title reddit
6
+
link(rel='stylesheet', href='/styles.css')
7
+
body
8
+
main#content
9
+
div.header
10
+
a(href=`/r/#{subreddit}`)
11
+
h1 r/#{subreddit}
12
+
if about
13
+
p #{about.public_description}
14
+
15
+
each child in posts.posts
16
+
include post
17
+
+post(child.data)
18
+
19
+
script(src='https://unpkg.com/htmx.org@1.9.10')
+21
src/views/post.pug
+21
src/views/post.pug
···
1
+
include utils
2
+
mixin post(p)
3
+
article.post
4
+
div.post-container
5
+
div.post-text
6
+
div.title-container !{p.title}
7
+
div.info-container
8
+
div.info-item by u/#{p.author}
9
+
div.info-item ↑ #{fmtnum(p.ups)}
10
+
div.info-item #{p.domain}
11
+
div.info-item
12
+
a(href=`/r/${p.subreddit}`) r/#{p.subreddit}
13
+
div.info-item
14
+
a(href=`/comments/${p.id}`) #{fmtnum (p.num_comments)} #{fmttxt(p.num_comments, 'comment')}
15
+
div.media-preview
16
+
if p.post_hint == "image" || p.post_hint == "link"
17
+
if p.thumbnail && p.thumbnail != "self" || p.thumbnail != "default"
18
+
a(href=p.url)
19
+
img(src=p.thumbnail width='100px')
20
+
else if p.post_hint == "hosted:video"
21
+
video(src=p.secure_media.reddit_video.scrubber_media_url width='100px')