diff options
-rw-r--r-- | .gitignore | 179 | ||||
-rw-r--r-- | build.js | 9 | ||||
-rw-r--r-- | flake.lock | 27 | ||||
-rw-r--r-- | flake.nix | 99 | ||||
-rw-r--r-- | package.json | 17 | ||||
-rw-r--r-- | public/styles.css | 92 | ||||
-rw-r--r-- | readme.txt | 1 | ||||
-rw-r--r-- | src/geddit.js | 392 | ||||
-rw-r--r-- | src/index.js | 17 | ||||
-rw-r--r-- | src/routes/index.js | 35 | ||||
-rw-r--r-- | src/views/comment.pug | 18 | ||||
-rw-r--r-- | src/views/comments.pug | 25 | ||||
-rw-r--r-- | src/views/index.pug | 19 | ||||
-rw-r--r-- | src/views/post.pug | 21 | ||||
-rw-r--r-- | src/views/utils.pug | 3 |
15 files changed, 954 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da80e40 --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1,179 @@ | |||
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 | ||
diff --git a/build.js b/build.js new file mode 100644 index 0000000..a16c54b --- /dev/null +++ b/build.js | |||
@@ -0,0 +1,9 @@ | |||
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 | } | ||
diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..fa59981 --- /dev/null +++ b/flake.lock | |||
@@ -0,0 +1,27 @@ | |||
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 | } | ||
diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..917e2e0 --- /dev/null +++ b/flake.nix | |||
@@ -0,0 +1,99 @@ | |||
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 | |||
diff --git a/package.json b/package.json new file mode 100644 index 0000000..3e68c4c --- /dev/null +++ b/package.json | |||
@@ -0,0 +1,17 @@ | |||
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 | } \ No newline at end of file | ||
diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..1a541de --- /dev/null +++ b/public/styles.css | |||
@@ -0,0 +1,92 @@ | |||
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 | } | ||
diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..f041728 --- /dev/null +++ b/readme.txt | |||
@@ -0,0 +1 @@ | |||
nix build .#readit | |||
diff --git a/src/geddit.js b/src/geddit.js new file mode 100644 index 0000000..825c3a9 --- /dev/null +++ b/src/geddit.js | |||
@@ -0,0 +1,392 @@ | |||
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 } | ||
diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..bfe31f4 --- /dev/null +++ b/src/index.js | |||
@@ -0,0 +1,17 @@ | |||
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 | |||
diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..6cf5403 --- /dev/null +++ b/src/routes/index.js | |||
@@ -0,0 +1,35 @@ | |||
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; | ||
diff --git a/src/views/comment.pug b/src/views/comment.pug new file mode 100644 index 0000000..24e1a9b --- /dev/null +++ b/src/views/comment.pug | |||
@@ -0,0 +1,18 @@ | |||
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) | ||
diff --git a/src/views/comments.pug b/src/views/comments.pug new file mode 100644 index 0000000..f7964a3 --- /dev/null +++ b/src/views/comments.pug | |||
@@ -0,0 +1,25 @@ | |||
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/[email protected]') | ||
diff --git a/src/views/index.pug b/src/views/index.pug new file mode 100644 index 0000000..d017570 --- /dev/null +++ b/src/views/index.pug | |||
@@ -0,0 +1,19 @@ | |||
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/[email protected]') | ||
diff --git a/src/views/post.pug b/src/views/post.pug new file mode 100644 index 0000000..77ef3f5 --- /dev/null +++ b/src/views/post.pug | |||
@@ -0,0 +1,21 @@ | |||
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') | ||
diff --git a/src/views/utils.pug b/src/views/utils.pug new file mode 100644 index 0000000..f3f61bb --- /dev/null +++ b/src/views/utils.pug | |||
@@ -0,0 +1,3 @@ | |||
1 | - var fmtnum = (n)=>n>=1000?(n/1000).toFixed(1)+'k':n; | ||
2 | - var fmttxt = (n,t)=>`${t}${n==1?'':'s'}` | ||
3 | |||