Merge pull request 'Implement full flair functionality' (#72) from StevenNMeza/teddit:flairs into main

Reviewed-on: https://codeberg.org/teddit/teddit/pulls/72
This commit is contained in:
teddit 2020-12-23 11:26:17 +01:00
commit 24b686fe1e
11 changed files with 184 additions and 107 deletions

View File

@ -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.`
};

49
dist/css/styles.css vendored
View File

@ -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;

View File

@ -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) => `<span class="flair">${inner}</span>`
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 += `<span class="emoji" style="background-image: url(${await downloadAndSave(fragment.u, 'flair_')})"></span>`
}
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) => `<span class="flair">${inner}</span>`
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 += `<span class="emoji" style="background-image: url(${await downloadAndSave(fragment.u, 'flair_')})"></span>` // `u` is the emoji URL
}
return wrap(flair)
}
return ''
}
}

View File

@ -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 = `<a href="${post_url}" title="submitter">[S]</a>`
@ -48,6 +48,7 @@ module.exports = function() {
</summary>
<div class="meta">
<p class="author">${commentAuthor(comments, classlist, submitter && submitter_link, moderator && moderator_badge)}</p>
<p>${comments.user_flair}</p>
<p class="ups">${ups}</p>
<p class="created" title="${toUTCString(comments.created)}">
<a href="${comments.permalink}">${timeDifference(comments.created)}${edited_span}</a>
@ -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 = `<a href="${post_url}" title="submitter">[S]</a>`
@ -134,6 +135,7 @@ module.exports = function() {
</summary>
<div class="meta">
<p class="author">${commentAuthor(comment, classlist, submitter && submitter_link, moderator && moderator_badge)}</p>
<p>${comment.user_flair}</p>
<p class="ups">${ups}</p>
<p class="created" title="${toUTCString(comment.created)}">
<a href="${comment.permalink}">${timeDifference(comment.created)}${edited_span}</a>
@ -185,7 +187,7 @@ module.exports = function() {
comments_html += `</details></div>`
}
next_comment_parent_id = null
resolve(comments_html)
})()
})

View File

@ -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)) {

View File

@ -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

View File

@ -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)
}

View File

@ -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)
})()
})

View File

@ -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.

View File

@ -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}

View File

@ -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" }