diff --git a/routes/index.js b/routes/index.js
index dda6488..c9ab462 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -1,11 +1,13 @@
const overridingRoutes = require('./overides');
const preferenceRoutes = require('./preferences');
const staticRoutes = require('./static');
+const subredditRoutes = require('./subreddit');
const allRoutes = require('express').Router();
allRoutes.use(overridingRoutes);
allRoutes.use(staticRoutes);
allRoutes.use(preferenceRoutes);
+allRoutes.use(subredditRoutes);
module.exports = allRoutes;
diff --git a/routes/preferences.js b/routes/preferences.js
index bcb18fd..a9a3abe 100644
--- a/routes/preferences.js
+++ b/routes/preferences.js
@@ -1,4 +1,4 @@
-const config = '../config';
+const config = require('../config');
const preferenceRoutes = require('express').Router();
function resetPreferences(res) {
diff --git a/routes/subreddit.js b/routes/subreddit.js
new file mode 100644
index 0000000..27016cf
--- /dev/null
+++ b/routes/subreddit.js
@@ -0,0 +1,790 @@
+const config = require('../config');
+const { redis, fetch, RedditAPI } = require('../app');
+const subredditRoutes = require('express').Router();
+
+const processSubreddit = require('../inc/processJsonSubreddit.js')();
+const processAbout = require('../inc/processSubredditAbout.js')();
+const processSearches = require('../inc/processSearchResults.js')();
+const processPost = require('../inc/processJsonPost.js')();
+
+subredditRoutes.get('/r/:subreddit/search', (req, res, next) => {
+ let subreddit = req.params.subreddit;
+ let q = req.query.q;
+
+ if (typeof q === 'undefined') {
+ return res.render('search', {
+ json: { posts: [] },
+ no_query: true,
+ q: '',
+ restrict_sr: undefined,
+ nsfw: undefined,
+ subreddit: subreddit,
+ sortby: undefined,
+ past: undefined,
+ user_preferences: req.cookies,
+ });
+ }
+
+ let restrict_sr = req.query.restrict_sr;
+ let nsfw = req.query.nsfw;
+ let sortby = req.query.sort;
+ let past = req.query.t;
+ let after = req.query.after;
+ let before = req.query.before;
+ if (!after) {
+ after = '';
+ }
+ if (!before) {
+ before = '';
+ }
+ let d = `&after=${after}`;
+ if (before) {
+ d = `&before=${before}`;
+ }
+
+ if (restrict_sr !== 'on') {
+ restrict_sr = 'off';
+ }
+
+ if (nsfw !== 'on') {
+ nsfw = 'off';
+ }
+
+ let key = `search:${subreddit}:${q}:${restrict_sr}:${sortby}:${past}:${after}:${before}:${nsfw}`;
+ redis.get(key, (error, json) => {
+ if (error) {
+ console.error('Error getting the search key from redis.', error);
+ return res.render('index', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ }
+ if (json) {
+ console.log('Got search key from redis.');
+ (async () => {
+ let processed_json = await processSearchResults(
+ json,
+ false,
+ after,
+ before,
+ req.cookies
+ );
+ return res.render('search', {
+ json: processed_json,
+ no_query: false,
+ q: q,
+ restrict_sr: restrict_sr,
+ nsfw: nsfw,
+ subreddit: subreddit,
+ sortby: sortby,
+ past: past,
+ user_preferences: req.cookies,
+ });
+ })();
+ } else {
+ let url = '';
+ if (config.use_reddit_oauth)
+ url = `https://oauth.reddit.com/r/${subreddit}/search?api_type=json&q=${q}&restrict_sr=${restrict_sr}&include_over_18=${nsfw}&sort=${sortby}&t=${past}${d}`;
+ else
+ url = `https://reddit.com/r/${subreddit}/search.json?api_type=json&q=${q}&restrict_sr=${restrict_sr}&include_over_18=${nsfw}&sort=${sortby}&t=${past}${d}`;
+ fetch(encodeURI(url), redditApiGETHeaders())
+ .then((result) => {
+ if (result.status === 200) {
+ result.json().then((json) => {
+ (async () => {
+ /**
+ * Fetch suggested subreddits when the restrict_sr option is
+ * turned off ("limit my search to") and we are on the first search
+ * page (just like in Reddit).
+ */
+ json.suggested_subreddits = {};
+ if (restrict_sr === 'off' && before == '' && after == '') {
+ let url = `https://reddit.com/subreddits/search.json?q=${q}&include_over_18=${nsfw}&limit=3`;
+ const response = await fetch(encodeURI(url));
+ const data = await response.json();
+ json.suggested_subreddits = data;
+ }
+
+ redis.setex(
+ key,
+ config.setexs.searches,
+ JSON.stringify(json),
+ (error) => {
+ if (error) {
+ console.error(
+ 'Error setting the searches key to redis.',
+ error
+ );
+ return res.render('index', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ } else {
+ console.log('Fetched search results from Reddit.');
+ (async () => {
+ let processed_json = await processSearchResults(
+ json,
+ true,
+ after,
+ before,
+ req.cookies
+ );
+ return res.render('search', {
+ no_query: false,
+ json: processed_json,
+ q: q,
+ restrict_sr: restrict_sr,
+ nsfw: nsfw,
+ subreddit: subreddit,
+ sortby: sortby,
+ past: past,
+ user_preferences: req.cookies,
+ });
+ })();
+ }
+ }
+ );
+ })();
+ });
+ } else {
+ console.error(
+ `Something went wrong while fetching data from Reddit. ${result.status} – ${result.statusText}`
+ );
+ console.error(config.reddit_api_error_text);
+ return res.render('index', {
+ json: null,
+ http_status_code: result.status,
+ user_preferences: req.cookies,
+ });
+ }
+ })
+ .catch((error) => {
+ console.error('Error fetching the frontpage JSON file.', error);
+ });
+ }
+ });
+});
+
+subredditRoutes.get(
+ '/r/:subreddit/wiki/:page?/:sub_page?',
+ (req, res, next) => {
+ let subreddit = req.params.subreddit;
+ let page = req.params.page;
+ let sub_page = req.params.sub_page || '';
+
+ if (!page) page = 'index';
+
+ if (sub_page != '') sub_page = `/${sub_page}`;
+
+ function formatWikipagelisting(json, subreddit) {
+ let html = '
';
+ if (json.kind === 'wikipagelisting' && json.data) {
+ for (var i = 0; i < json.data.length; i++) {
+ let d = json.data[i];
+ html += `- ${d}
`;
+ }
+ }
+ html += '
';
+ return html;
+ }
+
+ let key = `${subreddit.toLowerCase()}:wiki:page:${page}:sub_page:${sub_page}`;
+ redis.get(key, (error, json) => {
+ if (error) {
+ console.error(
+ `Error getting the ${subreddit} wiki key from redis.`,
+ error
+ );
+ return res.render('index', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ }
+ if (json) {
+ console.log(`Got /r/${subreddit} wiki key from redis.`);
+ json = JSON.parse(json);
+ return res.render('subreddit_wiki', {
+ content_html:
+ page !== 'pages'
+ ? unescape(json.data.content_html)
+ : formatWikipagelisting(json, subreddit),
+ subreddit: subreddit,
+ user_preferences: req.cookies,
+ });
+ } else {
+ let url = '';
+ if (config.use_reddit_oauth)
+ url = `https://oauth.reddit.com/r/${subreddit}/wiki/${page}${sub_page}?api_type=json`;
+ else
+ url = `https://reddit.com/r/${subreddit}/wiki/${page}${sub_page}.json?api_type=json`;
+ fetch(encodeURI(url), redditApiGETHeaders())
+ .then((result) => {
+ if (result.status === 200) {
+ result.json().then((json) => {
+ redis.setex(
+ key,
+ config.setexs.wikis,
+ JSON.stringify(json),
+ (error) => {
+ if (error) {
+ console.error(
+ `Error setting the ${subreddit} wiki key to redis.`,
+ error
+ );
+ return res.render('subreddit', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ } else {
+ console.log(
+ `Fetched the JSON from reddit.com/r/${subreddit}/wiki.`
+ );
+ return res.render('subreddit_wiki', {
+ content_html:
+ page !== 'pages'
+ ? unescape(json.data.content_html)
+ : formatWikipagelisting(json, subreddit),
+ subreddit: subreddit,
+ user_preferences: req.cookies,
+ });
+ }
+ }
+ );
+ });
+ } else {
+ if (result.status === 404) {
+ console.log('404 – Subreddit wiki not found');
+ } else {
+ console.error(
+ `Something went wrong while fetching data from Reddit. ${result.status} – ${result.statusText}`
+ );
+ console.error(config.reddit_api_error_text);
+ }
+ return res.render('index', {
+ json: null,
+ http_status_code: result.status,
+ user_preferences: req.cookies,
+ });
+ }
+ })
+ .catch((error) => {
+ console.error(
+ `Error fetching the JSON file from reddit.com/r/${subreddit}/wiki.`,
+ error
+ );
+ });
+ }
+ });
+ }
+);
+
+subredditRoutes.get('/r/:subreddit/w/:page?/:sub_page?', (req, res, next) => {
+ /* "w" is a shorturl for wikis for example https://old.reddit.com/r/privacytoolsIO/w/index */
+ let subreddit = req.params.subreddit;
+ let page = req.params.page;
+ let sub_page = req.params.sub_page || '';
+
+ if (!page) page = 'index';
+
+ if (sub_page != '') sub_page = `/${sub_page}`;
+
+ return res.redirect(`/r/${subreddit}/wiki/${page}${sub_page}`);
+});
+
+subredditRoutes.get('/r/random', (req, res, next) => {
+ let url = '';
+ if (config.use_reddit_oauth)
+ url = `https://oauth.reddit.com/r/random?api_type=json&count=25&g=GLOBAL`;
+ else url = `https://reddit.com/r/random.json?api_type=json&count=25&g=GLOBAL`;
+
+ fetch(encodeURI(url), redditApiGETHeaders())
+ .then((result) => {
+ if (result.status === 200) {
+ result.json().then((json) => {
+ let subreddit = json.data.children[0].data.subreddit;
+ if (subreddit) {
+ let key = `${subreddit.toLowerCase()}:undefined:undefined:sort:hot:past:undefined`;
+ redis.setex(
+ key,
+ config.setexs.subreddit,
+ JSON.stringify(json),
+ (error) => {
+ if (error) {
+ console.error(
+ `Error setting the random subreddit key to redis.`,
+ error
+ );
+ return res.render('subreddit', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ } else {
+ console.log(
+ `Fetched the JSON from reddit.com/r/${subreddit}.`
+ );
+ return res.redirect(`/r/${subreddit}`);
+ }
+ }
+ );
+ } else {
+ console.error(`Fetching random subreddit failed.`, json);
+ return res.render('index', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ }
+ });
+ } else {
+ if (result.status === 404) {
+ console.log('404 – Subreddit not found');
+ } else {
+ console.error(
+ `Something went wrong while fetching data from Reddit. ${result.status} – ${result.statusText}`
+ );
+ console.error(config.reddit_api_error_text);
+ }
+ return res.render('index', {
+ json: null,
+ http_status_code: result.status,
+ user_preferences: req.cookies,
+ });
+ }
+ })
+ .catch((error) => {
+ console.error(
+ `Error fetching the JSON file from reddit.com/r/random.`,
+ error
+ );
+ });
+});
+
+subredditRoutes.get('/r/:subreddit/:sort?', (req, res, next) => {
+ let subreddit = req.params.subreddit;
+ let sortby = req.params.sort;
+ let past = req.query.t;
+ let before = req.query.before;
+ let after = req.query.after;
+ let api_req = req.query.api;
+ let api_type = req.query.type;
+ let api_target = req.query.target;
+
+ if (req.query.hasOwnProperty('api')) api_req = true;
+ else api_req = false;
+
+ let raw_json = api_req && req.query.raw_json == '1' ? 1 : 0;
+
+ let d = `&after=${after}`;
+ if (before) {
+ d = `&before=${before}`;
+ }
+
+ if (!sortby) {
+ sortby = 'hot';
+ }
+
+ if (
+ !['new', 'rising', 'controversial', 'top', 'gilded', 'hot'].includes(sortby)
+ ) {
+ console.log(`Got invalid sort.`, req.originalUrl);
+ return res.redirect(`/r/${subreddit}`);
+ }
+
+ if (past) {
+ if (sortby === 'controversial' || sortby === 'top') {
+ if (!['hour', 'day', 'week', 'month', 'year', 'all'].includes(past)) {
+ console.error(`Got invalid past.`, req.originalUrl);
+ return res.redirect(`/r/${subreddit}/${sortby}`);
+ }
+ } else {
+ past = undefined;
+ }
+ } else {
+ if (sortby === 'controversial' || sortby === 'top') {
+ past = 'day';
+ }
+ }
+
+ let key = `${subreddit.toLowerCase()}:${after}:${before}:sort:${sortby}:past:${past}:raw_json:${raw_json}`;
+ redis.get(key, (error, json) => {
+ if (error) {
+ console.error(`Error getting the ${subreddit} key from redis.`, error);
+ return res.render('index', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ }
+ if (json) {
+ console.log(`Got /r/${subreddit} key from redis.`);
+ (async () => {
+ if (api_req) {
+ return handleTedditApiSubreddit(
+ json,
+ req,
+ res,
+ 'redis',
+ api_type,
+ api_target,
+ subreddit
+ );
+ } else {
+ let processed_json = await processJsonSubreddit(
+ json,
+ 'redis',
+ null,
+ req.cookies
+ );
+ let subreddit_about = await processSubredditAbout(
+ subreddit,
+ redis,
+ fetch,
+ RedditAPI
+ );
+ if (!processed_json.error) {
+ return res.render('subreddit', {
+ json: processed_json,
+ subreddit: subreddit,
+ subreddit_about: subreddit_about,
+ subreddit_front: !before && !after ? true : false,
+ sortby: sortby,
+ past: past,
+ user_preferences: req.cookies,
+ instance_nsfw_enabled: config.nsfw_enabled,
+ redis_key: key,
+ after: req.query.after,
+ before: req.query.before,
+ });
+ } else {
+ return res.render('subreddit', {
+ json: null,
+ error: true,
+ data: processed_json,
+ user_preferences: req.cookies,
+ });
+ }
+ }
+ })();
+ } else {
+ let url = '';
+ if (config.use_reddit_oauth)
+ url = `https://oauth.reddit.com/r/${subreddit}/${sortby}?api_type=json&count=25&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}`;
+ else
+ url = `https://reddit.com/r/${subreddit}/${sortby}.json?api_type=json&count=25&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}`;
+ fetch(encodeURI(url), redditApiGETHeaders())
+ .then((result) => {
+ if (result.status === 200) {
+ result.json().then((json) => {
+ redis.setex(
+ key,
+ config.setexs.subreddit,
+ JSON.stringify(json),
+ (error) => {
+ if (error) {
+ console.error(
+ `Error setting the ${subreddit} key to redis.`,
+ error
+ );
+ return res.render('subreddit', {
+ json: null,
+ user_preferences: req.cookies,
+ });
+ } else {
+ console.log(
+ `Fetched the JSON from reddit.com/r/${subreddit}.`
+ );
+ (async () => {
+ if (api_req) {
+ return handleTedditApiSubreddit(
+ json,
+ req,
+ res,
+ 'from_online',
+ api_type,
+ api_target,
+ subreddit
+ );
+ } else {
+ let processed_json = await processJsonSubreddit(
+ json,
+ 'from_online',
+ null,
+ req.cookies
+ );
+ let subreddit_about = await processSubredditAbout(
+ subreddit,
+ redis,
+ fetch,
+ RedditAPI
+ );
+ return res.render('subreddit', {
+ json: processed_json,
+ subreddit: subreddit,
+ subreddit_about: subreddit_about,
+ subreddit_front: !before && !after ? true : false,
+ sortby: sortby,
+ past: past,
+ user_preferences: req.cookies,
+ instance_nsfw_enabled: config.nsfw_enabled,
+ redis_key: key,
+ after: req.query.after,
+ before: req.query.before,
+ });
+ }
+ })();
+ }
+ }
+ );
+ });
+ } else {
+ if (result.status === 404) {
+ console.log('404 – Subreddit not found');
+ } else {
+ console.error(
+ `Something went wrong while fetching data from Reddit. ${result.status} – ${result.statusText}`
+ );
+ console.error(config.reddit_api_error_text);
+ }
+ return res.render('index', {
+ json: null,
+ http_status_code: result.status,
+ user_preferences: req.cookies,
+ });
+ }
+ })
+ .catch((error) => {
+ console.error(
+ `Error fetching the JSON file from reddit.com/r/${subreddit}.`,
+ error
+ );
+ });
+ }
+ });
+});
+
+subredditRoutes.get(
+ '/r/:subreddit/comments/:id/:snippet?/:comment_id?',
+ (req, res, next) => {
+ let subreddit = req.params.subreddit;
+ let id = req.params.id;
+ let snippet = encodeURIComponent(req.params.snippet);
+ let sortby = req.query.sort;
+ let comment_id = '';
+ let viewing_comment = false;
+ let comment_ids = req.query.comment_ids;
+ let context = parseInt(req.query.context);
+
+ if (req.params.comment_id) {
+ comment_id = `${req.params.comment_id}/`;
+ viewing_comment = true;
+ }
+
+ if (!sortby) {
+ sortby = config.post_comments_sort;
+ }
+
+ if (
+ ![
+ 'confidence',
+ 'top',
+ 'new',
+ 'controversial',
+ 'old',
+ 'qa',
+ 'random',
+ ].includes(sortby)
+ ) {
+ console.log(`Got invalid sort.`, req.originalUrl);
+ return res.redirect('/');
+ }
+
+ let comments_url = `/r/${subreddit}/comments/${id}/${snippet}/${comment_id}`;
+ let post_url = `/r/${subreddit}/comments/${id}/${snippet}/`;
+ let comments_key = `${comments_url}:sort:${sortby}`;
+
+ redis.get(comments_key, (error, json) => {
+ if (error) {
+ console.error(
+ `Error getting the ${comments_url} key from redis.`,
+ error
+ );
+ return res.render('index', {
+ post: null,
+ user_preferences: req.cookies,
+ });
+ }
+ if (json) {
+ console.log(`Got ${comments_url} key from redis.`);
+ (async () => {
+ let parsed = false;
+ let more_comments = null;
+ if (comment_ids) {
+ let key = `${post_url}:morechildren:comment_ids:${comment_ids}`;
+ more_comments = await moreComments(
+ fetch,
+ redis,
+ post_url,
+ comment_ids,
+ id
+ );
+
+ if (more_comments === false) {
+ return res.redirect(post_url);
+ } else {
+ json = JSON.parse(json);
+ json[1].data.children = more_comments;
+ parsed = true;
+ }
+ }
+
+ let processed_json = await processJsonPost(json, parsed, req.cookies);
+ let finalized_json = await finalizeJsonPost(
+ processed_json,
+ id,
+ post_url,
+ more_comments,
+ viewing_comment,
+ req.cookies
+ );
+ return res.render('post', {
+ post: finalized_json.post_data,
+ comments: finalized_json.comments,
+ viewing_comment: viewing_comment,
+ post_url: post_url,
+ subreddit: subreddit,
+ sortby: sortby,
+ user_preferences: req.cookies,
+ instance_nsfw_enabled: config.nsfw_enabled,
+ instance_videos_muted: config.videos_muted,
+ post_media_max_heights: config.post_media_max_heights,
+ redis_key: comments_key,
+ });
+ })();
+ } else {
+ let url = '';
+ if (config.use_reddit_oauth)
+ url = `https://oauth.reddit.com${comments_url}?api_type=json&sort=${sortby}&context=${context}`;
+ else
+ url = `https://reddit.com${comments_url}.json?api_type=json&sort=${sortby}&context=${context}`;
+
+ fetch(encodeURI(url), redditApiGETHeaders())
+ .then((result) => {
+ if (result.status === 200) {
+ result.json().then((json) => {
+ redis.setex(
+ comments_key,
+ config.setexs.posts,
+ JSON.stringify(json),
+ (error) => {
+ if (error) {
+ console.error(
+ `Error setting the ${comments_url} key to redis.`,
+ error
+ );
+ return res.render('post', {
+ post: null,
+ user_preferences: req.cookies,
+ });
+ } else {
+ console.log(
+ `Fetched the JSON from reddit.com${comments_url}.`
+ );
+ (async () => {
+ let more_comments = null;
+ if (comment_ids) {
+ let key = `${post_url}:morechildren:comment_ids:${comment_ids}`;
+ more_comments = await moreComments(
+ fetch,
+ redis,
+ post_url,
+ comment_ids,
+ id
+ );
+
+ if (more_comments === false) {
+ return res.redirect(post_url);
+ } else {
+ json[1].data.children = more_comments;
+ }
+ }
+
+ let processed_json = await processJsonPost(
+ json,
+ true,
+ req.cookies
+ );
+ let finalized_json = await finalizeJsonPost(
+ processed_json,
+ id,
+ post_url,
+ more_comments,
+ viewing_comment
+ );
+ return res.render('post', {
+ post: finalized_json.post_data,
+ comments: finalized_json.comments,
+ viewing_comment: viewing_comment,
+ post_url: post_url,
+ subreddit: subreddit,
+ sortby: sortby,
+ user_preferences: req.cookies,
+ instance_nsfw_enabled: config.nsfw_enabled,
+ instance_videos_muted: config.videos_muted,
+ post_media_max_heights: config.post_media_max_heights,
+ redis_key: comments_key,
+ });
+ })();
+ }
+ }
+ );
+ });
+ } else {
+ if (result.status === 404) {
+ console.log('404 – Post not found');
+ } else {
+ console.error(
+ `Something went wrong while fetching data from Reddit. ${result.status} – ${result.statusText}`
+ );
+ console.error(config.reddit_api_error_text);
+ }
+ return res.render('index', {
+ json: null,
+ http_status_code: result.status,
+ http_statustext: result.statusText,
+ user_preferences: req.cookies,
+ });
+ }
+ })
+ .catch((error) => {
+ console.error(
+ `Error fetching the JSON file from reddit.com${comments_url}.`,
+ error
+ );
+ });
+ }
+ });
+ }
+);
+
+subredditRoutes.post(
+ '/r/:subreddit/comments/:id/:snippet',
+ (req, res, next) => {
+ /**
+ * This is the "morechildren" route. This route is called when the
+ * "load more comments" button at the bottom of some post is clicked.
+ */
+ if (!config.use_reddit_oauth)
+ return res.send(
+ `This instance is using Reddit's public API (non-OAuth), and therefore this endpoint is not supported. In other words, this feature is only available if the instance is using Reddit OAuth API.`
+ );
+
+ let subreddit = req.params.subreddit;
+ let id = req.params.id;
+ let snippet = encodeURIComponent(req.params.snippet);
+ let post_url = `/r/${subreddit}/comments/${id}/${snippet}/`;
+ let page = req.query.page;
+ let comment_ids = req.body.comment_ids;
+
+ return res.redirect(`${post_url}?comment_ids=${comment_ids}&page=1`);
+ }
+);
+
+module.exports = subredditRoutes;