mirror of
https://github.com/gurnec/removeddit.git
synced 2026-03-11 08:54:27 +00:00
Requests to pushshift is now client-side. More feedback to users when loading.
This commit is contained in:
parent
9bb9c732e2
commit
3149e78fb6
7 changed files with 345 additions and 299 deletions
78
server.go
78
server.go
|
|
@ -20,11 +20,9 @@ import (
|
|||
|
||||
// consts
|
||||
const (
|
||||
userAgent = "Javascript:" + appName + ":" + version + "s (by /u/" + userName + ")"
|
||||
tokenURL = "https://www.reddit.com/api/v1/access_token"
|
||||
commentIDsURL = "https://api.pushshift.io/reddit/submission/comment_ids/"
|
||||
commentsURL = "https://api.pushshift.io/reddit/comment/search?ids="
|
||||
tmplFolder = "templates/"
|
||||
userAgent = "Javascript:" + appName + ":" + version + "s (by /u/" + userName + ")"
|
||||
tokenURL = "https://www.reddit.com/api/v1/access_token"
|
||||
tmplFolder = "templates/"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -49,25 +47,6 @@ type tokenResponse struct {
|
|||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type pushshiftIDsAPI struct {
|
||||
Data []string `json:"data"`
|
||||
}
|
||||
|
||||
type pushshiftCommentsAPI struct {
|
||||
Data []pushshiftComment `json:"data"`
|
||||
}
|
||||
|
||||
type pushshiftComment struct {
|
||||
Author string `json:"author"`
|
||||
AuthorFlairText string `json:"author_flair_text"`
|
||||
Body string `json:"body"`
|
||||
CreatedUTC int32 `json:"created_utc"`
|
||||
ID string `json:"id"`
|
||||
IsSubmitter bool `json:"is_submitter"`
|
||||
ParentID string `json:"parent_id"`
|
||||
Score int32 `json:"score"`
|
||||
}
|
||||
|
||||
type threadPageData struct {
|
||||
Token string
|
||||
Subreddit string
|
||||
|
|
@ -85,7 +64,6 @@ func main() {
|
|||
|
||||
http.HandleFunc("/", pageHandler(mainHandler))
|
||||
http.HandleFunc("/r/", pageHandler(threadHandler))
|
||||
http.HandleFunc("/comments/", pageHandler(commentHandler))
|
||||
|
||||
// Run locally for debugging/testing
|
||||
if debugMode {
|
||||
|
|
@ -173,65 +151,21 @@ func threadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
resp, _ := client.Get(commentIDsURL + pathParts[4])
|
||||
if resp.StatusCode != 200 {
|
||||
handleError(w, "Trouble getting removed comments from pushshift")
|
||||
return
|
||||
}
|
||||
|
||||
token, err := getAPIToken()
|
||||
if err != nil {
|
||||
handleError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
var dataStructure pushshiftIDsAPI
|
||||
json.Unmarshal(body, &dataStructure)
|
||||
|
||||
data := &threadPageData{
|
||||
Token: token,
|
||||
Subreddit: pathParts[2],
|
||||
ThreadID: pathParts[4],
|
||||
CommentIDs: dataStructure.Data,
|
||||
Token: token,
|
||||
Subreddit: pathParts[2],
|
||||
ThreadID: pathParts[4],
|
||||
}
|
||||
|
||||
renderTemplate(w, "thread", data)
|
||||
}
|
||||
|
||||
func commentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
commentIDs := r.FormValue("c")
|
||||
if commentIDs == "" {
|
||||
handleError(w, "Not enough arguments provided")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := client.Get(commentsURL + commentIDs)
|
||||
|
||||
if err != nil {
|
||||
handleError(w, "Trouble getting removed comments from pushshift1")
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
var c pushshiftCommentsAPI
|
||||
err = json.Unmarshal(body, &c)
|
||||
|
||||
if err != nil {
|
||||
handleError(w, "Trouble parsing removed comments from pushshift2")
|
||||
return
|
||||
}
|
||||
|
||||
jsonString, err := json.Marshal(c)
|
||||
|
||||
if err != nil {
|
||||
handleError(w, "Trouble parsing removed comments from pushshift3")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, string(jsonString))
|
||||
}
|
||||
|
||||
func getAPIToken() (string, error) {
|
||||
req, _ := http.NewRequest("POST", tokenURL, strings.NewReader(tokenData.Encode()))
|
||||
req.Header.Add("User-Agent", userAgent)
|
||||
|
|
|
|||
BIN
static/done.png
Normal file
BIN
static/done.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
static/loading.gif
Normal file
BIN
static/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
494
static/script.js
494
static/script.js
|
|
@ -1,202 +1,235 @@
|
|||
"use strict";
|
||||
|
||||
const markdown = SnuOwnd.getParser()
|
||||
const htmlParser = new DOMParser();
|
||||
|
||||
const thread_url = `https://oauth.reddit.com/r/${subreddit}/comments/${thread_id}`
|
||||
const morechildren_url = `https://oauth.reddit.com/api/morechildren?link_id=t3_${thread_id}&children=`
|
||||
const single_comment_url = `https://oauth.reddit.com/r/${subreddit}/api/info/?id=t1_`
|
||||
const removed_comments_url = "/comments/?c="
|
||||
const redditThreadURL = `https://oauth.reddit.com/r/${subreddit}/comments/${threadID}`
|
||||
const redditMorechildrenURL = `https://oauth.reddit.com/api/morechildren?link_id=t3_${threadID}&children=`
|
||||
const redditSingleCommentURL = `https://oauth.reddit.com/r/${subreddit}/api/info/?id=t1_`
|
||||
const pushshiftIDsURL = `https://api.pushshift.io/reddit/submission/comment_ids/${threadID}`
|
||||
const pushshiftCommentsURL = "https://api.pushshift.io/reddit/comment/search?ids="
|
||||
|
||||
const main_div = document.getElementById("main")
|
||||
const loading_comments = document.getElementById("loading-comments")
|
||||
const mainDiv = document.getElementById("main")
|
||||
const loadingText = document.getElementById("loading-text")
|
||||
const loadingImage = document.getElementById("loading-image")
|
||||
const loadingImageSrc = "/static/loading.gif"
|
||||
const doneImageSrc = "/static/done.png"
|
||||
|
||||
const comment_ids = []
|
||||
const morechildren_ids = []
|
||||
const continuethisthread_ids = []
|
||||
const max_ids_per_call = 100
|
||||
const comment_lookup = new Map()
|
||||
const comments_to_create = []
|
||||
let total_comments
|
||||
// I actually haven't found a better way of doing this...
|
||||
// Imgur has images-links with no indication that they are actually images
|
||||
const imageHosts = ["i.redd.it", "flickr.com", "i.imgur.com", "imgur.com", "m.imgur.com"]
|
||||
|
||||
const reddit_init = {
|
||||
const commentIDs = []
|
||||
const morechildrenIDs = []
|
||||
const continuethisthreadIDs = []
|
||||
const maxIDsPerRequest = 100
|
||||
const commentLookup = new Map()
|
||||
const commentsToCreate = []
|
||||
|
||||
let totalComments
|
||||
|
||||
const redditInit = {
|
||||
headers: { "Authorization": "bearer " + token }
|
||||
}
|
||||
|
||||
load_page()
|
||||
loadPage()
|
||||
|
||||
async function load_page() {
|
||||
const json = await fetch(thread_url, reddit_init).then(thread => thread.json())
|
||||
async function loadPage() {
|
||||
loadingImage.src = loadingImageSrc
|
||||
setLoadingText("Loading thread...")
|
||||
|
||||
generate_thread(json)
|
||||
comment_ids.push(...get_comment_ids(json))
|
||||
// Get thread from reddit and all comment IDs (inc. removed ones) from pushshift
|
||||
const requests = [
|
||||
fetch(redditThreadURL, redditInit).then(json),
|
||||
fetch(pushshiftIDsURL).then(json)
|
||||
]
|
||||
|
||||
await get_morechildren_ids()
|
||||
|
||||
const smaller_threads = await Promise.all(continuethisthread_ids.map(id =>fetch(thread_url + "/_/" + id.split("_")[1], reddit_init)))
|
||||
const smaller_threads_json = await Promise.all(smaller_threads.map(x => x.json()))
|
||||
smaller_threads_json.forEach(thread => comment_ids.push(...get_comment_ids(thread)))
|
||||
// Bottleneck here is allways the reddit api
|
||||
const [thread, allCommentIDs] = await Promise.all(requests)
|
||||
|
||||
const removed_comments = await get_removed_comments()
|
||||
generate_comments(removed_comments)
|
||||
generateThread(thread)
|
||||
|
||||
// Get all the comments from the first request
|
||||
setLoadingText("Loading comments from reddit...")
|
||||
commentIDs.push(...getCommentIDs(thread))
|
||||
|
||||
// Recursivly extract all IDs from "show more"-comments
|
||||
await getMorechildrenIDs()
|
||||
|
||||
// Do same thing for "continue this thread"-comments
|
||||
const smallerThreads = await Promise.all(continuethisthreadIDs.map(id => fetch(redditThreadURL + "/_/" + id.split("_")[1], redditInit).then(json)))
|
||||
//const smallerThreadsJson = await Promise.all(smallerThreads.map(x => x.json()))
|
||||
smallerThreads.forEach(thread => commentIDs.push(...getCommentIDs(thread)))
|
||||
|
||||
setLoadingText("Getting removed comments...")
|
||||
const removedComments = await getRemovedComments(allCommentIDs.data)
|
||||
|
||||
setLoadingText("Generating comments...")
|
||||
generateComments(removedComments)
|
||||
|
||||
setLoadingText("")
|
||||
loadingImage.src = doneImageSrc
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// ----------------------- Functions for getting IDs ----------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
async function get_removed_comments() {
|
||||
const ids_diff = all_ids.filter(x => !comment_ids.includes(x))
|
||||
generate_comment_info(ids_diff.length)
|
||||
console.log("All ids :", all_ids)
|
||||
console.log("Comments ids (dup): ", comment_ids)
|
||||
console.log("With/without dups: ", comment_ids.length, new Set(comment_ids).size)
|
||||
console.log("Difference: ", ids_diff)
|
||||
console.log("Lookup size:", comment_lookup.size)
|
||||
return fetch_multiple(removed_comments_url, ids_diff, null, ["data"], true)
|
||||
function getCommentIDs(thread) {
|
||||
return flattenArray(thread[1].data.children.map(extractIDFromComment))
|
||||
}
|
||||
|
||||
async function get_morechildren_ids() {
|
||||
//const responses = await Promise.all(morechildren_ids.map(id_array => fetch(morechildren_url + id_array.join(), reddit_init)))
|
||||
const responses_arrays = await Promise.all(morechildren_ids.map(id_array => fetch_multiple(morechildren_url, id_array, reddit_init)))
|
||||
const responses = flatten_array(responses_arrays)
|
||||
morechildren_ids.length = 0
|
||||
responses.forEach(extract_morechildren_ids)
|
||||
|
||||
if(morechildren_ids.length !== 0) {
|
||||
await get_morechildren_ids()
|
||||
}
|
||||
}
|
||||
|
||||
function extract_morechildren_ids(comments) {
|
||||
comments.jquery[14][3][0].forEach(comment => {
|
||||
if(comment.kind == "more") {
|
||||
if(comment.data.id === "_") {
|
||||
console.log("From more, continue: " + comment.data.parent_id)
|
||||
continuethisthread_ids.push(comment.data.parent_id)
|
||||
} else {
|
||||
|
||||
const children = comment.data.children
|
||||
|
||||
if(children.length < comment.data.count) {
|
||||
morechildren_ids.push(children)
|
||||
}
|
||||
comment_ids.push(...children)
|
||||
}
|
||||
} else {
|
||||
comment_ids.push(comment.data.id)
|
||||
comment_lookup.set(comment.data.id, comment.data)
|
||||
}
|
||||
});
|
||||
}
|
||||
function get_comment_ids(data) {
|
||||
return flatten_array(data[1].data.children.map(extract_id_from_comment))
|
||||
}
|
||||
|
||||
function extract_id_from_comment(comment) {
|
||||
function extractIDFromComment(comment) {
|
||||
const data = comment.data
|
||||
// "Show more"-comments
|
||||
if(comment.kind == "more") {
|
||||
//console.log(data.id)
|
||||
//console.log(data.children)
|
||||
//console.log(data.children.length + "/" + data.count)
|
||||
//console.log("-----------------------")
|
||||
|
||||
if(data.id === "_") {
|
||||
if(comment.kind == "more") {
|
||||
if(data.id === "_") { // = "continue this thread"-comment
|
||||
console.log("From thread countinue: "+ data.parent_id)
|
||||
continuethisthread_ids.push(data.parent_id)
|
||||
return []
|
||||
} else if(data.children.length < data.count){
|
||||
morechildren_ids.push(data.children)
|
||||
// ???????????????????? maybe wrong, data.id? ytest this
|
||||
//return [] //-----------------------------------
|
||||
continuethisthreadIDs.push(data.parent_id)
|
||||
} else if(data.children.length < data.count){ // "Load more"-comment (that is missing some of its children)
|
||||
morechildrenIDs.push(data.children)
|
||||
}
|
||||
return data.children
|
||||
}
|
||||
// Normal comment
|
||||
let replies_ids = [data.id]
|
||||
const repliesIDs = [data.id]
|
||||
|
||||
if(data.replies) {
|
||||
data.replies.data.children.forEach(child => replies_ids.push(...extract_id_from_comment(child)))
|
||||
data.replies.data.children.forEach(child => repliesIDs.push(...extractIDFromComment(child)))
|
||||
delete data.replies
|
||||
}
|
||||
|
||||
comment_lookup.set(data.id, data)
|
||||
return replies_ids
|
||||
commentLookup.set(data.id, data)
|
||||
return repliesIDs
|
||||
}
|
||||
|
||||
async function getMorechildrenIDs() {
|
||||
//const responses = await Promise.all(morechildren_ids.map(id_array => fetch(morechildren_url + id_array.join(), reddit_init)))
|
||||
const responsesArrays = await Promise.all(morechildrenIDs.map(idArray => fetchMultiple(redditMorechildrenURL, idArray, redditInit)))
|
||||
const responses = flattenArray(responsesArrays)
|
||||
morechildrenIDs.length = 0
|
||||
responses.forEach(extractMorechildrenIDs)
|
||||
|
||||
if(morechildrenIDs.length !== 0) {
|
||||
await getMorechildrenIDs()
|
||||
}
|
||||
}
|
||||
|
||||
function extractMorechildrenIDs(comments) {
|
||||
comments.jquery[14][3][0].forEach(comment => {
|
||||
if(comment.kind == "more") {
|
||||
if(comment.data.id === "_") {
|
||||
console.log("From more, continue: " + comment.data.parent_id)
|
||||
continuethisthreadIDs.push(comment.data.parent_id)
|
||||
} else {
|
||||
const children = comment.data.children
|
||||
|
||||
if(children.length < comment.data.count) {
|
||||
morechildrenIDs.push(children)
|
||||
}
|
||||
commentIDs.push(...children)
|
||||
}
|
||||
} else {
|
||||
commentIDs.push(comment.data.id)
|
||||
commentLookup.set(comment.data.id, comment.data)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function getRemovedComments(allCommentIDs) {
|
||||
const idsDiff = allCommentIDs.filter(x => !commentIDs.includes(x))
|
||||
generateCommentInfo(idsDiff.length)
|
||||
console.log("All ids :", allCommentIDs)
|
||||
console.log("Comments ids (dup): ", commentIDs)
|
||||
console.log("With/without dups: ", commentIDs.length, new Set(commentIDs).size)
|
||||
console.log("Difference: ", idsDiff)
|
||||
console.log("Lookup size:", commentLookup.size)
|
||||
return fetchMultiple(pushshiftCommentsURL, idsDiff, null, ["data"], true)
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// ----------------------- Comment generating functions -------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
async function generate_comments(removed_comments) {
|
||||
const comment_section = document.createElement("div")
|
||||
comment_section.id = thread_id
|
||||
main_div.insertBefore(comment_section, loading_comments)
|
||||
async function generateComments(removedComments) {
|
||||
const commentSection = generateHTML(`<div id="${threadID}"></div>`)
|
||||
mainDiv.appendChild(commentSection)
|
||||
|
||||
removed_comments.forEach(comment => {
|
||||
removedComments.forEach(comment => {
|
||||
comment["removed"] = true
|
||||
comment_lookup.set(comment.id, comment)
|
||||
commentLookup.set(comment.id, comment)
|
||||
})
|
||||
|
||||
const comments_to_lookup = removed_comments.map(comment => comment.id)
|
||||
await get_comments_to_generate(comments_to_lookup)
|
||||
|
||||
comments_to_create.sort(function(a, b) {
|
||||
const a_score = comment_lookup.get(a).score
|
||||
const b_score = comment_lookup.get(b).score
|
||||
const commentsToLookup = removedComments.map(comment => comment.id)
|
||||
await getCommentsToGenerate(commentsToLookup)
|
||||
|
||||
commentsToCreate.sort(function(a, b) {
|
||||
const aScore = commentLookup.get(a).score
|
||||
const bScore = commentLookup.get(b).score
|
||||
|
||||
return a_score === b_score ? 0 : (a_score > b_score ? 1 : -1)
|
||||
return aScore === bScore ? 0 : (aScore > bScore ? 1 : -1)
|
||||
});
|
||||
|
||||
create_comments()
|
||||
createComments()
|
||||
}
|
||||
|
||||
async function get_comments_to_generate(comments_to_lookup) {
|
||||
const new_comments_to_lookup = []
|
||||
|
||||
comments_to_lookup.forEach(id => {
|
||||
const parent_id = comment_lookup.get(id).parent_id.split("_")[1]
|
||||
async function getCommentsToGenerate(commentsToLookup) {
|
||||
const newCommentsToLookup = []
|
||||
const commentsToFetch = []
|
||||
|
||||
if(parent_id === thread_id) {} // Has no parent (is parent of thread)
|
||||
else if(comments_to_create.includes(parent_id)) {} // Parent already exists, do nothing
|
||||
else if(new_comments_to_lookup.includes(parent_id)) {} // Parent already exists (this iteration)
|
||||
else if(comment_lookup.has(parent_id)) {
|
||||
new_comments_to_lookup.push(parent_id)
|
||||
commentsToLookup.forEach(id => {
|
||||
const parentID = commentLookup.get(id).parent_id.split("_")[1]
|
||||
|
||||
if(parentID === threadID) {} // Has no parent (is parent of thread)
|
||||
else if(commentsToCreate.includes(parentID)) {} // Parent already exists, do nothing
|
||||
else if(newCommentsToLookup.includes(parentID)) {} // Parent already exists (this iteration)
|
||||
else if(commentLookup.has(parentID)) {
|
||||
newCommentsToLookup.push(parentID)
|
||||
} else{
|
||||
console.error("Comment doesn't exists, HALP")
|
||||
console.log(id)
|
||||
console.log(parent_id)
|
||||
console.error("Comment doesn't exists, but lets try it anyway :D")
|
||||
console.log("ID: " + id)
|
||||
console.log("Parent: " + parent_id)
|
||||
|
||||
commentsToFetch.push(parentID)
|
||||
}
|
||||
|
||||
comments_to_create.push(id)
|
||||
commentsToCreate.push(id)
|
||||
})
|
||||
|
||||
if(new_comments_to_lookup.length !== 0) {
|
||||
await get_comments_to_generate(new_comments_to_lookup)
|
||||
if(commentsToFetch.length !== 0) {
|
||||
//await
|
||||
console.log("fuck it")
|
||||
}
|
||||
|
||||
if(newCommentsToLookup.length !== 0) {
|
||||
await getCommentsToGenerate(newCommentsToLookup)
|
||||
}
|
||||
}
|
||||
|
||||
function create_comments() {
|
||||
const created_comment_ids = [thread_id]
|
||||
let id, parent_id
|
||||
let did_something
|
||||
function createComments() {
|
||||
const createdCommentIDs = [threadID]
|
||||
let id, parentID
|
||||
let didSomething
|
||||
|
||||
while(comments_to_create.length > 0) {
|
||||
did_something = false
|
||||
while(commentsToCreate.length > 0) {
|
||||
didSomething = false
|
||||
|
||||
for(let i = comments_to_create.length -1; i >= 0; i--) {
|
||||
id = comments_to_create[i]
|
||||
parent_id = comment_lookup.get(id).parent_id.split("_")[1]
|
||||
for(let i = commentsToCreate.length -1; i >= 0; i--) {
|
||||
id = commentsToCreate[i]
|
||||
parentID = commentLookup.get(id).parent_id.split("_")[1]
|
||||
|
||||
if(created_comment_ids.includes(parent_id)) {
|
||||
document.getElementById(parent_id).appendChild(create_comment(comment_lookup.get(id)))
|
||||
created_comment_ids.push(id)
|
||||
comments_to_create.splice(i, 1)
|
||||
did_something = true
|
||||
if(createdCommentIDs.includes(parentID)) {
|
||||
document.getElementById(parentID).appendChild(createComment(commentLookup.get(id)))
|
||||
createdCommentIDs.push(id)
|
||||
commentsToCreate.splice(i, 1)
|
||||
didSomething = true
|
||||
}
|
||||
}
|
||||
|
||||
if(!did_something) {
|
||||
// Fail safe (parents missing for all comments left, should happend but oh well :D)
|
||||
if(!didSomething) {
|
||||
console.error("Didn't generate all comments correctly")
|
||||
break
|
||||
}
|
||||
|
|
@ -206,35 +239,40 @@ function create_comments() {
|
|||
// ------------------------- HTML-generating functions --------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
function generate_thread(data) {
|
||||
function generateThread(data) {
|
||||
const thread = data[0].data.children[0].data
|
||||
const thread_div = document.createElement("div")
|
||||
thread_div.id = "thread"
|
||||
thread_div.innerHTML = `
|
||||
<div id="thread-score-box">
|
||||
<div class="vote upvote"></div>
|
||||
<div id="thread-score">${pretty_score(thread.score)}</div>
|
||||
<div class="vote downvote"></div>
|
||||
</div>
|
||||
<div id="thumbnail"></div>
|
||||
<div id="thread-content">
|
||||
${thread.link_flair_text !== null ? '<span class="link-flair">'+thread.link_flair_text+'</span>' : ''}
|
||||
<a id="thread-title" href="${thread.url}">${thread.title}</a>
|
||||
<span id="domain">(${thread.domain})</span>
|
||||
<div id="thread-info">
|
||||
submitted <span id="thread-time">${pretty_date(thread.created_utc)}</span> by
|
||||
<a id="thread-author" class="user-link" href="https://www.reddit.com/user/${thread.author}">${thread.author}</a> to
|
||||
<a id="subreddit-link" href="https://reddit.com/r/${subreddit}">/r/${subreddit}</a>
|
||||
totalComments = thread.num_comments
|
||||
|
||||
const threadDiv = document.createElement("div")
|
||||
threadDiv.id = "thread"
|
||||
threadDiv.innerHTML = `
|
||||
<div id="thread">
|
||||
<div id="thread-score-box">
|
||||
<div class="vote upvote"></div>
|
||||
<div id="thread-score">${prettyScore(thread.score)}</div>
|
||||
<div class="vote downvote"></div>
|
||||
</div>
|
||||
<div id="thumbnail"></div>
|
||||
<div id="thread-content">
|
||||
${thread.link_flair_text !== null ? '<span class="link-flair">'+thread.link_flair_text+'</span>' : ''}
|
||||
<a id="thread-title" href="${thread.url}">${thread.title}</a>
|
||||
<span id="domain">(${thread.domain})</span>
|
||||
<div id="thread-info">
|
||||
submitted <span id="thread-time">${prettyDate(thread.created_utc)}</span> by
|
||||
<a id="thread-author" class="user-link" href="https://www.reddit.com/user/${thread.author}">${thread.author}</a> to
|
||||
<a id="subreddit-link" class="user-link" href="https://reddit.com/r/${subreddit}">/r/${subreddit}</a>
|
||||
</div>
|
||||
${thread.selftext !== '' ? '<div id="thread-selftext" class="user-text">'+markdown.render(thread.selftext)+'</div>':''}
|
||||
<div id="total-comments"><b>${totalComments} comments</b></div>
|
||||
${thread.media !== null ? parseHTML(thread.media_embed.content) : ''}
|
||||
${imageHosts.includes(thread.domain) ? '<a href="'+thread.url+'"><img id="thread-image" src="'+thread.preview.images[0].source.url+'"></a>' : ''}
|
||||
</div>
|
||||
${thread.selftext !== '' ? '<div id="thread-selftext" class="user-text">'+markdown.render(thread.selftext)+'</div>':''}
|
||||
</div>
|
||||
`
|
||||
|
||||
main_div.insertBefore(thread_div, loading_comments)
|
||||
total_comments = thread.num_comments
|
||||
mainDiv.appendChild(threadDiv)
|
||||
}
|
||||
function create_comment(comment) {
|
||||
const comment_div = document.createElement("div")
|
||||
function createComment(comment) {
|
||||
/*const comment_div = document.createElement("div")
|
||||
comment_div.id = comment.id
|
||||
comment_div.className = "comment comment-" + (comment.hasOwnProperty("removed") ? "removed" : (comment.depth % 2 == 0 ? "even" : "odd"));
|
||||
comment_div.innerHTML = `
|
||||
|
|
@ -249,85 +287,121 @@ function create_comment(comment) {
|
|||
<a href="https://todo.com">permalink</a>
|
||||
</div>
|
||||
`
|
||||
return comment_div
|
||||
return comment_div*/
|
||||
return generateHTML(`
|
||||
<div id="${comment.id}" class="comment comment-${comment.hasOwnProperty("removed") ? "removed" : (comment.depth % 2 == 0 ? "even" : "odd")}">
|
||||
<div class="comment-head">
|
||||
<a href="javascript:void(0)" class="user-link">[–]</a>
|
||||
<a href="https://www.reddit.com/user/${comment.author}" class="user-link comment-author">${comment.author}</a>
|
||||
<span class="comment-score">${prettyScore(comment.score)} point${(comment.score == 1) ? '': 's'}</span>
|
||||
<span class="comment-time">${prettyDate(comment.created_utc) }</span>
|
||||
</div>
|
||||
<div class="comment-body">${markdown.render(comment.body)}</div>
|
||||
<div class="comment-links">
|
||||
<a href="https://todo.com">permalink</a>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
|
||||
function generate_comment_info(removed_comments) {
|
||||
const comment_info = document.createElement("div")
|
||||
comment_info.innerHTML = `
|
||||
<div id="comment-info">
|
||||
removed comments: ${removed_comments}/${total_comments} (${ (100 * removed_comments / total_comments).toFixed(1)}%)
|
||||
function generateCommentInfo(removedCommentsAmount) {
|
||||
mainDiv.appendChild(generateHTML(`
|
||||
<div>
|
||||
<div id="comment-info">
|
||||
removed comments: ${removedCommentsAmount}/${totalComments} (${ (100 * removedCommentsAmount / totalComments).toFixed(1)}%)
|
||||
</div>
|
||||
<div id="comment-sort">sorted by: top</div>
|
||||
</div>
|
||||
<div id="comment-sort">sorted by: top</div>
|
||||
`
|
||||
main_div.insertBefore(comment_info, loading_comments)
|
||||
`))
|
||||
}
|
||||
|
||||
// Works for everything except iframes/script-tags, use "old fashion"-way in those cases (createElement etc...)
|
||||
function generateHTML(html) {
|
||||
return htmlParser.parseFromString(html, "text/html").documentElement
|
||||
}
|
||||
|
||||
// "<" => "<"
|
||||
function parseHTML(html) {
|
||||
const dummy = document.createElement('div')
|
||||
dummy.innerHTML = html
|
||||
return dummy.childNodes.length === 0 ? "" : dummy.childNodes[0].nodeValue;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// ----------------------------- AJAX-functions ---------------------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
async function fetch_multiple(url, data, init, json_walkdown=[], flattening=false) {
|
||||
const responses = await Promise.all(split_array(data, max_ids_per_call).map(single_request_data => fetch(url + single_request_data.join(), init)))
|
||||
async function fetchMultiple(url, data, init, jsonWalkdown=[], flattening=false) {
|
||||
const responses = await Promise.all(splitArray(data, maxIDsPerRequest).map(singleRequestData => fetch(url + singleRequestData.join(), init)))
|
||||
let json = await Promise.all(responses.map(x => x.json()))
|
||||
json_walkdown.forEach(property =>{
|
||||
jsonWalkdown.forEach(property =>{
|
||||
json.forEach((x, i) => json[i] = json[i][property])
|
||||
})
|
||||
return flattening ? flatten_array(json) : json
|
||||
return flattening ? flattenArray(json) : json
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// ---------------------- Other less interesting functions ----------------------
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
function split_array(array, size) {
|
||||
const array_split = []
|
||||
function splitArray(array, size) {
|
||||
const arraySplit = []
|
||||
|
||||
for(let i = 0, len = array.length ; i < len; i += size) {
|
||||
array_split.push(array.slice(i, i + size))
|
||||
arraySplit.push(array.slice(i, i + size))
|
||||
}
|
||||
|
||||
return array_split
|
||||
return arraySplit
|
||||
}
|
||||
|
||||
function flatten_array(arrays) {
|
||||
function flattenArray(arrays) {
|
||||
return arrays.reduce((array, item) => { return array.concat(item) }, [])
|
||||
}
|
||||
|
||||
// UTC -> "Reddit time format"
|
||||
function pretty_date(created_utc) {
|
||||
const current_utc = Math.floor((new Date()).getTime() / 1000)
|
||||
const second_diff = current_utc - created_utc
|
||||
const day_diff = Math.floor(second_diff / 86400)
|
||||
|
||||
if(day_diff < 0)
|
||||
return ''
|
||||
|
||||
if(day_diff == 0) {
|
||||
if(second_diff < 10)
|
||||
return "just now"
|
||||
if(second_diff < 60)
|
||||
return second_diff + " seconds ago"
|
||||
if(second_diff < 120)
|
||||
return "a minute ago"
|
||||
if(second_diff < 3600)
|
||||
return Math.floor(second_diff / 60) + " minutes ago"
|
||||
if(second_diff < 7200)
|
||||
return "an hour ago"
|
||||
if(second_diff < 86400)
|
||||
return Math.floor(second_diff / 3600) + " hours ago"
|
||||
}
|
||||
if(day_diff == 1)
|
||||
return "Yesterday"
|
||||
if(day_diff < 7)
|
||||
return day_diff + " days ago"
|
||||
if(day_diff < 31)
|
||||
return Math.floor(day_diff / 7) + " weeks ago"
|
||||
if(day_diff < 365)
|
||||
return Math.floor(day_diff / 30) + " months ago"
|
||||
return Math.floor(day_diff / 365) + " years ago"
|
||||
function setLoadingText(text) {
|
||||
loadingText.innerHTML = text
|
||||
}
|
||||
|
||||
function pretty_score(score) {
|
||||
function json(x) {
|
||||
return x.json()
|
||||
}
|
||||
|
||||
// UTC -> "Reddit time format" (5 hours ago, just now, etc...)
|
||||
function prettyDate(createdUTC) {
|
||||
const currentUTC = Math.floor((new Date()).getTime() / 1000)
|
||||
const secondDiff = currentUTC - createdUTC
|
||||
const dayDiff = Math.floor(secondDiff / 86400)
|
||||
|
||||
if(dayDiff < 0)
|
||||
return ''
|
||||
|
||||
if(dayDiff == 0) {
|
||||
if(secondDiff < 10)
|
||||
return "just now"
|
||||
if(secondDiff < 60)
|
||||
return secondDiff + " seconds ago"
|
||||
if(secondDiff < 120)
|
||||
return "a minute ago"
|
||||
if(secondDiff < 3600)
|
||||
return Math.floor(secondDiff / 60) + " minutes ago"
|
||||
if(secondDiff < 7200)
|
||||
return "an hour ago"
|
||||
if(secondDiff < 86400)
|
||||
return Math.floor(secondDiff / 3600) + " hours ago"
|
||||
}
|
||||
if(dayDiff == 1)
|
||||
return "Yesterday"
|
||||
if(dayDiff < 7)
|
||||
return dayDiff + " days ago"
|
||||
if(dayDiff < 31)
|
||||
return Math.floor(dayDiff / 7) + " weeks ago"
|
||||
if(dayDiff < 365)
|
||||
return Math.floor(dayDiff / 30) + " months ago"
|
||||
return Math.floor(dayDiff / 365) + " years ago"
|
||||
}
|
||||
|
||||
// 12000 => 1.2k
|
||||
function prettyScore(score) {
|
||||
return score >= 10000 ? (score / 1000).toFixed(1) + "k" : score
|
||||
}
|
||||
|
|
@ -43,18 +43,21 @@ a:hover {
|
|||
|
||||
#header {
|
||||
background-color: #ca302c;/*#19171c;/*#181818*/
|
||||
padding: 20px 10px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#header a {
|
||||
#header-title-link {
|
||||
align-self: center;
|
||||
color: #fff;/*#ddd;*/
|
||||
transition: color 0.3s;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
#header a:hover {
|
||||
#header-title-link:hover {
|
||||
color: #FF8B88;/*#ca302c*/;
|
||||
text-decoration: none;
|
||||
|
||||
}
|
||||
|
||||
#header-title {
|
||||
|
|
@ -62,6 +65,24 @@ a:hover {
|
|||
display: inline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#loading {
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#loading-text {
|
||||
margin: 0 20px 0 0;
|
||||
}
|
||||
|
||||
#loading-image {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
border-radius: 32px;
|
||||
}
|
||||
|
||||
#main {
|
||||
margin: 15px;
|
||||
}
|
||||
|
|
@ -94,6 +115,7 @@ a:hover {
|
|||
|
||||
#thread {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -133,15 +155,13 @@ a:hover {
|
|||
float: left;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#thread-content {
|
||||
flex: 1;
|
||||
float: left;
|
||||
margin-left: 3px;
|
||||
max-width: 840px;
|
||||
}
|
||||
|
||||
.link-flair {
|
||||
|
|
@ -174,16 +194,29 @@ a:hover {
|
|||
margin-top: 2px;
|
||||
}
|
||||
|
||||
#total-comments {
|
||||
color: #828282;
|
||||
font-size: 10px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
#thread-selftext {
|
||||
border: 1px solid #666;
|
||||
border-radius: 7px;
|
||||
margin: 5px 0;
|
||||
padding: 5px 10px;
|
||||
border-radius: 7px;
|
||||
margin: 5px 0 7px;
|
||||
padding: 5px 10px;
|
||||
max-width: 840px;
|
||||
}
|
||||
|
||||
#thread-image {
|
||||
max-width: 768px;
|
||||
max-height: 768px;
|
||||
}
|
||||
|
||||
#comment-info {
|
||||
color: #ca302c;
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px dotted #808080;
|
||||
font-size: 16px;
|
||||
|
|
@ -196,7 +229,7 @@ a:hover {
|
|||
}
|
||||
|
||||
.comment {
|
||||
margin: 0 8px 8px 0;
|
||||
margin: 0 0 8px 0;
|
||||
padding: 5px 8px 5px /*14px*/ 30px;
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px;
|
||||
|
|
@ -237,7 +270,7 @@ a:hover {
|
|||
}
|
||||
|
||||
.comment-removed {
|
||||
background-color: #633636;
|
||||
background-color: #840c09/*#633636*/;
|
||||
}
|
||||
|
||||
.comment-odd {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,16 @@
|
|||
<head>
|
||||
<title>Removeddit</title>
|
||||
<meta charset="utf-8">
|
||||
<link href="https://cdn.rawgit.com/JubbeArt/removeddit/fc5a696e/static/style.css" rel="stylesheet">
|
||||
<!-- https://cdn.rawgit.com/JubbeArt/removeddit/fc5a696e/static/style.css-->
|
||||
<link href="/static/style.css" rel="stylesheet">
|
||||
<link rel="shotcut icon" href="https://cdn.rawgit.com/JubbeArt/removeddit/fc5a696e/static/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<header id="header">
|
||||
<a href="/"><h1 id="header-title">Removeddit [beta]</h1></a>
|
||||
<a href="/" id="header-title-link"><h1 id="header-title">Removeddit [beta]</h1></a>
|
||||
<div id="loading">
|
||||
<p id="loading-text">Loading...</p>
|
||||
<img id="loading-image"></img>
|
||||
</div>
|
||||
</header>
|
||||
<div id="main">
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<div id="loading-comments" class="loading-comments"></div>
|
||||
<script src="https://cdn.rawgit.com/gamefreak/snuownd/533e8dcb/snuownd.js"></script>
|
||||
<script>
|
||||
const thread_id = "{{ .ThreadID }}";
|
||||
const threadID = "{{ .ThreadID }}";
|
||||
const token = "{{ .Token }}";
|
||||
const subreddit = "{{ .Subreddit }}";
|
||||
const all_ids = {{ .CommentIDs }};
|
||||
</script>
|
||||
<script src="https://cdn.rawgit.com/JubbeArt/removeddit/d0d29310/static/script.js"></script>
|
||||
<!--https://cdn.rawgit.com/JubbeArt/removeddit/d0d29310/static/script.js-->
|
||||
<script src="/static/script.js"></script>
|
||||
Loading…
Reference in a new issue