diff --git a/config.js.template b/config.js.template index b9cd00e..dcab1cf 100644 --- a/config.js.template +++ b/config.js.template @@ -2,6 +2,7 @@ const config = { domain: process.env.DOMAIN || '127.0.0.1', // Or for example 'teddit.net' reddit_app_id: process.env.REDDIT_APP_ID || 'H6-HjZ5pUPjaFQ', // You should obtain your own Reddit app ID. For testing purposes it's okay to use this project's default app ID. Create your Reddit app here: https://old.reddit.com/prefs/apps/. Make sure to create an "installed app" type of app. cert_dir: process.env.CERT_DIR || '', // For example '/home/teddit/letsencrypt/live/teddit.net', if you are using https. No trailing slash. + flairs_enabled: process.env.FLAIRS_ENABLED !== "true" || true, // Enables the rendering of user and link flairs on teddit api_enabled: process.env.API_ENABLED !== "true" || true, // Teddit API feature. Might increase loads significantly on your instance. video_enabled: process.env.VIDEO_ENABLED !== "true" || true, redis_enabled: process.env.REDIS_ENABLED !== "true" || true, // If disabled, does not cache Reddit API calls @@ -34,7 +35,7 @@ const config = { shorts: 60 * 60 * 24 * 31 }, post_comments_sort: 'confidence', // "confidence" is the default sorting in Reddit. Must be one of: confidence, top, new, controversial, old, random, qa, live. - valid_media_domains: ['preview.redd.it', 'external-preview.redd.it', 'i.redd.it', 'v.redd.it', 'a.thumbs.redditmedia.com', 'b.thumbs.redditmedia.com', 'thumbs.gfycat.com', 'i.ytimg.com'], + valid_media_domains: ['preview.redd.it', 'external-preview.redd.it', 'i.redd.it', 'v.redd.it', 'a.thumbs.redditmedia.com', 'b.thumbs.redditmedia.com', 'emoji.redditmedia.com', 'thumbs.gfycat.com', 'i.ytimg.com'], reddit_api_error_text: `Seems like your instance is either blocked (e.g. due to API rate limiting), reddit is currently down, or your API key is expired and not renewd properly. This can also happen for other reasons.` }; diff --git a/dist/css/styles.css b/dist/css/styles.css index 043de90..7bccea4 100644 --- a/dist/css/styles.css +++ b/dist/css/styles.css @@ -172,11 +172,6 @@ body.dark #search form input[type="text"] { background: #0f0f0f; color: white; } -body.dark #links .link .entry .title span.postflair, -body.dark #post .info .title span.postflair { - color: #eaeaea; - background-color: #404040; -} a { color: var(--linkcolor); text-decoration: none; @@ -402,16 +397,6 @@ input[type="submit"]:hover, cursor: pointer; text-decoration: none; } -#links .link .entry .title span.postflair, -#post .info .title span.postflair { - display: inline-block; - border-radius: 4px; - color: #404040; - background-color: #e8e8e8; - font-size: x-small; - margin-left: 10px; - padding: 0 2px; -} /* SUBREDDIT LINKS */ #links { float: left; @@ -994,6 +979,40 @@ input[type="submit"]:hover, #user .entries .entry a.context { margin-right: 10px; } +/* FLAIR */ +.flair, +#links .link .entry .title span.flair, +#post .info .title span.flair { + display: inline-block; + border-radius: 4px; + color: #404040; + background-color: #e8e8e8; + font-size: x-small; + margin-left: 10px; + padding: 0 2px; +} +body.dark .flair { + color: #eaeaea !important; + background-color: #404040 !important; +} +#post .comments .flair, +#user .comment .meta .flair { + margin-left: 0 !important; +} +#links .link .entry .meta p.submitted .flair, +#user .comment .meta .flair, +#user .entries p.submitted .flair { + margin-right: 4px; +} +.flair .emoji { + background-position: center; + background-repeat: no-repeat; + background-size: contain; + display: inline-block; + height: 16px; + width: 16px; + vertical-align: middle; +} /* SIDEBAR */ #sidebar { float: left; diff --git a/inc/commons.js b/inc/commons.js index c04c178..2cea66e 100644 --- a/inc/commons.js +++ b/inc/commons.js @@ -1,4 +1,4 @@ -module.exports = function(request, fs) { +module.exports = function(request, fs) { const config = require('../config') this.downloadFile = (url) => { return new Promise(resolve => { @@ -51,7 +51,7 @@ module.exports = function(request, fs) { if(video_exts.includes(file_ext) || !image_exts.includes(file_ext)) url = url.replace(u.host, `${config.domain}/vids`) + '.mp4' } - + } catch(e) { } return url } @@ -59,7 +59,7 @@ module.exports = function(request, fs) { this.kFormatter = (num) => { return Math.abs(num) > 999 ? Math.sign(num)*((Math.abs(num)/1000).toFixed(1)) + 'k' : Math.sign(num)*Math.abs(num) } - + this.timeDifference = (time) => { time = parseInt(time) * 1000 let ms_per_minute = 60 * 1000 @@ -121,7 +121,7 @@ module.exports = function(request, fs) { return r } } - + this.toUTCString = (time) => { let d = new Date(); d.setTime(time*1000); @@ -165,7 +165,7 @@ module.exports = function(request, fs) { }) }) } - + this.isGif = (url) => { try { url = new URL(url) @@ -197,4 +197,61 @@ module.exports = function(request, fs) { return '' } } + + this.formatLinkFlair = async (post) => { + if (!config.flairs_enabled) { + return '' + } + + const wrap = (inner) => `${inner}` + + if (post.link_flair_text === null) + return '' + + if (post.link_flair_type === 'text') + return wrap(post.link_flair_text) + + if (post.link_flair_type === 'richtext') { + let flair = '' + for (let fragment of post.link_flair_richtext) { + if (fragment.e === 'text') + flair += fragment.t + else if (fragment.e === 'emoji') + flair += `` + } + return wrap(flair) + } + + return '' + } + + this.formatUserFlair = async (post) => { + if (!config.flairs_enabled) { + return '' + } + + // Generate the entire HTML here for consistency in both pug and HTML + const wrap = (inner) => `${inner}` + + if (post.author_flair_text === null) + return '' + + if (post.author_flair_type === 'text') + return wrap(post.author_flair_text) + + if (post.author_flair_type === 'richtext') { + let flair = '' + for (let fragment of post.author_flair_richtext) { + // `e` seems to mean `type` + if (fragment.e === 'text') + flair += fragment.t // `t` is the text + else if (fragment.e === 'emoji') + flair += `` // `u` is the emoji URL + } + return wrap(flair) + } + + return '' + } + } diff --git a/inc/compilePostComments.js b/inc/compilePostComments.js index 3abf2cd..dcab27d 100644 --- a/inc/compilePostComments.js +++ b/inc/compilePostComments.js @@ -18,7 +18,7 @@ module.exports = function() { let moderator = false let submitter = false let edited_span = '' - + if(post_author === comments.author) { classlist.push('submitter') submitter_link = `[S]` @@ -48,6 +48,7 @@ module.exports = function() {

${commentAuthor(comments, classlist, submitter && submitter_link, moderator && moderator_badge)}

+

${comments.user_flair}

${ups}

${timeDifference(comments.created)}${edited_span} @@ -104,7 +105,7 @@ module.exports = function() { let submitter = false let ups = '' let edited_span = '' - + if(post_author === comment.author) { classlist.push('submitter') submitter_link = `[S]` @@ -134,6 +135,7 @@ module.exports = function() {

${commentAuthor(comment, classlist, submitter && submitter_link, moderator && moderator_badge)}

+

${comment.user_flair}

${ups}

${timeDifference(comment.created)}${edited_span} @@ -185,7 +187,7 @@ module.exports = function() { comments_html += `

` } next_comment_parent_id = null - + resolve(comments_html) })() }) diff --git a/inc/downloadAndSave.js b/inc/downloadAndSave.js index fdd83e3..d0224db 100644 --- a/inc/downloadAndSave.js +++ b/inc/downloadAndSave.js @@ -1,12 +1,12 @@ -module.exports = function(tools) { +module.exports = function(tools) { const config = require('../config') const {spawn} = require('child_process') const fs = require('fs') this.downloadAndSave = (url, file_prefix = '', gifmp4, isYouTubeThumbnail) => { - /** + /** * This function downloads media (video or image) to disk. * Returns a localized URL - * + * * For example for images: * https://external-preview.redd.it/DiaeK_j5fqpBqbatvo7GZzbHNJY2oxEym93B_3.jpg * => @@ -32,21 +32,23 @@ module.exports = function(tools) { if(gifmp4) { file_ext = 'mp4' } else { - if(!pathname.includes('.')) { - /** + if (file_prefix === 'flair_') { + // Flair emojis end in the name without a file extension + file_ext = 'png' + } else if(!pathname.includes('.')) { /** * Sometimes reddit API returns video without extension, like * "DASH_480" and not "DASH_480.mp4". */ file_ext = 'mp4' has_extension = false - } else { + } else { file_ext = pathname.substring(pathname.lastIndexOf('.') + 1) } } - + if(file_prefix === 'thumb_') dir = 'thumbs/' - if(file_prefix === 'flair') + if(file_prefix === 'flair_') dir = 'flairs/' if(valid_video_extensions.includes(file_ext) || gifmp4) { @@ -130,7 +132,14 @@ module.exports = function(tools) { if(temp_url.searchParams.get('width')) { width = temp_url.searchParams.get('width') } - filename = `${file_prefix}w:${temp_url.searchParams.get('width')}_${temp_url.pathname.split('/').slice(-1)}` + if(file_prefix === 'flair_') { + // Flair emojis have a full path of `UUID/name`, + // so we need to incorporate the UUID to avoid duplicates + // since names alone are not unique across all of reddit + filename = `${pathname.slice(1).replace('/', '_')}.png` // Only first replacement is fine + } else { + filename = `${file_prefix}w:${temp_url.searchParams.get('width')}_${temp_url.pathname.split('/').slice(-1)}` + } } path = `./dist/pics/${dir}${filename}` if(!fs.existsSync(path)) { diff --git a/inc/processJsonPost.js b/inc/processJsonPost.js index a40485d..467009f 100644 --- a/inc/processJsonPost.js +++ b/inc/processJsonPost.js @@ -7,7 +7,7 @@ module.exports = function(fetch) { if(!parsed) { json = JSON.parse(json) } - + let post = json[0].data.children[0].data let post_id = post.name let comments = json[1].data.children @@ -32,14 +32,16 @@ module.exports = function(fetch) { media: null, images: null, crosspost: false, - selftext: unescape(post.selftext_html) + selftext: unescape(post.selftext_html), + link_flair: await formatLinkFlair(post), + user_flair: await formatUserFlair(post) } let validEmbedDomains = ['gfycat.com', 'youtube.com'] let has_gif = false let gif_to_mp4 = null let reddit_video = null - + if(post.preview) { if(post.preview.reddit_video_preview) { if(post.preview.reddit_video_preview.is_gif) { @@ -66,7 +68,7 @@ module.exports = function(fetch) { } obj = await processPostMedia(obj, post, post.media, has_gif, reddit_video, gif_to_mp4) - + if(post.crosspost_parent_list) { post.crosspost = post.crosspost_parent_list[0] } @@ -84,7 +86,8 @@ module.exports = function(fetch) { ups: post.crosspost.ups, selftext: unescape(post.selftext_html), selftext_crosspost: unescape(post.crosspost.selftext_html), - is_crosspost: true + is_crosspost: true, + user_flair: await formatUserFlair(post) } } @@ -93,7 +96,7 @@ module.exports = function(fetch) { source: await downloadAndSave(post.preview.images[0].source.url) } } - + if(obj.media) { if(obj.media.source === 'external') { if(post.preview) { @@ -103,7 +106,7 @@ module.exports = function(fetch) { } } } - + if(post.gallery_data) { obj.gallery = true obj.gallery_items = [] @@ -120,13 +123,13 @@ module.exports = function(fetch) { } } } - + let comms = [] for(var i = 0; i < comments.length; i++) { let comment = comments[i].data let kind = comments[i].kind let obj = {} - + if(kind !== 'more') { obj = { author: comment.author, @@ -143,7 +146,8 @@ module.exports = function(fetch) { score_hidden: comment.score_hidden, edited: comment.edited, replies: [], - depth: 0 + depth: 0, + user_flair: await formatUserFlair(comment) } } else { obj = { @@ -155,26 +159,26 @@ module.exports = function(fetch) { children: [] } } - + if(comment.replies && kind !== 'more') { if(comment.replies.data) { if(comment.replies.data.children.length > 0) { - obj.replies = processReplies(comment.replies.data.children, post_id, 1) + obj.replies = await processReplies(comment.replies.data.children, post_id, 1) } } } - + if(comment.children) { for(var j = 0; j < comment.children.length; j++) { obj.children.push(comment.children[j]) } } - + comms.push(obj) } - + obj.comments = comms - + resolve(obj) })() }) @@ -198,7 +202,7 @@ module.exports = function(fetch) { return { post_data: post_data, comments: comments_html } } - this.processReplies = (data, post_id, depth) => { + this.processReplies = async (data, post_id, depth) => { let return_replies = [] for(var i = 0; i < data.length; i++) { let kind = data[i].kind @@ -220,7 +224,8 @@ module.exports = function(fetch) { score_hidden: reply.score_hidden, edited: reply.edited, replies: [], - depth: depth + depth: depth, + user_flair: await formatUserFlair(reply) } } else { obj = { @@ -233,13 +238,13 @@ module.exports = function(fetch) { depth: depth } } - + if(reply.replies && kind !== 'more') { if(reply.replies.data.children.length) { for(var j = 0; j < reply.replies.data.children.length; j++) { let comment = reply.replies.data.children[j].data let objct = {} - + if(comment.author && comment.body_html) { objct = { author: comment.author, @@ -255,7 +260,8 @@ module.exports = function(fetch) { distinguished: comment.distinguished, distinguished: comment.edited, replies: [], - depth: depth + 1 + depth: depth + 1, + user_flair: await formatUserFlair(comment) } } else { objct = { @@ -273,11 +279,11 @@ module.exports = function(fetch) { } } } - + if(comment.replies) { if(comment.replies.data) { if(comment.replies.data.children.length > 0) { - objct.replies = processReplies(comment.replies.data.children, post_id, depth ) + objct.replies = await processReplies(comment.replies.data.children, post_id, depth ) } } } @@ -286,13 +292,13 @@ module.exports = function(fetch) { } } } - + if(reply.children) { for(var j = 0; j < reply.children.length; j++) { obj.children.push(reply.children[j]) } } - + return_replies.push(obj) } return return_replies diff --git a/inc/processJsonSubreddit.js b/inc/processJsonSubreddit.js index 27535b2..1b4c04c 100644 --- a/inc/processJsonSubreddit.js +++ b/inc/processJsonSubreddit.js @@ -11,7 +11,7 @@ module.exports = function() { } else { let before = json.data.before let after = json.data.after - + let ret = { info: { before: before, @@ -19,9 +19,9 @@ module.exports = function() { }, links: [] } - + let children_len = json.data.children.length - + for(var i = 0; i < children_len; i++) { let data = json.data.children[i].data let images = null @@ -39,7 +39,7 @@ module.exports = function() { is_self_link = true } } - + if(data.preview && data.thumbnail !== 'self') { if(!data.url.startsWith('/r/') && isGif(data.url)) { images = { @@ -73,7 +73,9 @@ module.exports = function() { url: data.url, stickied: data.stickied, is_self_link: is_self_link, - subreddit_front: subreddit_front + subreddit_front: subreddit_front, + link_flair: await formatLinkFlair(data), + user_flair: await formatUserFlair(data) } ret.links.push(obj) } diff --git a/inc/processJsonUser.js b/inc/processJsonUser.js index f081138..fe86322 100644 --- a/inc/processJsonUser.js +++ b/inc/processJsonUser.js @@ -21,7 +21,7 @@ module.exports = function() { if(!after && !before) { user_front = true } - + if(json.overview.data.children) { if(json.overview.data.children[posts_limit - 1]) { after = json.overview.data.children[posts_limit - 1].data.name @@ -30,16 +30,16 @@ module.exports = function() { before = json.overview.data.children[0].data.name } } - + for(var i = 0; i < posts_limit; i++) { let post = json.overview.data.children[i].data let thumbnail = 'self' let type = json.overview.data.children[i].kind let obj - + let post_id = post.permalink.split('/').slice(-2)[0] + '/' let url = post.permalink.replace(post_id, '') - + if(type === 't3') { let duration = null if(post.media) { @@ -62,7 +62,8 @@ module.exports = function() { edited: post.edited, selftext_html: unescape(post.selftext_html), num_comments: post.num_comments, - permalink: post.permalink + permalink: post.permalink, + user_flair: await formatUserFlair(post) } } if(type === 't1') { @@ -79,7 +80,8 @@ module.exports = function() { num_comments: post.num_comments, permalink: post.permalink, link_author: post.link_author, - link_title: post.link_title + link_title: post.link_title, + user_flair: await formatUserFlair(post) } } posts.push(obj) @@ -98,7 +100,7 @@ module.exports = function() { after: after, posts: posts } - + resolve(obj) })() }) diff --git a/views/post.pug b/views/post.pug index 3eea48b..96c0114 100644 --- a/views/post.pug +++ b/views/post.pug @@ -12,7 +12,7 @@ html #post header div - p subreddit: + p subreddit: a(href="/r/" + subreddit + "") p /r/#{subreddit} .info @@ -23,8 +23,7 @@ html .title a(href="" + post.url + "") h2 #{cleanTitle(post.title)} - if post.link_flair_text - span(class="postflair") #{post.link_flair_text} + != post.link_flair span(class="domain") (#{post.domain}) p.submitted span(title="" + toUTCString(post.created) + "") submitted #{timeDifference(post.created)} by @@ -33,6 +32,7 @@ html else a(href="/u/" + post.author + "") | #{post.author} + != post.user_flair if post.crosspost.is_crosspost === true .crosspost .title @@ -52,6 +52,7 @@ html else a(href="/u/" + post.crosspost.author + "") | #{post.crosspost.author} + != post.user_flair p.to to a(href="/r/" + post.crosspost.subreddit + "") | #{post.crosspost.subreddit} @@ -128,8 +129,8 @@ html source(src="" + post.media.source + "", type="video/mp4") | Your browser does not support the video element. a(href="" + post.media.source + "") [media] - if post.selftext - div.usertext-body !{post.selftext} + if post.selftext + div.usertext-body !{post.selftext} if viewing_comment .infobar p you are viewing a single comment's thread. diff --git a/views/subreddit.pug b/views/subreddit.pug index 31fa078..bbf90a0 100644 --- a/views/subreddit.pug +++ b/views/subreddit.pug @@ -84,14 +84,12 @@ html if link.is_self_link a(href="" + link.permalink + "") h2(class="" + (link.stickied ? 'green' : '') + "") #{cleanTitle(link.title)} - if link.link_flair_text - span(class="postflair") #{link.link_flair_text} + != link.link_flair span (#{link.domain}) else a(href="" + link.url + "") h2(class="" + (link.stickied ? 'green' : '') + "") #{cleanTitle(link.title)} - if link.link_flair_text - span(class="postflair") #{link.link_flair_text} + != link.link_flair span (#{link.domain}) .meta p.submitted submitted @@ -101,6 +99,7 @@ html else a(href="/u/" + link.author + "") | #{link.author} + != link.user_flair p.to to a(href="/r/" + link.subreddit + "") | #{link.subreddit} diff --git a/views/user.pug b/views/user.pug index 8344c37..d1766b3 100644 --- a/views/user.pug +++ b/views/user.pug @@ -80,9 +80,11 @@ html .title a(href="" + post.permalink + "") #{cleanTitle(post.title)} .meta - p.submitted(title="" + toUTCString(post.created) + "") submitted #{timeDifference(post.created)} by + p.submitted(title="" + toUTCString(post.created) + "") submitted #{timeDifference(post.created)} + | by a(href="/u/" + data.username + "") #{data.username} | to + != post.user_flair a(href="/r/" + post.subreddit + "", class="subreddit") #{post.subreddit} a.comments(href="" + post.permalink + "") #{post.num_comments} comments if post.type === 't1' @@ -98,7 +100,7 @@ html a(href="/u/" + post.link_author + "") #{post.link_author} .subreddit p in - a(href="/r/" + post.subreddit + "") #{post.subreddit} + a(href="/r/" + post.subreddit + "") #{post.subreddit} .comment details(open="") summary @@ -108,10 +110,12 @@ html .meta p.author a(href="/u/" + data.username + "") #{data.username} + p + != post.user_flair p.ups #{post.ups} points p.created(title="" + toUTCString(post.created) + "") #{timeDifference(post.created)} .body - div !{post.body_html} + div !{post.body_html} a.context(href="" + post.permalink + "?context=10") context a.comments.t1(href="" + post.url + "") full comments (#{post.num_comments}) if data.before || data.after @@ -127,28 +131,3 @@ html br p(title="" + toUTCString(data.created) + "") account created: #{toDateString(data.created)} p verified: #{(data.verified) ? "yes" : "no" } - - - - - - - - - - - - - - - - - - - - - - - - -