Last active
April 21, 2024 21:18
-
-
Save lylejantzi3rd/a11afd2d3d6c7370156cec8576f53536 to your computer and use it in GitHub Desktop.
Flatten Substack Comments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const postId = window._preloads.post.id; | |
const url = window._preloads.base_url; | |
async function getComments() { | |
const response = await fetch(`${url}/api/v1/post/${postId}/comments?token=&all_comments=true&sort=most_recent_first`); | |
const result = await response.json(); | |
return result?.comments; | |
} | |
function getAllChildren(children, acc = []) { | |
for(const child of children) { | |
if (child?.children?.length) { | |
acc = [...acc, ...getAllChildren(child.children)]; | |
} | |
acc.push(child); | |
} | |
return acc; | |
} | |
function sortAsc(a, b) { | |
let dateA = new Date(a.edited_at || a.date); | |
let dateB = new Date(b.edited_at || b.date); | |
return dateA - dateB; | |
} | |
function formatDate(date) { | |
let seconds = Math.floor((new Date() - date) / 1000); | |
let interval = 0; | |
interval = seconds / 31536000; | |
if (interval > 1) { | |
const month = date.toLocaleString('default', { month: 'long' }); | |
return `${month} ${date.getDate()}, ${date.getFullYear()}`; | |
} | |
interval = seconds / 86400; | |
if (interval > 1) { | |
const month = date.toLocaleString('default', { month: 'long' }); | |
return `${month} ${date.getDate()}`; | |
} | |
interval = seconds / 3600; | |
if (interval > 1) { | |
return Math.floor(interval) + " hrs ago"; | |
} | |
interval = seconds / 60; | |
if (interval > 1) { | |
return Math.floor(interval) + " minutes ago"; | |
} | |
return Math.floor(seconds) + " seconds ago"; | |
} | |
function printCommentBody(body) { | |
const paragraphs = body.split(/\r\n|\r|\n/).filter(Boolean); | |
return paragraphs.map(p => `<p><span>${p}</p></span>`).join("\n"); | |
} | |
function printPublication(metadata) { | |
const pub = metadata?.author_on_other_pub; | |
if (pub) { | |
return ` | |
<div class="_publicationHoverCardTarget_1ypz6_51"> | |
<a class="commenter-publication" href="${pub.base_url}" rel="nofollow" target="_blank"> | |
<span>${pub.name}</span> | |
</a> | |
</div>`; | |
} | |
return ''; | |
} | |
function printComment(comment) { | |
let profileUrl = ''; | |
if(comment.user_banned && comment.name) { | |
comment.name = comment.name + ' (Banned)'; | |
profileUrl = `https://substack.com/profile/${comment.user_id}-${comment.name.replaceAll(' ', '-').toLowerCase()}`; | |
} else if(comment.user_banned && !comment.name) { | |
comment.name = 'Removed (Banned)'; | |
comment.body = 'Comment removed'; | |
profileUrl = 'https://substack.com/profile/null' | |
comment.photo_url = 'https%3A%2F%2Fsubstack.com%2Fimg%2Favatars%2Flogged-out.png'; | |
} else if ('deleted' === comment.status) { | |
comment.name = 'deleted'; | |
comment.body = 'Comment deleted'; | |
profileUrl = 'https://substack.com/profile/null' | |
comment.photo_url = 'https%3A%2F%2Fsubstack.com%2Fimg%2Favatars%2Flogged-out.png'; | |
} else if ('moderator_removed' === comment.status) { | |
comment.name = 'Removed'; | |
comment.body = 'Comment removed'; | |
profileUrl = 'https://substack.com/profile/null' | |
comment.photo_url = 'https%3A%2F%2Fsubstack.com%2Fimg%2Favatars%2Flogged-out.png'; | |
} else { | |
profileUrl = `https://substack.com/profile/${comment.user_id}-${comment.name.replaceAll(' ', '-').toLowerCase()}`; | |
} | |
const profilePhotoUrl = comment.photo_url ? encodeURIComponent(comment.photo_url) : 'https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbfe3f930-3206-4dd0-a1e4-138ce0b0b743_144x144.png'; | |
return ` | |
<div class="comment"> | |
<div id="comment-${comment.id}" class="comment-anchor"></div> | |
<div id="comment-${comment.id}-reply" class="comment-anchor"></div> | |
<table class="comment-content"> | |
<tr> | |
<td class="comment-head"> | |
<div class="profile-hover-card-target _profileHoverCardTarget_1ypz6_50"> | |
<div class="user-head"> | |
<a href="${profileUrl}"> | |
<div class="profile-img-wrap"> | |
<img | |
class="_img_16u6n_1 pencraft pc-reset" | |
src="https://substackcdn.com/image/fetch/w_66,h_66,c_fill,f_auto,q_auto:good,fl_progressive:steep/${profilePhotoUrl}" | |
sizes="100vw" | |
alt="" | |
width="66" | |
height="66" | |
/> | |
${comment?.user_banned ? ` | |
<svg role="img" width="100" height="100" viewBox="0 0 100 100" fill="none" stroke-width="1.8" stroke="#000" xmlns="http://www.w3.org/2000/svg"> | |
<g> | |
<title></title> | |
<circle cx="50" cy="50" stroke="#000000" r="46" stroke-width="8"></circle> | |
<line x1="20" y1="20" x2="80" y2="80" stroke="#000000" stroke-width="8"></line> | |
</g> | |
</svg> | |
`: ''} | |
</div> | |
</a> | |
</div> | |
</div> | |
</td> | |
<td class="comment-rest"> | |
<div class="comment-meta"> | |
<span class="commenter-name"> | |
<div class="pencraft pc-display-flex pc-gap-4 pc-alignItems-center pc-reset pc-display-inline-flex"> | |
<div class="profile-hover-card-target _profileHoverCardTarget_1ypz6_50"> | |
<a href="${profileUrl}"> | |
<div class="pencraft pc-reset _color-pub-primary-text_1k90y_204 _line-height-20_1k90y_95 _font-text_1k90y_121 _size-13_1k90y_45 _weight-semibold_1k90y_165 _reset_1k90y_1">${comment.name}</div> | |
</a> | |
</div> | |
</div> | |
</span> | |
${printPublication(comment.metadata)} | |
<a class="comment-timestamp" href="${url}/comment/${comment.id}" rel="nofollow" native="true">${formatDate(new Date(comment.date))}</a> | |
</div> | |
<div class="comment-body">${printCommentBody(comment.body)}</div> | |
</td> | |
</tr> | |
</table> | |
<div class="comment-list-collapser"> | |
<div class="comment-list-collapser-line"></div> | |
</div> | |
</div> | |
`; | |
} | |
function printComments(comments) { | |
return ` | |
<div class="comment-list"> | |
<div class="comment-list-items"> | |
${comments.map(printComment).join("\n")} | |
</div> | |
</div> | |
<hr/> | |
`; | |
} | |
async function replaceComments() { | |
let comments = await getComments(); | |
let flattenedComments = getAllChildren(comments); | |
flattenedComments.sort(sortAsc); | |
document.querySelector('.comment-list-container').innerHTML = printComments(flattenedComments); | |
} | |
replaceComments(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment