diff --git a/app.js b/app.js index 504aacd..7f21d05 100644 --- a/app.js +++ b/app.js @@ -1,16 +1,18 @@ -const config = require('./config') +const config = require('./config'); -global.client_id_b64 = Buffer.from(`${config.reddit_app_id}:`).toString('base64') -global.reddit_access_token = null -global.reddit_refresh_token = null -global.ratelimit_counts = {} -global.ratelimit_timestamps = {} +global.client_id_b64 = Buffer.from(`${config.reddit_app_id}:`).toString( + 'base64' +); +global.reddit_access_token = null; +global.reddit_refresh_token = null; +global.ratelimit_counts = {}; +global.ratelimit_timestamps = {}; -const pug = require('pug') -const compression = require('compression') -const express = require('express') -const cookieParser = require('cookie-parser') -const r = require('redis') +const pug = require('pug'); +const compression = require('compression'); +const express = require('express'); +const cookieParser = require('cookie-parser'); +const r = require('redis'); const redis = (() => { if (!config.redis_enabled) { @@ -18,145 +20,162 @@ const redis = (() => { return { get: (_, callback) => callback(null, null), setex: (_, _1, _2, callback) => callback(null), - on: () => {} - } + on: () => {}, + }; } const redisOptions = { host: '127.0.0.1', - port: 6379 - } - + port: 6379, + }; + if (config.redis_db) { - redisOptions.db = config.redis_db + redisOptions.db = config.redis_db; } if (config.redis_host) { - redisOptions.host = config.redis_host + redisOptions.host = config.redis_host; } - + if (config.redis_port && config.redis_port > 0) { - redisOptions.port = config.redis_port - } + redisOptions.port = config.redis_port; + } if (config.redis_password) { - redisOptions.password = config.redis_password + redisOptions.password = config.redis_password; } - return r.createClient(redisOptions) -})() + return r.createClient(redisOptions); +})(); -const nodeFetch = require('node-fetch') +const nodeFetch = require('node-fetch'); const fetch = config.http_proxy ? (() => { - const agent = require('https-proxy-agent')(config.http_proxy) + const agent = require('https-proxy-agent')(config.http_proxy); return (url, options) => { const instanceOptions = { agent, - ...options + ...options, }; return nodeFetch(url, instanceOptions); - } + }; })() - : nodeFetch + : nodeFetch; -const helmet = require('helmet') -const bodyParser = require('body-parser') -const fs = require('fs') -const app = express() -const request = require('postman-request') -const commons = require('./inc/commons.js')(request, fs) +const helmet = require('helmet'); +const bodyParser = require('body-parser'); +const fs = require('fs'); +const app = express(); +const request = require('postman-request'); +const commons = require('./inc/commons.js')(request, fs); const dlAndSave = require('./inc/downloadAndSave.js')(commons); ['pics/thumbs', 'pics/flairs', 'pics/icons', 'vids'] - .map(d => `./static/${d}`) - .filter(d => !fs.existsSync(d)) - .forEach(d => fs.mkdirSync(d, { recursive: true })) + .map((d) => `./static/${d}`) + .filter((d) => !fs.existsSync(d)) + .forEach((d) => fs.mkdirSync(d, { recursive: true })); -if(!config.https_enabled && config.redirect_http_to_https) { - console.error(`Cannot redirect HTTP=>HTTPS while "https_enabled" is false.`) +if (!config.https_enabled && config.redirect_http_to_https) { + console.error(`Cannot redirect HTTP=>HTTPS while "https_enabled" is false.`); } -let https = null -if(config.https_enabled) { - const privateKey = fs.readFileSync(`${config.cert_dir}/privkey.pem`, 'utf8') - const certificate = fs.readFileSync(`${config.cert_dir}/cert.pem`, 'utf8') - const ca = fs.readFileSync(`${config.cert_dir}/chain.pem`, 'utf8') +let https = null; +if (config.https_enabled) { + const privateKey = fs.readFileSync(`${config.cert_dir}/privkey.pem`, 'utf8'); + const certificate = fs.readFileSync(`${config.cert_dir}/cert.pem`, 'utf8'); + const ca = fs.readFileSync(`${config.cert_dir}/chain.pem`, 'utf8'); const credentials = { - key: privateKey, - cert: certificate, - ca: ca - } - https = require('https').Server(credentials, app) - global.protocol = 'https://' + key: privateKey, + cert: certificate, + ca: ca, + }; + https = require('https').Server(credentials, app); + global.protocol = 'https://'; } else { - global.protocol = 'http://' + global.protocol = 'http://'; } -const http = require('http').Server(app) +const http = require('http').Server(app); -if(config.redirect_www) { +if (config.redirect_www) { app.use((req, res, next) => { - if(req.headers.host) { - if(req.headers.host.slice(0, 4) === 'www.') { - let newhost = req.headers.host.slice(4) - return res.redirect(301, `${req.protocol}://${newhost}${req.originalUrl}`) + if (req.headers.host) { + if (req.headers.host.slice(0, 4) === 'www.') { + let newhost = req.headers.host.slice(4); + return res.redirect( + 301, + `${req.protocol}://${newhost}${req.originalUrl}` + ); } } - next() - }) + next(); + }); } -if(config.use_helmet && config.https_enabled) { - app.use(helmet()) - if(config.use_helmet_hsts) { - app.use(helmet.hsts({ maxAge: 31536000, preload: true })) +if (config.use_helmet && config.https_enabled) { + app.use(helmet()); + if (config.use_helmet_hsts) { + app.use(helmet.hsts({ maxAge: 31536000, preload: true })); } } -if(config.use_compression) { - app.use(compression()) +if (config.use_compression) { + app.use(compression()); } -app.use(cookieParser()) +app.use(cookieParser()); -if(config.use_view_cache) { - app.set('view cache', true) +if (config.use_view_cache) { + app.set('view cache', true); } -if(config.trust_proxy) { - app.set('trust proxy', config.trust_proxy_address) +if (config.trust_proxy) { + app.set('trust proxy', config.trust_proxy_address); } -app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })) -app.use(bodyParser.json({ limit: '10mb' })) -app.use(express.static(`${__dirname}/static`)) +app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); +app.use(bodyParser.json({ limit: '10mb' })); +app.use(express.static(`${__dirname}/static`)); -app.set('views', './views') -app.set('view engine', 'pug') +app.set('views', './views'); +app.set('view engine', 'pug'); -if(config.redirect_http_to_https) { +const redditAPI = require('./inc/initRedditApi.js')(fetch); + +/* +This is temporary. It's needed for the routes to work. +It can be removed once these functions are made more modular. +*/ +module.exports = { redis, fetch, RedditAPI: redditAPI }; + +const allRoutes = require('./routes/index'); + +app.use('/', allRoutes); + +// The old routes +//require('./routes')(app, redis, fetch, redditAPI); + +if (config.redirect_http_to_https) { app.use((req, res, next) => { - if(req.secure) - next() - else - res.redirect(`https://${req.headers.host}${req.url}`) - }) + if (req.secure) next(); + else res.redirect(`https://${req.headers.host}${req.url}`); + }); } -const redditAPI = require('./inc/initRedditApi.js')(fetch) -require('./routes')(app, redis, fetch, redditAPI) - redis.on('error', (error) => { - if(error) { - console.error(`Redis error: ${error}`) + if (error) { + console.error(`Redis error: ${error}`); } -}) +}); -const cacheControl = require('./cacheControl.js') -cacheControl.removeCacheFiles() +const cacheControl = require('./cacheControl.js'); +cacheControl.removeCacheFiles(); -if(config.https_enabled) { - https.listen(config.ssl_port, config.listen_address, () => console.log(`Teddit running on https://${config.domain}:${config.ssl_port}`)) +if (config.https_enabled) { + https.listen(config.ssl_port, config.listen_address, () => + console.log(`Teddit running on https://${config.domain}:${config.ssl_port}`) + ); } -http.listen(config.nonssl_port, config.listen_address, () => console.log(`Teddit running on http://${config.domain}:${config.nonssl_port}`)) +http.listen(config.nonssl_port, config.listen_address, () => + console.log(`Teddit running on http://${config.domain}:${config.nonssl_port}`) +); diff --git a/routes.js b/routes.js deleted file mode 100644 index 0bd943c..0000000 --- a/routes.js +++ /dev/null @@ -1,1921 +0,0 @@ -/** -* Lots of routes.. would be good idea to do some separation I guess. -*/ -module.exports = (app, redis, fetch, RedditAPI) => { - const config = require('./config'); - let processSubreddit = require('./inc/processJsonSubreddit.js')(); - let processPost = require('./inc/processJsonPost.js')(); - let processUser = require('./inc/processJsonUser.js')(); - let processSearches = require('./inc/processSearchResults.js')(); - let processAbout = require('./inc/processSubredditAbout.js')(); - let tedditApiSubreddit = require('./inc/teddit_api/handleSubreddit.js')(); - let tedditApiUser = require('./inc/teddit_api/handleUser.js')(); - let processSubredditsExplore = require('./inc/processSubredditsExplore.js')(); - let processMoreComments = require('./inc/processMoreComments.js')(); - - app.all('*', (req, res, next) => { - let themeOverride = req.query.theme - if(themeOverride) { - // Convert Dark to dark since the stylesheet has it lower case - themeOverride = themeOverride.toLowerCase() - // This override here will set it for the current request - req.cookies.theme = themeOverride - // this will set it for future requests - res.cookie('theme', themeOverride, { maxAge: 31536000, httpOnly: true }) - } else if(!req.cookies.theme && req.cookies.theme !== '') { - req.cookies.theme = config.theme - } - - let flairsOverride = req.query.flairs - if(flairsOverride) { - req.cookies.flairs = flairsOverride - res.cookie('flairs', flairsOverride, { maxAge: 31536000, httpOnly: true }) - } - - let nsfwEnabledOverride = req.query.nsfw_enabled - if(nsfwEnabledOverride) { - req.cookies.nsfw_enabled = nsfwEnabledOverride - res.cookie('nsfw_enabled', nsfwEnabledOverride, { maxAge: 31536000, httpOnly: true }) - } - - let highlightControversialOverride = req.query.highlight_controversial - if(highlightControversialOverride) { - req.cookies.highlight_controversial = highlightControversialOverride - res.cookie('highlight_controversial', highlightControversialOverride, { maxAge: 31536000, httpOnly: true }) - } - - let postMediaMaxHeight = req.query.post_media_max_height - if(postMediaMaxHeight) { - if(config.post_media_max_heights.hasOwnProperty(postMediaMaxHeight) || !isNaN(postMediaMaxHeight)) { - req.cookies.post_media_max_height = postMediaMaxHeight - res.cookie('post_media_max_height', postMediaMaxHeight, { maxAge: 31536000, httpOnly: true }) - } - } - - let collapseChildComments = req.query.collapse_child_comments - if(collapseChildComments) { - req.cookies.collapse_child_comments = collapseChildComments - res.cookie('collapse_child_comments', collapseChildComments, { maxAge: 31536000, httpOnly: true }) - } - - let showUpvotedPercentage = req.query.show_upvoted_percentage - if(showUpvotedPercentage) { - req.cookies.show_upvoted_percentage = showUpvotedPercentage - res.cookie('show_upvoted_percentage', showUpvotedPercentage, { maxAge: 31536000, httpOnly: true }) - } - - let domainTwitter = req.query.domain_twitter - if(domainTwitter) { - req.cookies.domain_twitter = domainTwitter - res.cookie('domain_twitter', domainTwitter, { maxAge: 31536000, httpOnly: true }) - } - - let domainYoutube = req.query.domain_youtube - if(domainYoutube) { - req.cookies.domain_youtube = domainYoutube - res.cookie('domain_youtube', domainYoutube, { maxAge: 31536000, httpOnly: true }) - } - - let domainInstagram = req.query.domain_instagram - if(domainInstagram) { - req.cookies.domain_instagram = domainInstagram - res.cookie('domain_instagram', domainInstagram, { maxAge: 31536000, httpOnly: true }) - } - - let videosMuted = req.query.videos_muted - if(videosMuted) { - req.cookies.videos_muted = videosMuted - res.cookie('videos_muted', videosMuted, { maxAge: 31536000, httpOnly: true }) - } - - if(!config.rate_limiting) { - return next() - } - - const valid_reddit_starts = ['/https://old.reddit.com', '/https://reddit.com', '/https://www.reddit.com', '/old.reddit.com', '/reddit.com', '/www.reddit.com'] - for(var i = 0; i < valid_reddit_starts.length; i++) { - if(req.url.startsWith(valid_reddit_starts[i])) { - req.url = req.url.substring(1) - const redditRegex = /([A-z.]+\.)?(reddit(\.com))/gm; - let teddified_url = req.url.replace(redditRegex, '') - if(teddified_url.includes('://')) { - teddified_url = teddified_url.split('://')[1] - } - if(teddified_url == '') { - teddified_url = '/' - } - return res.redirect(teddified_url) - } - } - - if(config.rate_limiting.enabled) { - /** - * This route enforces request limits based on an IP address if - * config.rate_limiting.enabled is true. By default it's false. - */ - - let ip = String(req.headers['x-forwarded-for'] || req.connection.remoteAddress || 'unknown') - - if(ip === 'unknown') { - return next() - } - - if(ratelimit_counts[ip] == undefined) { - ratelimit_counts[ip] = 0 - } - - if(ratelimit_timestamps[ip] == undefined) { - ratelimit_timestamps[ip] = Date.now() - } - - let diff = Date.now() - ratelimit_timestamps[ip] - let credit = (diff / 60000) * config.rate_limiting.limit_after_limited - ratelimit_counts[ip] -= credit - - if(ratelimit_counts[ip] < 0) { - ratelimit_counts[ip] = 0 - } - - ratelimit_counts[ip]++ - ratelimit_timestamps[ip] = Date.now() - - if(ratelimit_counts[ip] > config.rate_limiting.initial_limit) { - console.log(`RATE LIMITED IP ADDRESS: ${ip}`) - return res.send(`Hold your horses! You have hit the request limit. You should be able to refresh this page in a couple of seconds. If you think you are wrongfully limited, create an issue at https://codeberg.org/teddit/teddit. Rate limiting is highly experimental feature.`) - } else { - return next() - } - } else { - return next() - } - }) - - app.get('/about', (req, res, next) => { - return res.render('about', { user_preferences: req.cookies }) - }) - - app.get('/preferences', (req, res, next) => { - return res.render('preferences', { user_preferences: req.cookies, instance_config: config }) - }) - - app.get('/resetprefs', (req, res, next) => { - resetPreferences(res) - return res.redirect('/preferences') - }) - - app.get('/import_prefs/:key', (req, res, next) => { - let key = req.params.key - if(!key) - return res.redirect('/') - if(key.length !== 10) - return res.redirect('/') - - key = `prefs_key:${key}` - redis.get(key, (error, json) => { - if(error) { - console.error(`Error getting the preferences import key ${key} from redis.`, error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } - if(json) { - try { - let prefs = JSON.parse(json) - let subbed_subreddits_is_set = false - for(var setting in prefs) { - if(prefs.hasOwnProperty(setting)) { - res.cookie(setting, prefs[setting], { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - if(setting === 'subbed_subreddits') - subbed_subreddits_is_set = true - } - } - if(!subbed_subreddits_is_set) - res.clearCookie('subbed_subreddits') - return res.redirect('/') - } catch(e) { - console.error(`Error setting imported preferences to the cookies. Key: ${key}.`, error) - } - } else { - return res.redirect('/preferences') - } - }) - }) - - app.get('/privacy', (req, res, next) => { - return res.render('privacypolicy', { user_preferences: req.cookies }) - }) - - app.get('/gallery/:id', (req, res, next) => { - return res.redirect(`/comments/${req.params.id}`) - }) - - app.get('/poll/:id', (req, res, next) => { - return res.redirect(`/comments/${req.params.id}`) - }) - - app.get('/saved', (req, res, next) => { - let saved = req.cookies.saved - - if(!saved || !Array.isArray(saved)) { - return res.render('saved', { - json: null, - user_preferences: req.cookies, - }) - } - - let key = `saved_posts:${saved.join(',')}` - redis.get(key, (error, json) => { - if(error) { - console.error(`Error getting saved_post ${saved_post} key from redis.`, error) - return res.redirect('/') - } - if(json) { - (async () => { - let processed_json = await processJsonSubreddit(json, 'redis', null, req.cookies, true) - if(!processed_json.error) { - return res.render('saved', { - json: processed_json, - user_preferences: req.cookies, - }) - } else { - return res.render('subreddit', { - json: null, - error: true, - data: processed_json, - user_preferences: req.cookies - }) - } - })() - } - }) - }) - - app.get('/save/:id', (req, res, next) => { - let post_id = req.params.id - let redis_key = req.query.rk - let back = req.query.b - let saved = req.cookies.saved - let fetched = req.query.f - - if(!post_id || !redis_key) - return res.redirect('/saved') - - if(!saved || !Array.isArray(saved)) - saved = [] - - if(saved.length > 100) - return res.send('You can not save more than 100 posts.') - - redis.get(redis_key, (error, json) => { - if(error) { - console.error(`Error getting the ${redis_key} key from redis (via /save/).`, error) - return res.redirect('/') - } - if(json) { - json = JSON.parse(json) - if(fetched === 'true' || redis_key.includes('/comments/')) - json = json[0] - - let post_to_save = false - for(var i = 0; i < json.data.children.length; i++) { - let post = json.data.children[i] - if(post.data.id === post_id) { - post_to_save = post - break - } - } - - if(post_to_save) { - if(!saved || !Array.isArray(saved)) - saved = [] - - for(var i = 0; i < saved.length; i++) { - if(post_to_save.data.id === saved[i]) - return res.redirect('/saved') - } - - let key = `saved_posts:${saved.join(',')}` - redis.get(key, (error, json) => { - if(error) { - console.error(`Error getting saved_posts ${key} key from redis.`, error) - return res.redirect('/') - } - links = JSON.parse(json) - if(!links) - links = [] - - links.unshift(post_to_save) - saved.unshift(post_to_save.data.id) - res.cookie('saved', saved, { maxAge: 3 * 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - let new_key = `saved_posts:${saved.join(',')}` - redis.set(new_key, JSON.stringify(links), (error) => { - if(error) - console.error(`Error saving ${new_key} to redis.`, error) - - if(!back) - return res.redirect('/saved') - else { - back = back.replace(/§2/g, '?').replace(/§1/g, '&') - return res.redirect(back) - } - }) - }) - } else { - return res.redirect(`/comments/${post_id}/?save=true&b=${back}`) - } - } else { - return res.redirect(`/comments/${post_id}/?save=true&b=${back}`) - } - }) - }) - - app.get('/unsave/:id', (req, res, next) => { - let post_id = req.params.id - let back = req.query.b - let saved = req.cookies.saved - - if(!post_id) - return res.redirect('/saved') - - if(!saved || !Array.isArray(saved)) - return res.redirect('/saved') - - let key = `saved_posts:${saved.join(',')}` - redis.get(key, (error, json) => { - if(error) { - console.error(`Error getting the ${key} key from redis (via /save/).`, error) - return res.redirect('/') - } - if(json) { - json = JSON.parse(json) - let post_found = false - for(var i = 0; i < json.length; i++) { - if(json[i].data.id === post_id) { - post_found = true - json.splice(i, 1) - for(var j = 0; j < saved.length; j++) { - if(saved[j] === post_id) - saved.splice(j, 1) - } - } - } - if(post_found) { - res.cookie('saved', saved, { maxAge: 3 * 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - let new_key = `saved_posts:${saved.join(',')}` - redis.set(new_key, JSON.stringify(json), (error) => { - if(error) - console.error(`Error saving ${new_key} to redis.`, error) - - if(!back) - return res.redirect('/saved') - else { - back = back.replace(/§2/g, '?').replace(/§1/g, '&') - return res.redirect(back) - } - }) - } else { - return res.redirect(`/saved`) - } - } else { - return res.redirect(`/saved`) - } - }) - }) - - app.get('/subreddits/:sort?', (req, res, next) => { - let q = req.query.q - let nsfw = req.query.nsfw - let after = req.query.after - let before = req.query.before - let sortby = req.params.sort - let searching = false - - if(!after) { - after = '' - } - if(!before) { - before = '' - } - - let d = `&after=${after}` - if(before) { - d = `&before=${before}` - } - - if(nsfw !== 'on') { - nsfw = 'off' - } - - if(!sortby) { - sortby = '' - } - - let key = `subreddits:sort:${sortby}${d}` - - if(sortby === 'search') { - if(typeof(q) == 'undefined' || q == '') - return res.redirect('/subreddits') - - key = `subreddits:search:q:${q}:nsfw:${nsfw}${d}` - searching = true - } - - redis.get(key, (error, json) => { - if(error) { - console.error(`Error getting the subreddits key from redis.`, error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } - if(json) { - console.log(`Got subreddits key from redis.`); - (async () => { - let processed_json = await processJsonSubredditsExplore(json, 'redis', null, req.cookies) - if(!processed_json.error) { - return res.render('subreddits_explore', { - json: processed_json, - sortby: sortby, - after: after, - before: before, - q: q, - nsfw: nsfw, - searching: searching, - subreddits_front: (!before && !after) ? true : false, - user_preferences: req.cookies, - instance_nsfw_enabled: config.nsfw_enabled - }) - } else { - return res.render('subreddits_explore', { - json: null, - error: true, - data: processed_json, - user_preferences: req.cookies - }) - } - })() - } else { - let url = '' - if(config.use_reddit_oauth) { - if(!searching) - url = `https://oauth.reddit.com/subreddits/${sortby}?api_type=json&count=25&g=GLOBAL&t=${d}` - else - url = `https://oauth.reddit.com/subreddits/search?api_type=json&q=${q}&include_over_18=${nsfw}${d}` - } else { - if(!searching) - url = `https://reddit.com/subreddits/${sortby}.json?api_type=json&count=25&g=GLOBAL&t=${d}` - else - url = `https://reddit.com/subreddits/search.json?api_type=json&q=${q}&include_over_18=${nsfw}${d}` - } - - fetch(encodeURI(url), redditApiGETHeaders()) - .then(result => { - if(result.status === 200) { - result.json() - .then(json => { - let ex = config.setexs.subreddits_explore.front - if(sortby === 'new') - ex = config.setexs.subreddits_explore.new_page - redis.setex(key, ex, JSON.stringify(json), (error) => { - if(error) { - console.error(`Error setting the subreddits key to redis.`, error) - return res.render('subreddits_explore', { json: null, user_preferences: req.cookies }) - } else { - console.log(`Fetched the JSON from reddit.com/subreddits.`); - (async () => { - let processed_json = await processJsonSubredditsExplore(json, 'from_online', null, req.cookies) - return res.render('subreddits_explore', { - json: processed_json, - sortby: sortby, - after: after, - before: before, - q: q, - nsfw: nsfw, - searching: searching, - subreddits_front: (!before && !after) ? true : false, - user_preferences: req.cookies, - instance_nsfw_enabled: config.nsfw_enabled - }) - })() - } - }) - }) - } else { - if(result.status === 404) { - console.log('404 – Subreddits 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/subreddits.`, error) - }) - } - }) - }) - - app.get('/subscribe/:subreddit', (req, res, next) => { - let subreddit = req.params.subreddit - let subbed = req.cookies.subbed_subreddits - let back = req.query.b - - if(!subreddit) - return res.redirect('/') - - if(!subbed || !Array.isArray(subbed)) - subbed = [] - - if(!subbed.includes(subreddit)) - subbed.push(subreddit) - - res.cookie('subbed_subreddits', subbed, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(!back) - return res.redirect('/r/' + subreddit) - else { - back = back.replace(/,/g, '+').replace(/§1/g, '&') - return res.redirect(back) - } - }) - - app.get('/import_subscriptions/:subreddits', (req, res, next) => { - let subreddits = req.params.subreddits - let subbed = req.cookies.subbed_subreddits - let back = req.query.b - - if(!subreddits) - return res.redirect('/') - - if(!subbed || !Array.isArray(subbed)) - subbed = [] - - subreddits = subreddits.split('+') - for(var i = 0; i < subreddits.length; i++) { - if(!subbed.includes(subreddits[i])) - subbed.push(subreddits[i]) - } - - res.cookie('subbed_subreddits', subbed, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(!back) - return res.redirect('/r/' + subreddits) - else { - back = back.replace(/,/g, '+').replace(/ /g, '+') - return res.redirect(back) - } - }) - - app.get('/unsubscribe/:subreddit', (req, res, next) => { - let subreddit = req.params.subreddit - let subbed = req.cookies.subbed_subreddits - let back = req.query.b - - if(!subreddit || !subbed || !Array.isArray(subbed)) { - res.clearCookie('subbed_subreddits') - return res.redirect('/') - } - - var index = subbed.indexOf(subreddit) - if(index !== -1) - subbed.splice(index, 1) - - if(subbed.length <= 0) - res.clearCookie('subbed_subreddits') - else - res.cookie('subbed_subreddits', subbed, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(!back) - return res.redirect('/r/' + subreddit) - else { - back = back.replace(/,/g, '+').replace(/§1/g, '&') - return res.redirect(back) - } - }) - - app.get('/search', (req, res, next) => { - 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: 'all', - 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 = '' - } - if(restrict_sr !== 'on') { - restrict_sr = 'off' - } - - if(nsfw !== 'on') { - nsfw = 'off' - } - let d = `&after=${after}` - if(before) { - d = `&before=${before}` - } - return res.redirect(`/r/all/search?q=${q}&restrict_sr=${restrict_sr}&nsfw=${nsfw}&sort=${sortby}&t=${past}${d}`) - }) - - app.get('/:sort?', async (req, res, next) => { - let past = req.query.t - let before = req.query.before - let after = req.query.after - let sortby = req.params.sort || '' - let api_req = req.query.api - let api_type = req.query.type - let api_target = req.query.target - - let proxyable = (sortby.includes('.jpg') || sortby.includes('.png') || sortby.includes('.jpeg')) ? true : false - if(proxyable) { - let params = new URLSearchParams(req.query).toString() - let image_url = `https://preview.redd.it/${sortby}?${params}` - let proxied_image = await downloadAndSave(image_url) - if(proxied_image) { - return res.redirect(proxied_image) - } else { - return res.redirect('/') - } - } - - let d = `&after=${after}` - if(before) { - d = `&before=${before}` - } - - if(sortby == '') { - sortby = 'hot' - } - - if(['apple-touch-icon.png', 'apple-touch-icon-precomposed.png', 'apple-touch-icon-120x120.png', 'apple-touch-icon-120x120-precomposed.png'].includes(sortby)) { - return res.sendStatus(404) // return 404 on shitty apple favicon stuff - } - - if(!['new', 'rising', 'controversial', 'top', 'gilded', 'hot'].includes(sortby)) { - console.log(`Got invalid sort.`, req.originalUrl) - return res.redirect('/') - } - - 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(`/`) - } - } else { - past = undefined - } - } else { - if(sortby === 'controversial' || sortby === 'top') { - past = 'day' - } - } - - 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 key = `/after:${after}:before:${before}:sort:${sortby}:past:${past}:raw_json:${raw_json}` - - let subbed_subreddits = req.cookies.subbed_subreddits - let get_subbed_subreddits = false - if(subbed_subreddits && Array.isArray(subbed_subreddits)) { - get_subbed_subreddits = true - subbed_subreddits = subbed_subreddits.join('+') - key = `${subbed_subreddits.toLowerCase()}:${after}:${before}:sort:${sortby}:past:${past}:raw_json:${raw_json}` - } - - redis.get(key, (error, json) => { - if(error) { - console.error('Error getting the frontpage key from redis.', error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } - if(json) { - console.log('Got frontpage key from redis.'); - (async () => { - if(api_req) { - return handleTedditApiSubreddit(json, req, res, 'redis', api_type, api_target, '/') - } else { - let processed_json = await processJsonSubreddit(json, 'redis', null, req.cookies) - return res.render('index', { - json: processed_json, - sortby: sortby, - past: past, - user_preferences: req.cookies, - redis_key: key - }) - } - })() - } else { - let url = '' - if(config.use_reddit_oauth) { - if(get_subbed_subreddits) - url = `https://oauth.reddit.com/r/${subbed_subreddits}/${sortby}?api_type=json&count=25&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}` - else - url = `https://oauth.reddit.com/${sortby}?api_type=json&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}` - } else { - if(get_subbed_subreddits) - url = `https://reddit.com/r/${subbed_subreddits}/${sortby}.json?api_type=json&count=25&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}` - else - url = `https://reddit.com/${sortby}.json?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.frontpage, JSON.stringify(json), (error) => { - if(error) { - console.error('Error setting the frontpage key to redis.', error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } else { - console.log('Fetched the frontpage from Reddit.'); - (async () => { - if(api_req) { - return handleTedditApiSubreddit(json, req, res, 'from_online', api_type, api_target, '/') - } else { - let processed_json = await processJsonSubreddit(json, 'from_online', null, req.cookies) - return res.render('index', { - json: processed_json, - sortby: sortby, - past: past, - user_preferences: req.cookies, - redis_key: key - }) - } - })() - } - }) - }) - } 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) - }) - } - }) - }) - - app.get('/comments/:post_id/:comment?/:comment_id?', (req, res, next) => { - let post_id = req.params.post_id - let comment = req.params.comment - let comment_id = req.params.comment_id - let back = req.query.b - let save = req.query.save - let post_url = false - let comment_url = false - - if(comment) - if(comment !== 'comment' || !comment_id) - return res.redirect('/') - - if(comment) - comment_url = true - else - post_url = true - - let key = `/shorturl:post:${post_id}:comment:${comment_id}` - redis.get(key, (error, json) => { - if(error) { - console.error('Error getting the short URL for post key from redis.', error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } - if(json) { - console.log('Got short URL for post key from redis.') - json = JSON.parse(json) - if(post_url) { - if(save === 'true') - return res.redirect(`/save/${post_id}/?rk=${key}&b=${back}&f=true`) - return res.redirect(json[0].data.children[0].data.permalink) - } else { - return res.redirect(json[1].data.children[0].data.permalink) - } - } else { - let url = '' - if(config.use_reddit_oauth) { - if(post_url) - url = `https://oauth.reddit.com/comments/${post_id}?api_type=json` - else - url = `https://oauth.reddit.com/comments/${post_id}/comment/${comment_id}?api_type=json` - } else { - if(post_url) - url = `https://reddit.com/comments/${post_id}.json?api_type=json` - else - url = `https://reddit.com/comments/${post_id}/comment/${comment_id}.json?api_type=json` - } - - fetch(encodeURI(url), redditApiGETHeaders()) - .then(result => { - if(result.status === 200) { - result.json() - .then(json => { - redis.setex(key, config.setexs.shorts, JSON.stringify(json), (error) => { - if(error) { - console.error('Error setting the short URL for post key to redis.', error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } else { - console.log('Fetched the short URL for post from Reddit.') - if(post_url) { - if(save === 'true') - return res.redirect(`/save/${post_id}/?rk=${key}&b=${back}&f=true`) - return res.redirect(json[0].data.children[0].data.permalink) - } else { - return res.redirect(json[1].data.children[0].data.permalink) - } - } - }) - }) - } 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 short URL for post with sortby JSON file.', error) - }) - } - }) - }) - - app.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) - }) - } - }) - }) - - app.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 = '' - 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) - }) - } - }) - }) - - app.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}`) - }) - - app.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) - }) - }) - - app.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) - }) - } - }) - }) - - app.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) - }) - } - }) - }) - - app.get('/user/:user/:kind?', (req, res, next) => { - let kind = '' - if(req.params.kind) - kind = `/${req.params.kind}` - let q = '' - if(req.query.sort) - q += `?sort=${req.query.sort}&` - if(req.query.t) - q += `t=${req.query.t}` - - res.redirect(`/u/${req.params.user}${kind}${q}`) - }) - - app.get('/u/:user/:kind?', (req, res, next) => { - let user = req.params.user - let after = req.query.after - let before = req.query.before - let post_type = req.params.kind - let kind = post_type - let user_data = {} - 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) - - if(!after) { - after = '' - } - if(!before) { - before = '' - } - let d = `&after=${after}` - if(before) { - d = `&before=${before}` - } - - post_type = `/${post_type}` - switch(post_type) { - case '/comments': - kind = 't1' - break; - case '/submitted': - kind = 't3' - break; - default: - post_type = '' - kind = '' - } - - let sortby = req.query.sort - let past = req.query.t - - if(!sortby) { - sortby = 'new' - } - - if(!['hot', 'new', 'controversial', 'top'].includes(sortby)) { - console.log(`Got invalid sort.`, req.originalUrl) - return res.redirect(`/u/${user}`) - } - - 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(`/u/${user}/${sortby}`) - } - } else { - past = '' - } - } else { - if(sortby === 'controversial' || sortby === 'top') { - past = 'all' - } else { - past = '' - } - } - - let key = `${user}:${after}:${before}:sort:${sortby}:past:${past}:post_type:${post_type}:raw_json:${raw_json}` - redis.get(key, (error, json) => { - if(error) { - console.error(`Error getting the user ${key} key from redis.`, error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } - if(json) { - console.log(`Got user ${user} key from redis.`); - (async () => { - if(api_req) { - return handleTedditApiUser(json, req, res, 'redis', api_type, api_target, user, after, before) - } else { - let processed_json = await processJsonUser(json, false, after, before, req.cookies, kind, post_type) - return res.render('user', { - data: processed_json, - sortby: sortby, - past: past, - user_preferences: req.cookies - }) - } - })() - } else { - let url = '' - if(config.use_reddit_oauth) - url = `https://oauth.reddit.com/user/${user}/about?raw_json=${raw_json}` - else - url = `https://reddit.com/user/${user}/about.json?raw_json=${raw_json}` - fetch(encodeURI(url), redditApiGETHeaders()) - .then(result => { - if(result.status === 200) { - result.json() - .then(json => { - user_data.about = json - let url = '' - if(config.use_reddit_oauth) { - let endpoint = '/overview' - if(post_type !== '') - endpoint = post_type - url = `https://oauth.reddit.com/user/${user}${post_type}?limit=26${d}&sort=${sortby}&t=${past}&raw_json=${raw_json}` - } else { - url = `https://reddit.com/user/${user}${post_type}.json?limit=26${d}&sort=${sortby}&t=${past}&raw_json=${raw_json}` - } - fetch(encodeURI(url), redditApiGETHeaders()) - .then(result => { - if(result.status === 200) { - result.json() - .then(json => { - user_data.overview = json - redis.setex(key, config.setexs.user, JSON.stringify(user_data), (error) => { - if(error) { - console.error(`Error setting the user ${key} key to redis.`, error) - return res.render('index', { post: null, user_preferences: req.cookies }) - } else { - (async () => { - if(api_req) { - return handleTedditApiUser(user_data, req, res, 'online', api_type, api_target, user, after, before) - } else { - let processed_json = await processJsonUser(user_data, true, after, before, req.cookies, kind, post_type) - return res.render('user', { - data: processed_json, - 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 overview JSON file from reddit.com/u/${user}`, error) - return res.render('index', { - json: null, - http_status_code: result.status, - user_preferences: req.cookies - }) - }) - }) - } else { - if(result.status === 404) { - console.log('404 – User 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 about JSON file from reddit.com/u/${user}`, error) - }) - } - }) - }) - - app.get('/user/:user/m/:custom_feed', (req, res, next) => { - res.redirect(`/u/${req.params.user}/m/${req.params.custom_feed}`) - }) - - app.get('/u/:user/m/:custom_feed/:sort?', (req, res, next) => { - let user = req.params.user - let custom_feed = req.params.custom_feed - let subreddit = `u/${user}/m/${custom_feed}` - 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 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(`/u/${user}`) - } - - 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(`/u/${user}/${sortby}`) - } - } else { - past = undefined - } - } else { - if(sortby === 'controversial' || sortby === 'top') { - past = 'day' - } - } - - let key = `${user.toLowerCase()}:m:${custom_feed}:${after}:${before}:sort:${sortby}:past:${past}` - redis.get(key, (error, json) => { - if(error) { - console.error(`Error getting the ${user} custom_feed key from redis.`, error) - return res.render('index', { json: null, user_preferences: req.cookies }) - } - if(json) { - console.log(`Got /u/${user} custom_feed 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) - if(!processed_json.error) { - return res.render('subreddit', { - json: processed_json, - subreddit: '../' + subreddit, - subreddit_about: null, - 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/${subreddit}/${sortby}?api_type=json&count=25&g=GLOBAL&t=${past}${d}` - else - url = `https://reddit.com/${subreddit}/${sortby}.json?api_type=json&count=25&g=GLOBAL&t=${past}${d}` - 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) - return res.render('subreddit', { - json: processed_json, - subreddit: '../' + subreddit, - subreddit_about: null, - 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/${subreddit}.`, error) - }) - } - }) - }) - - /** - * POSTS - */ - - app.post('/saveprefs', (req, res, next) => { - let theme = req.body.theme - let flairs = req.body.flairs - let nsfw_enabled = req.body.nsfw_enabled - let highlight_controversial = req.body.highlight_controversial - let post_media_max_height = req.body.post_media_max_height - let collapse_child_comments = req.body.collapse_child_comments - let show_upvoted_percentage = req.body.show_upvoted_percentage - let domain_twitter = req.body.domain_twitter - let domain_youtube = req.body.domain_youtube - let domain_instagram = req.body.domain_instagram - let videos_muted = req.body.videos_muted - - res.cookie('theme', theme, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(flairs === 'on') - flairs = 'true' - else - flairs = 'false' - res.cookie('flairs', flairs, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(nsfw_enabled === 'on') - nsfw_enabled = 'true' - else - nsfw_enabled = 'false' - res.cookie('nsfw_enabled', nsfw_enabled, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(highlight_controversial === 'on') - highlight_controversial = 'true' - else - highlight_controversial = 'false' - res.cookie('highlight_controversial', highlight_controversial, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(config.post_media_max_heights.hasOwnProperty(post_media_max_height) || !isNaN(post_media_max_height)) - res.cookie('post_media_max_height', post_media_max_height, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(collapse_child_comments === 'on') - collapse_child_comments = 'true' - else - collapse_child_comments = 'false' - res.cookie('collapse_child_comments', collapse_child_comments, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(show_upvoted_percentage === 'on') - show_upvoted_percentage = 'true' - else - show_upvoted_percentage = 'false' - res.cookie('show_upvoted_percentage', show_upvoted_percentage, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - if(videos_muted === 'on') - videos_muted = 'true' - else - videos_muted = 'false' - res.cookie('videos_muted', videos_muted, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - res.cookie('domain_twitter', domain_twitter, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - res.cookie('domain_youtube', domain_youtube, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - res.cookie('domain_instagram', domain_instagram, { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - - return res.redirect('/preferences') - }) - - app.post('/export_prefs', (req, res, next) => { - let export_saved = req.body.export_saved - let export_data = req.cookies - let export_to_file = req.body.export_to_file - - if(export_saved !== 'on') { - if(req.cookies.saved) - delete export_data.saved - } - - if(export_to_file === 'on') { - res.setHeader('Content-disposition', 'attachment; filename=teddit_prefs.json') - res.setHeader('Content-type', 'application/json') - return res.send(export_data) - } - - let r = `${(Math.random().toString(36)+'00000000000000000').slice(2, 10+2).toUpperCase()}` - let key = `prefs_key:${r}` - redis.set(key, JSON.stringify(export_data), (error) => { - if(error) { - console.error(`Error saving preferences to redis.`, error) - return res.redirect('/preferences') - } else { - return res.render('preferences', { user_preferences: req.cookies, instance_config: config, preferences_key: r }) - } - }) - }) - - app.post('/import_prefs', (req, res, next) => { - let body = '' - req.on('data', chunk => { - body += chunk.toString() - }) - req.on('end', () => { - body = body.toString() - try { - let json = body.split('Content-Type: application/json')[1].trim().split('--')[0] - let prefs = JSON.parse(json) - resetPreferences(res) - for(var setting in prefs) { - if(prefs.hasOwnProperty(setting)) { - res.cookie(setting, prefs[setting], { maxAge: 365 * 24 * 60 * 60 * 1000, httpOnly: true }) - } - } - return res.redirect('/preferences') - } catch(e) { - console.error(`Error importing preferences from a JSON file. Please report this error on https://codeberg.org/teddit/teddit.`, e) - } - }) - }) - - app.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`) - }) - - function resetPreferences(res) { - res.clearCookie('theme') - res.clearCookie('flairs') - res.clearCookie('nsfw_enabled') - res.clearCookie('highlight_controversial') - res.clearCookie('post_media_max_height') - res.clearCookie('collapse_child_comments') - res.clearCookie('show_upvoted_percentage') - res.clearCookie('subbed_subreddits') - res.clearCookie('domain_twitter') - res.clearCookie('domain_youtube') - res.clearCookie('domain_instagram') - res.clearCookie('videos_muted') - } -} - - diff --git a/routes/gallery.js b/routes/gallery.js new file mode 100644 index 0000000..3590aca --- /dev/null +++ b/routes/gallery.js @@ -0,0 +1,7 @@ +const galleryRoute = require('express').Router(); + +galleryRoute.get('/gallery/:id', (req, res, next) => { + return res.redirect(`/comments/${req.params.id}`); +}); + +module.exports = galleryRoute; diff --git a/routes/home.js b/routes/home.js new file mode 100644 index 0000000..ceb5800 --- /dev/null +++ b/routes/home.js @@ -0,0 +1,219 @@ +const config = require('../config'); +const { redis, fetch } = require('../app'); +const homeRoute = require('express').Router(); + +const processUser = require('../inc/processJsonUser.js')(); +const processPost = require('../inc/processJsonPost.js')(); +const processAbout = require('../inc/processSubredditAbout.js')(); +const tedditApiUser = require('../inc/teddit_api/handleUser.js')(); +const processSearches = require('../inc/processSearchResults.js')(); +const processSubreddit = require('../inc/processJsonSubreddit.js')(); +const tedditApiSubreddit = require('../inc/teddit_api/handleSubreddit.js')(); +const processMoreComments = require('../inc/processMoreComments.js')(); +const processSubredditsExplore = + require('../inc/processSubredditsExplore.js')(); + +homeRoute.get('/:sort?', async (req, res, next) => { + let past = req.query.t; + let before = req.query.before; + let after = req.query.after; + let sortby = req.params.sort || ''; + let api_req = req.query.api; + let api_type = req.query.type; + let api_target = req.query.target; + + let proxyable = + sortby.includes('.jpg') || + sortby.includes('.png') || + sortby.includes('.jpeg') + ? true + : false; + if (proxyable) { + let params = new URLSearchParams(req.query).toString(); + let image_url = `https://preview.redd.it/${sortby}?${params}`; + let proxied_image = await downloadAndSave(image_url); + if (proxied_image) { + return res.redirect(proxied_image); + } else { + return res.redirect('/'); + } + } + + let d = `&after=${after}`; + if (before) { + d = `&before=${before}`; + } + + if (sortby == '') { + sortby = 'hot'; + } + + if ( + [ + 'apple-touch-icon.png', + 'apple-touch-icon-precomposed.png', + 'apple-touch-icon-120x120.png', + 'apple-touch-icon-120x120-precomposed.png', + ].includes(sortby) + ) { + return res.sendStatus(404); // return 404 on shitty apple favicon stuff + } + + if ( + !['new', 'rising', 'controversial', 'top', 'gilded', 'hot'].includes(sortby) + ) { + console.log(`Got invalid sort.`, req.originalUrl); + return res.redirect('/'); + } + + 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(`/`); + } + } else { + past = undefined; + } + } else { + if (sortby === 'controversial' || sortby === 'top') { + past = 'day'; + } + } + + 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 key = `/after:${after}:before:${before}:sort:${sortby}:past:${past}:raw_json:${raw_json}`; + + let subbed_subreddits = req.cookies.subbed_subreddits; + let get_subbed_subreddits = false; + if (subbed_subreddits && Array.isArray(subbed_subreddits)) { + get_subbed_subreddits = true; + subbed_subreddits = subbed_subreddits.join('+'); + key = `${subbed_subreddits.toLowerCase()}:${after}:${before}:sort:${sortby}:past:${past}:raw_json:${raw_json}`; + } + + redis.get(key, (error, json) => { + if (error) { + console.error('Error getting the frontpage key from redis.', error); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } + if (json) { + console.log('Got frontpage key from redis.'); + (async () => { + if (api_req) { + return handleTedditApiSubreddit( + json, + req, + res, + 'redis', + api_type, + api_target, + '/' + ); + } else { + let processed_json = await processJsonSubreddit( + json, + 'redis', + null, + req.cookies + ); + return res.render('index', { + json: processed_json, + sortby: sortby, + past: past, + user_preferences: req.cookies, + redis_key: key, + }); + } + })(); + } else { + let url = ''; + if (config.use_reddit_oauth) { + if (get_subbed_subreddits) + url = `https://oauth.reddit.com/r/${subbed_subreddits}/${sortby}?api_type=json&count=25&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}`; + else + url = `https://oauth.reddit.com/${sortby}?api_type=json&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}`; + } else { + if (get_subbed_subreddits) + url = `https://reddit.com/r/${subbed_subreddits}/${sortby}.json?api_type=json&count=25&g=GLOBAL&t=${past}${d}&raw_json=${raw_json}`; + else + url = `https://reddit.com/${sortby}.json?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.frontpage, + JSON.stringify(json), + (error) => { + if (error) { + console.error( + 'Error setting the frontpage key to redis.', + error + ); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } else { + console.log('Fetched the frontpage from Reddit.'); + (async () => { + if (api_req) { + return handleTedditApiSubreddit( + json, + req, + res, + 'from_online', + api_type, + api_target, + '/' + ); + } else { + let processed_json = await processJsonSubreddit( + json, + 'from_online', + null, + req.cookies + ); + return res.render('index', { + json: processed_json, + sortby: sortby, + past: past, + user_preferences: req.cookies, + redis_key: key, + }); + } + })(); + } + } + ); + }); + } 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); + }); + } + }); +}); + +module.exports = homeRoute; diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..8541395 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,27 @@ +const galleryRoute = require('./gallery'); +const homeRoute = require('./home'); +const overridingRoutes = require('./overides'); +const pollRoute = require('./poll'); +const preferenceRoutes = require('./preferences'); +const saveRoutes = require('./save'); +const searchRoute = require('./search'); +const staticRoutes = require('./static'); +const subredditRoutes = require('./subreddit'); +const subscriptionRoutes = require('./subscription'); +const userRoutes = require('./user'); + +const allRoutes = require('express').Router(); + +allRoutes.use(overridingRoutes); +allRoutes.use(staticRoutes); +allRoutes.use(preferenceRoutes); +allRoutes.use(subredditRoutes); +allRoutes.use(userRoutes); +allRoutes.use(subscriptionRoutes); +allRoutes.use(saveRoutes); +allRoutes.use(searchRoute); +allRoutes.use(homeRoute); +allRoutes.use(galleryRoute); +allRoutes.use(pollRoute); + +module.exports = allRoutes; diff --git a/routes/overides.js b/routes/overides.js new file mode 100644 index 0000000..4b5192f --- /dev/null +++ b/routes/overides.js @@ -0,0 +1,184 @@ +const config = require('../config'); +const overridingRoutes = require('express').Router(); + +overridingRoutes.all('*', (req, res, next) => { + let themeOverride = req.query.theme; + if (themeOverride) { + // Convert Dark to dark since the stylesheet has it lower case + themeOverride = themeOverride.toLowerCase(); + // This override here will set it for the current request + req.cookies.theme = themeOverride; + // this will set it for future requests + res.cookie('theme', themeOverride, { maxAge: 31536000, httpOnly: true }); + } else if (!req.cookies.theme && req.cookies.theme !== '') { + req.cookies.theme = config.theme; + } + + let flairsOverride = req.query.flairs; + if (flairsOverride) { + req.cookies.flairs = flairsOverride; + res.cookie('flairs', flairsOverride, { maxAge: 31536000, httpOnly: true }); + } + + let nsfwEnabledOverride = req.query.nsfw_enabled; + if (nsfwEnabledOverride) { + req.cookies.nsfw_enabled = nsfwEnabledOverride; + res.cookie('nsfw_enabled', nsfwEnabledOverride, { + maxAge: 31536000, + httpOnly: true, + }); + } + + let highlightControversialOverride = req.query.highlight_controversial; + if (highlightControversialOverride) { + req.cookies.highlight_controversial = highlightControversialOverride; + res.cookie('highlight_controversial', highlightControversialOverride, { + maxAge: 31536000, + httpOnly: true, + }); + } + + let postMediaMaxHeight = req.query.post_media_max_height; + if (postMediaMaxHeight) { + if ( + config.post_media_max_heights.hasOwnProperty(postMediaMaxHeight) || + !isNaN(postMediaMaxHeight) + ) { + req.cookies.post_media_max_height = postMediaMaxHeight; + res.cookie('post_media_max_height', postMediaMaxHeight, { + maxAge: 31536000, + httpOnly: true, + }); + } + } + + let collapseChildComments = req.query.collapse_child_comments; + if (collapseChildComments) { + req.cookies.collapse_child_comments = collapseChildComments; + res.cookie('collapse_child_comments', collapseChildComments, { + maxAge: 31536000, + httpOnly: true, + }); + } + + let showUpvotedPercentage = req.query.show_upvoted_percentage; + if (showUpvotedPercentage) { + req.cookies.show_upvoted_percentage = showUpvotedPercentage; + res.cookie('show_upvoted_percentage', showUpvotedPercentage, { + maxAge: 31536000, + httpOnly: true, + }); + } + + let domainTwitter = req.query.domain_twitter; + if (domainTwitter) { + req.cookies.domain_twitter = domainTwitter; + res.cookie('domain_twitter', domainTwitter, { + maxAge: 31536000, + httpOnly: true, + }); + } + + let domainYoutube = req.query.domain_youtube; + if (domainYoutube) { + req.cookies.domain_youtube = domainYoutube; + res.cookie('domain_youtube', domainYoutube, { + maxAge: 31536000, + httpOnly: true, + }); + } + + let domainInstagram = req.query.domain_instagram; + if (domainInstagram) { + req.cookies.domain_instagram = domainInstagram; + res.cookie('domain_instagram', domainInstagram, { + maxAge: 31536000, + httpOnly: true, + }); + } + + let videosMuted = req.query.videos_muted; + if (videosMuted) { + req.cookies.videos_muted = videosMuted; + res.cookie('videos_muted', videosMuted, { + maxAge: 31536000, + httpOnly: true, + }); + } + + if (!config.rate_limiting) { + return next(); + } + + const valid_reddit_starts = [ + '/https://old.reddit.com', + '/https://reddit.com', + '/https://www.reddit.com', + '/old.reddit.com', + '/reddit.com', + '/www.reddit.com', + ]; + for (var i = 0; i < valid_reddit_starts.length; i++) { + if (req.url.startsWith(valid_reddit_starts[i])) { + req.url = req.url.substring(1); + const redditRegex = /([A-z.]+\.)?(reddit(\.com))/gm; + let teddified_url = req.url.replace(redditRegex, ''); + if (teddified_url.includes('://')) { + teddified_url = teddified_url.split('://')[1]; + } + if (teddified_url == '') { + teddified_url = '/'; + } + return res.redirect(teddified_url); + } + } + + if (config.rate_limiting.enabled) { + /** + * This route enforces request limits based on an IP address if + * config.rate_limiting.enabled is true. By default it's false. + */ + + let ip = String( + req.headers['x-forwarded-for'] || + req.connection.remoteAddress || + 'unknown' + ); + + if (ip === 'unknown') { + return next(); + } + + if (ratelimit_counts[ip] == undefined) { + ratelimit_counts[ip] = 0; + } + + if (ratelimit_timestamps[ip] == undefined) { + ratelimit_timestamps[ip] = Date.now(); + } + + let diff = Date.now() - ratelimit_timestamps[ip]; + let credit = (diff / 60000) * config.rate_limiting.limit_after_limited; + ratelimit_counts[ip] -= credit; + + if (ratelimit_counts[ip] < 0) { + ratelimit_counts[ip] = 0; + } + + ratelimit_counts[ip]++; + ratelimit_timestamps[ip] = Date.now(); + + if (ratelimit_counts[ip] > config.rate_limiting.initial_limit) { + console.log(`RATE LIMITED IP ADDRESS: ${ip}`); + return res.send( + `Hold your horses! You have hit the request limit. You should be able to refresh this page in a couple of seconds. If you think you are wrongfully limited, create an issue at https://codeberg.org/teddit/teddit. Rate limiting is highly experimental feature.` + ); + } else { + return next(); + } + } else { + return next(); + } +}); + +module.exports = overridingRoutes; diff --git a/routes/poll.js b/routes/poll.js new file mode 100644 index 0000000..83c4dab --- /dev/null +++ b/routes/poll.js @@ -0,0 +1,7 @@ +const pollRoute = require('express').Router(); + +pollRoute.get('/poll/:id', (req, res, next) => { + return res.redirect(`/comments/${req.params.id}`); +}); + +module.exports = pollRoute; diff --git a/routes/preferences.js b/routes/preferences.js new file mode 100644 index 0000000..a9a3abe --- /dev/null +++ b/routes/preferences.js @@ -0,0 +1,229 @@ +const config = require('../config'); +const preferenceRoutes = require('express').Router(); + +function resetPreferences(res) { + res.clearCookie('theme'); + res.clearCookie('flairs'); + res.clearCookie('nsfw_enabled'); + res.clearCookie('highlight_controversial'); + res.clearCookie('post_media_max_height'); + res.clearCookie('collapse_child_comments'); + res.clearCookie('show_upvoted_percentage'); + res.clearCookie('subbed_subreddits'); + res.clearCookie('domain_twitter'); + res.clearCookie('domain_youtube'); + res.clearCookie('domain_instagram'); + res.clearCookie('videos_muted'); +} + +preferenceRoutes.get('/preferences', (req, res, next) => { + return res.render('preferences', { + user_preferences: req.cookies, + instance_config: config, + }); +}); + +preferenceRoutes.get('/resetprefs', (req, res, next) => { + resetPreferences(res); + return res.redirect('/preferences'); +}); + +preferenceRoutes.get('/import_prefs/:key', (req, res, next) => { + let key = req.params.key; + if (!key) return res.redirect('/'); + if (key.length !== 10) return res.redirect('/'); + + key = `prefs_key:${key}`; + redis.get(key, (error, json) => { + if (error) { + console.error( + `Error getting the preferences import key ${key} from redis.`, + error + ); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } + if (json) { + try { + let prefs = JSON.parse(json); + let subbed_subreddits_is_set = false; + for (var setting in prefs) { + if (prefs.hasOwnProperty(setting)) { + res.cookie(setting, prefs[setting], { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + if (setting === 'subbed_subreddits') + subbed_subreddits_is_set = true; + } + } + if (!subbed_subreddits_is_set) res.clearCookie('subbed_subreddits'); + return res.redirect('/'); + } catch (e) { + console.error( + `Error setting imported preferences to the cookies. Key: ${key}.`, + error + ); + } + } else { + return res.redirect('/preferences'); + } + }); +}); + +preferenceRoutes.post('/saveprefs', (req, res, next) => { + let theme = req.body.theme; + let flairs = req.body.flairs; + let nsfw_enabled = req.body.nsfw_enabled; + let highlight_controversial = req.body.highlight_controversial; + let post_media_max_height = req.body.post_media_max_height; + let collapse_child_comments = req.body.collapse_child_comments; + let show_upvoted_percentage = req.body.show_upvoted_percentage; + let domain_twitter = req.body.domain_twitter; + let domain_youtube = req.body.domain_youtube; + let domain_instagram = req.body.domain_instagram; + let videos_muted = req.body.videos_muted; + + res.cookie('theme', theme, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (flairs === 'on') flairs = 'true'; + else flairs = 'false'; + res.cookie('flairs', flairs, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (nsfw_enabled === 'on') nsfw_enabled = 'true'; + else nsfw_enabled = 'false'; + res.cookie('nsfw_enabled', nsfw_enabled, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (highlight_controversial === 'on') highlight_controversial = 'true'; + else highlight_controversial = 'false'; + res.cookie('highlight_controversial', highlight_controversial, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if ( + config.post_media_max_heights.hasOwnProperty(post_media_max_height) || + !isNaN(post_media_max_height) + ) + res.cookie('post_media_max_height', post_media_max_height, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (collapse_child_comments === 'on') collapse_child_comments = 'true'; + else collapse_child_comments = 'false'; + res.cookie('collapse_child_comments', collapse_child_comments, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (show_upvoted_percentage === 'on') show_upvoted_percentage = 'true'; + else show_upvoted_percentage = 'false'; + res.cookie('show_upvoted_percentage', show_upvoted_percentage, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (videos_muted === 'on') videos_muted = 'true'; + else videos_muted = 'false'; + res.cookie('videos_muted', videos_muted, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + res.cookie('domain_twitter', domain_twitter, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + res.cookie('domain_youtube', domain_youtube, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + res.cookie('domain_instagram', domain_instagram, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + return res.redirect('/preferences'); +}); + +preferenceRoutes.post('/export_prefs', (req, res, next) => { + let export_saved = req.body.export_saved; + let export_data = req.cookies; + let export_to_file = req.body.export_to_file; + + if (export_saved !== 'on') { + if (req.cookies.saved) delete export_data.saved; + } + + if (export_to_file === 'on') { + res.setHeader( + 'Content-disposition', + 'attachment; filename=teddit_prefs.json' + ); + res.setHeader('Content-type', 'preferenceRouteslication/json'); + return res.send(export_data); + } + + let r = `${(Math.random().toString(36) + '00000000000000000') + .slice(2, 10 + 2) + .toUpperCase()}`; + let key = `prefs_key:${r}`; + redis.set(key, JSON.stringify(export_data), (error) => { + if (error) { + console.error(`Error saving preferences to redis.`, error); + return res.redirect('/preferences'); + } else { + return res.render('preferences', { + user_preferences: req.cookies, + instance_config: config, + preferences_key: r, + }); + } + }); +}); + +preferenceRoutes.post('/import_prefs', (req, res, next) => { + let body = ''; + req.on('data', (chunk) => { + body += chunk.toString(); + }); + req.on('end', () => { + body = body.toString(); + try { + let json = body + .split('Content-Type: preferenceRouteslication/json')[1] + .trim() + .split('--')[0]; + let prefs = JSON.parse(json); + resetPreferences(res); + for (var setting in prefs) { + if (prefs.hasOwnProperty(setting)) { + res.cookie(setting, prefs[setting], { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + } + } + return res.redirect('/preferences'); + } catch (e) { + console.error( + `Error importing preferences from a JSON file. Please report this error on https://codeberg.org/teddit/teddit.`, + e + ); + } + }); +}); + +module.exports = preferenceRoutes; diff --git a/routes/save.js b/routes/save.js new file mode 100644 index 0000000..6a8cdbf --- /dev/null +++ b/routes/save.js @@ -0,0 +1,315 @@ +const config = require('../config'); +const { redis, fetch } = require('../app'); +const saveRoutes = require('express').Router(); + +const processUser = require('../inc/processJsonUser.js')(); +const processPost = require('../inc/processJsonPost.js')(); +const processAbout = require('../inc/processSubredditAbout.js')(); +const tedditApiUser = require('../inc/teddit_api/handleUser.js')(); +const processSearches = require('../inc/processSearchResults.js')(); +const processSubreddit = require('../inc/processJsonSubreddit.js')(); +const tedditApiSubreddit = require('../inc/teddit_api/handleSubreddit.js')(); +const processMoreComments = require('../inc/processMoreComments.js')(); +const processSubredditsExplore = + require('../inc/processSubredditsExplore.js')(); + +saveRoutes.get('/saved', (req, res, next) => { + let saved = req.cookies.saved; + + if (!saved || !Array.isArray(saved)) { + return res.render('saved', { + json: null, + user_preferences: req.cookies, + }); + } + + let key = `saved_posts:${saved.join(',')}`; + redis.get(key, (error, json) => { + if (error) { + console.error( + `Error getting saved_post ${saved_post} key from redis.`, + error + ); + return res.redirect('/'); + } + if (json) { + (async () => { + let processed_json = await processJsonSubreddit( + json, + 'redis', + null, + req.cookies, + true + ); + if (!processed_json.error) { + return res.render('saved', { + json: processed_json, + user_preferences: req.cookies, + }); + } else { + return res.render('subreddit', { + json: null, + error: true, + data: processed_json, + user_preferences: req.cookies, + }); + } + })(); + } + }); +}); + +saveRoutes.get('/save/:id', (req, res, next) => { + let post_id = req.params.id; + let redis_key = req.query.rk; + let back = req.query.b; + let saved = req.cookies.saved; + let fetched = req.query.f; + + if (!post_id || !redis_key) return res.redirect('/saved'); + + if (!saved || !Array.isArray(saved)) saved = []; + + if (saved.length > 100) + return res.send('You can not save more than 100 posts.'); + + redis.get(redis_key, (error, json) => { + if (error) { + console.error( + `Error getting the ${redis_key} key from redis (via /save/).`, + error + ); + return res.redirect('/'); + } + if (json) { + json = JSON.parse(json); + if (fetched === 'true' || redis_key.includes('/comments/')) + json = json[0]; + + let post_to_save = false; + for (var i = 0; i < json.data.children.length; i++) { + let post = json.data.children[i]; + if (post.data.id === post_id) { + post_to_save = post; + break; + } + } + + if (post_to_save) { + if (!saved || !Array.isArray(saved)) saved = []; + + for (var i = 0; i < saved.length; i++) { + if (post_to_save.data.id === saved[i]) return res.redirect('/saved'); + } + + let key = `saved_posts:${saved.join(',')}`; + redis.get(key, (error, json) => { + if (error) { + console.error( + `Error getting saved_posts ${key} key from redis.`, + error + ); + return res.redirect('/'); + } + links = JSON.parse(json); + if (!links) links = []; + + links.unshift(post_to_save); + saved.unshift(post_to_save.data.id); + res.cookie('saved', saved, { + maxAge: 3 * 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + let new_key = `saved_posts:${saved.join(',')}`; + redis.set(new_key, JSON.stringify(links), (error) => { + if (error) + console.error(`Error saving ${new_key} to redis.`, error); + + if (!back) return res.redirect('/saved'); + else { + back = back.replace(/§2/g, '?').replace(/§1/g, '&'); + return res.redirect(back); + } + }); + }); + } else { + return res.redirect(`/comments/${post_id}/?save=true&b=${back}`); + } + } else { + return res.redirect(`/comments/${post_id}/?save=true&b=${back}`); + } + }); +}); + +saveRoutes.get('/unsave/:id', (req, res, next) => { + let post_id = req.params.id; + let back = req.query.b; + let saved = req.cookies.saved; + + if (!post_id) return res.redirect('/saved'); + + if (!saved || !Array.isArray(saved)) return res.redirect('/saved'); + + let key = `saved_posts:${saved.join(',')}`; + redis.get(key, (error, json) => { + if (error) { + console.error( + `Error getting the ${key} key from redis (via /save/).`, + error + ); + return res.redirect('/'); + } + if (json) { + json = JSON.parse(json); + let post_found = false; + for (var i = 0; i < json.length; i++) { + if (json[i].data.id === post_id) { + post_found = true; + json.splice(i, 1); + for (var j = 0; j < saved.length; j++) { + if (saved[j] === post_id) saved.splice(j, 1); + } + } + } + if (post_found) { + res.cookie('saved', saved, { + maxAge: 3 * 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + let new_key = `saved_posts:${saved.join(',')}`; + redis.set(new_key, JSON.stringify(json), (error) => { + if (error) console.error(`Error saving ${new_key} to redis.`, error); + + if (!back) return res.redirect('/saved'); + else { + back = back.replace(/§2/g, '?').replace(/§1/g, '&'); + return res.redirect(back); + } + }); + } else { + return res.redirect(`/saved`); + } + } else { + return res.redirect(`/saved`); + } + }); +}); + +saveRoutes.get( + '/comments/:post_id/:comment?/:comment_id?', + (req, res, next) => { + let post_id = req.params.post_id; + let comment = req.params.comment; + let comment_id = req.params.comment_id; + let back = req.query.b; + let save = req.query.save; + let post_url = false; + let comment_url = false; + + if (comment) + if (comment !== 'comment' || !comment_id) return res.redirect('/'); + + if (comment) comment_url = true; + else post_url = true; + + let key = `/shorturl:post:${post_id}:comment:${comment_id}`; + redis.get(key, (error, json) => { + if (error) { + console.error( + 'Error getting the short URL for post key from redis.', + error + ); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } + if (json) { + console.log('Got short URL for post key from redis.'); + json = JSON.parse(json); + if (post_url) { + if (save === 'true') + return res.redirect(`/save/${post_id}/?rk=${key}&b=${back}&f=true`); + return res.redirect(json[0].data.children[0].data.permalink); + } else { + return res.redirect(json[1].data.children[0].data.permalink); + } + } else { + let url = ''; + if (config.use_reddit_oauth) { + if (post_url) + url = `https://oauth.reddit.com/comments/${post_id}?api_type=json`; + else + url = `https://oauth.reddit.com/comments/${post_id}/comment/${comment_id}?api_type=json`; + } else { + if (post_url) + url = `https://reddit.com/comments/${post_id}.json?api_type=json`; + else + url = `https://reddit.com/comments/${post_id}/comment/${comment_id}.json?api_type=json`; + } + + fetch(encodeURI(url), redditApiGETHeaders()) + .then((result) => { + if (result.status === 200) { + result.json().then((json) => { + redis.setex( + key, + config.setexs.shorts, + JSON.stringify(json), + (error) => { + if (error) { + console.error( + 'Error setting the short URL for post key to redis.', + error + ); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } else { + console.log( + 'Fetched the short URL for post from Reddit.' + ); + if (post_url) { + if (save === 'true') + return res.redirect( + `/save/${post_id}/?rk=${key}&b=${back}&f=true` + ); + return res.redirect( + json[0].data.children[0].data.permalink + ); + } else { + return res.redirect( + json[1].data.children[0].data.permalink + ); + } + } + } + ); + }); + } 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 short URL for post with sortby JSON file.', + error + ); + }); + } + }); + } +); + +module.exports = saveRoutes; diff --git a/routes/search.js b/routes/search.js new file mode 100644 index 0000000..29427b5 --- /dev/null +++ b/routes/search.js @@ -0,0 +1,48 @@ +const searchRoute = require('express').Router(); + +searchRoute.get('/search', (req, res, next) => { + 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: 'all', + 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 = ''; + } + if (restrict_sr !== 'on') { + restrict_sr = 'off'; + } + + if (nsfw !== 'on') { + nsfw = 'off'; + } + let d = `&after=${after}`; + if (before) { + d = `&before=${before}`; + } + return res.redirect( + `/r/all/search?q=${q}&restrict_sr=${restrict_sr}&nsfw=${nsfw}&sort=${sortby}&t=${past}${d}` + ); +}); + +module.exports = searchRoute; diff --git a/routes/static.js b/routes/static.js new file mode 100644 index 0000000..62485fb --- /dev/null +++ b/routes/static.js @@ -0,0 +1,11 @@ +const staticRoutes = require('express').Router(); + +staticRoutes.get('/privacy', (req, res, next) => { + return res.render('privacypolicy', { user_preferences: req.cookies }); +}); + +staticRoutes.get('/about', (req, res, next) => { + return res.render('about', { user_preferences: req.cookies }); +}); + +module.exports = staticRoutes; diff --git a/routes/subreddit.js b/routes/subreddit.js new file mode 100644 index 0000000..a8830dc --- /dev/null +++ b/routes/subreddit.js @@ -0,0 +1,954 @@ +const config = require('../config'); +const { redis, fetch, RedditAPI } = require('../app'); +const subredditRoutes = require('express').Router(); + +const processUser = require('../inc/processJsonUser.js')(); +const processPost = require('../inc/processJsonPost.js')(); +const processAbout = require('../inc/processSubredditAbout.js')(); +const tedditApiUser = require('../inc/teddit_api/handleUser.js')(); +const processSearches = require('../inc/processSearchResults.js')(); +const processSubreddit = require('../inc/processJsonSubreddit.js')(); +const tedditApiSubreddit = require('../inc/teddit_api/handleSubreddit.js')(); +const processMoreComments = require('../inc/processMoreComments.js')(); +const processSubredditsExplore = + require('../inc/processSubredditsExplore.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 = ''; + 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`); + } +); + +subredditRoutes.get('/subreddits/:sort?', (req, res, next) => { + let q = req.query.q; + let nsfw = req.query.nsfw; + let after = req.query.after; + let before = req.query.before; + let sortby = req.params.sort; + let searching = false; + + if (!after) { + after = ''; + } + if (!before) { + before = ''; + } + + let d = `&after=${after}`; + if (before) { + d = `&before=${before}`; + } + + if (nsfw !== 'on') { + nsfw = 'off'; + } + + if (!sortby) { + sortby = ''; + } + + let key = `subreddits:sort:${sortby}${d}`; + + if (sortby === 'search') { + if (typeof q == 'undefined' || q == '') return res.redirect('/subreddits'); + + key = `subreddits:search:q:${q}:nsfw:${nsfw}${d}`; + searching = true; + } + + redis.get(key, (error, json) => { + if (error) { + console.error(`Error getting the subreddits key from redis.`, error); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } + if (json) { + console.log(`Got subreddits key from redis.`); + (async () => { + let processed_json = await processJsonSubredditsExplore( + json, + 'redis', + null, + req.cookies + ); + if (!processed_json.error) { + return res.render('subreddits_explore', { + json: processed_json, + sortby: sortby, + after: after, + before: before, + q: q, + nsfw: nsfw, + searching: searching, + subreddits_front: !before && !after ? true : false, + user_preferences: req.cookies, + instance_nsfw_enabled: config.nsfw_enabled, + }); + } else { + return res.render('subreddits_explore', { + json: null, + error: true, + data: processed_json, + user_preferences: req.cookies, + }); + } + })(); + } else { + let url = ''; + if (config.use_reddit_oauth) { + if (!searching) + url = `https://oauth.reddit.com/subreddits/${sortby}?api_type=json&count=25&g=GLOBAL&t=${d}`; + else + url = `https://oauth.reddit.com/subreddits/search?api_type=json&q=${q}&include_over_18=${nsfw}${d}`; + } else { + if (!searching) + url = `https://reddit.com/subreddits/${sortby}.json?api_type=json&count=25&g=GLOBAL&t=${d}`; + else + url = `https://reddit.com/subreddits/search.json?api_type=json&q=${q}&include_over_18=${nsfw}${d}`; + } + + fetch(encodeURI(url), redditApiGETHeaders()) + .then((result) => { + if (result.status === 200) { + result.json().then((json) => { + let ex = config.setexs.subreddits_explore.front; + if (sortby === 'new') + ex = config.setexs.subreddits_explore.new_page; + redis.setex(key, ex, JSON.stringify(json), (error) => { + if (error) { + console.error( + `Error setting the subreddits key to redis.`, + error + ); + return res.render('subreddits_explore', { + json: null, + user_preferences: req.cookies, + }); + } else { + console.log(`Fetched the JSON from reddit.com/subreddits.`); + (async () => { + let processed_json = await processJsonSubredditsExplore( + json, + 'from_online', + null, + req.cookies + ); + return res.render('subreddits_explore', { + json: processed_json, + sortby: sortby, + after: after, + before: before, + q: q, + nsfw: nsfw, + searching: searching, + subreddits_front: !before && !after ? true : false, + user_preferences: req.cookies, + instance_nsfw_enabled: config.nsfw_enabled, + }); + })(); + } + }); + }); + } else { + if (result.status === 404) { + console.log('404 – Subreddits 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/subreddits.`, + error + ); + }); + } + }); +}); + +module.exports = subredditRoutes; diff --git a/routes/subscription.js b/routes/subscription.js new file mode 100644 index 0000000..deb8bf1 --- /dev/null +++ b/routes/subscription.js @@ -0,0 +1,82 @@ +const subscriptionRoutes = require('express').Router(); + +subscriptionRoutes.get('/subscribe/:subreddit', (req, res, next) => { + let subreddit = req.params.subreddit; + let subbed = req.cookies.subbed_subreddits; + let back = req.query.b; + + if (!subreddit) return res.redirect('/'); + + if (!subbed || !Array.isArray(subbed)) subbed = []; + + if (!subbed.includes(subreddit)) subbed.push(subreddit); + + res.cookie('subbed_subreddits', subbed, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (!back) return res.redirect('/r/' + subreddit); + else { + back = back.replace(/,/g, '+').replace(/§1/g, '&'); + return res.redirect(back); + } +}); + +subscriptionRoutes.get( + '/import_subscriptions/:subreddits', + (req, res, next) => { + let subreddits = req.params.subreddits; + let subbed = req.cookies.subbed_subreddits; + let back = req.query.b; + + if (!subreddits) return res.redirect('/'); + + if (!subbed || !Array.isArray(subbed)) subbed = []; + + subreddits = subreddits.split('+'); + for (var i = 0; i < subreddits.length; i++) { + if (!subbed.includes(subreddits[i])) subbed.push(subreddits[i]); + } + + res.cookie('subbed_subreddits', subbed, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (!back) return res.redirect('/r/' + subreddits); + else { + back = back.replace(/,/g, '+').replace(/ /g, '+'); + return res.redirect(back); + } + } +); + +subscriptionRoutes.get('/unsubscribe/:subreddit', (req, res, next) => { + let subreddit = req.params.subreddit; + let subbed = req.cookies.subbed_subreddits; + let back = req.query.b; + + if (!subreddit || !subbed || !Array.isArray(subbed)) { + res.clearCookie('subbed_subreddits'); + return res.redirect('/'); + } + + var index = subbed.indexOf(subreddit); + if (index !== -1) subbed.splice(index, 1); + + if (subbed.length <= 0) res.clearCookie('subbed_subreddits'); + else + res.cookie('subbed_subreddits', subbed, { + maxAge: 365 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); + + if (!back) return res.redirect('/r/' + subreddit); + else { + back = back.replace(/,/g, '+').replace(/§1/g, '&'); + return res.redirect(back); + } +}); + +module.exports = subscriptionRoutes; diff --git a/routes/user.js b/routes/user.js new file mode 100644 index 0000000..4abd6cf --- /dev/null +++ b/routes/user.js @@ -0,0 +1,459 @@ +const config = require('../config'); +const { redis, fetch } = require('../app'); +const userRoutes = require('express').Router(); + +const processUser = require('../inc/processJsonUser.js')(); +const processPost = require('../inc/processJsonPost.js')(); +const processAbout = require('../inc/processSubredditAbout.js')(); +const tedditApiUser = require('../inc/teddit_api/handleUser.js')(); +const processSearches = require('../inc/processSearchResults.js')(); +const processSubreddit = require('../inc/processJsonSubreddit.js')(); +const tedditApiSubreddit = require('../inc/teddit_api/handleSubreddit.js')(); +const processMoreComments = require('../inc/processMoreComments.js')(); +const processSubredditsExplore = + require('../inc/processSubredditsExplore.js')(); + +userRoutes.get('/user/:user/:kind?', (req, res, next) => { + let kind = ''; + if (req.params.kind) kind = `/${req.params.kind}`; + let q = ''; + if (req.query.sort) q += `?sort=${req.query.sort}&`; + if (req.query.t) q += `t=${req.query.t}`; + + res.redirect(`/u/${req.params.user}${kind}${q}`); +}); + +userRoutes.get('/u/:user/:kind?', (req, res, next) => { + let user = req.params.user; + let after = req.query.after; + let before = req.query.before; + let post_type = req.params.kind; + let kind = post_type; + let user_data = {}; + 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; + + if (!after) { + after = ''; + } + if (!before) { + before = ''; + } + let d = `&after=${after}`; + if (before) { + d = `&before=${before}`; + } + + post_type = `/${post_type}`; + switch (post_type) { + case '/comments': + kind = 't1'; + break; + case '/submitted': + kind = 't3'; + break; + default: + post_type = ''; + kind = ''; + } + + let sortby = req.query.sort; + let past = req.query.t; + + if (!sortby) { + sortby = 'new'; + } + + if (!['hot', 'new', 'controversial', 'top'].includes(sortby)) { + console.log(`Got invalid sort.`, req.originalUrl); + return res.redirect(`/u/${user}`); + } + + 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(`/u/${user}/${sortby}`); + } + } else { + past = ''; + } + } else { + if (sortby === 'controversial' || sortby === 'top') { + past = 'all'; + } else { + past = ''; + } + } + + let key = `${user}:${after}:${before}:sort:${sortby}:past:${past}:post_type:${post_type}:raw_json:${raw_json}`; + redis.get(key, (error, json) => { + if (error) { + console.error(`Error getting the user ${key} key from redis.`, error); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } + if (json) { + console.log(`Got user ${user} key from redis.`); + (async () => { + if (api_req) { + return handleTedditApiUser( + json, + req, + res, + 'redis', + api_type, + api_target, + user, + after, + before + ); + } else { + let processed_json = await processJsonUser( + json, + false, + after, + before, + req.cookies, + kind, + post_type + ); + return res.render('user', { + data: processed_json, + sortby: sortby, + past: past, + user_preferences: req.cookies, + }); + } + })(); + } else { + let url = ''; + if (config.use_reddit_oauth) + url = `https://oauth.reddit.com/user/${user}/about?raw_json=${raw_json}`; + else + url = `https://reddit.com/user/${user}/about.json?raw_json=${raw_json}`; + fetch(encodeURI(url), redditApiGETHeaders()) + .then((result) => { + if (result.status === 200) { + result.json().then((json) => { + user_data.about = json; + let url = ''; + if (config.use_reddit_oauth) { + let endpoint = '/overview'; + if (post_type !== '') endpoint = post_type; + url = `https://oauth.reddit.com/user/${user}${post_type}?limit=26${d}&sort=${sortby}&t=${past}&raw_json=${raw_json}`; + } else { + url = `https://reddit.com/user/${user}${post_type}.json?limit=26${d}&sort=${sortby}&t=${past}&raw_json=${raw_json}`; + } + fetch(encodeURI(url), redditApiGETHeaders()) + .then((result) => { + if (result.status === 200) { + result.json().then((json) => { + user_data.overview = json; + redis.setex( + key, + config.setexs.user, + JSON.stringify(user_data), + (error) => { + if (error) { + console.error( + `Error setting the user ${key} key to redis.`, + error + ); + return res.render('index', { + post: null, + user_preferences: req.cookies, + }); + } else { + (async () => { + if (api_req) { + return handleTedditApiUser( + user_data, + req, + res, + 'online', + api_type, + api_target, + user, + after, + before + ); + } else { + let processed_json = await processJsonUser( + user_data, + true, + after, + before, + req.cookies, + kind, + post_type + ); + return res.render('user', { + data: processed_json, + 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 overview JSON file from reddit.com/u/${user}`, + error + ); + return res.render('index', { + json: null, + http_status_code: result.status, + user_preferences: req.cookies, + }); + }); + }); + } else { + if (result.status === 404) { + console.log('404 – User 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 about JSON file from reddit.com/u/${user}`, + error + ); + }); + } + }); +}); + +userRoutes.get('/user/:user/m/:custom_feed', (req, res, next) => { + res.redirect(`/u/${req.params.user}/m/${req.params.custom_feed}`); +}); + +userRoutes.get('/u/:user/m/:custom_feed/:sort?', (req, res, next) => { + let user = req.params.user; + let custom_feed = req.params.custom_feed; + let subreddit = `u/${user}/m/${custom_feed}`; + 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 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(`/u/${user}`); + } + + 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(`/u/${user}/${sortby}`); + } + } else { + past = undefined; + } + } else { + if (sortby === 'controversial' || sortby === 'top') { + past = 'day'; + } + } + + let key = `${user.toLowerCase()}:m:${custom_feed}:${after}:${before}:sort:${sortby}:past:${past}`; + redis.get(key, (error, json) => { + if (error) { + console.error( + `Error getting the ${user} custom_feed key from redis.`, + error + ); + return res.render('index', { + json: null, + user_preferences: req.cookies, + }); + } + if (json) { + console.log(`Got /u/${user} custom_feed 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 + ); + if (!processed_json.error) { + return res.render('subreddit', { + json: processed_json, + subreddit: '../' + subreddit, + subreddit_about: null, + 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/${subreddit}/${sortby}?api_type=json&count=25&g=GLOBAL&t=${past}${d}`; + else + url = `https://reddit.com/${subreddit}/${sortby}.json?api_type=json&count=25&g=GLOBAL&t=${past}${d}`; + 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 + ); + return res.render('subreddit', { + json: processed_json, + subreddit: '../' + subreddit, + subreddit_about: null, + 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/${subreddit}.`, + error + ); + }); + } + }); +}); + +module.exports = userRoutes;