Skip to content

Instantly share code, notes, and snippets.

@towerofnix
Created May 7, 2018 21:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save towerofnix/445a04615c48e18651d15f06f01ec128 to your computer and use it in GitHub Desktop.
Save towerofnix/445a04615c48e18651d15f06f01ec128 to your computer and use it in GitHub Desktop.
Anonymizes Scratch comments
// ==UserScript==
// @name Scratch Comment Redactor
// @namespace https://towerofnix.github.io
// @match *://scratch.mit.edu/*
// @grant none
// ==/UserScript==
const usersSymbol = Symbol()
const replaceText = function(el, newText) {
while (el.firstChild) {
el.firstChild.remove()
}
el.appendChild(document.createTextNode(newText))
}
const redactCommentThread = function(comment, usernameToRedact) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
let topThread = comment
while (topThread && !topThread.classList.contains('top-level-reply')) {
topThread = topThread.parentElement
}
if (!topThread) {
console.error('Could not get the thread element, sorry :(')
console.info(comment)
return
}
if (!(usersSymbol in topThread)) {
topThread[usersSymbol] = {} // Mapping of username -> descriptor
}
const users = topThread[usersSymbol]
if (!users.hasOwnProperty(usernameToRedact)) {
const index = Object.keys(users).length
users[usernameToRedact] = {
realUsername: usernameToRedact,
fakeUsername: `[User ${alphabet[index]}]`,
hue: Math.round(360 / 7 * index)
}
}
const descriptor = users[usernameToRedact]
const allComments = [comment, ...topThread.querySelectorAll('.reply .comment')]
for (const comment of allComments) {
// Redact bold "author" username at top of comment
const authorUsernameEl = comment.querySelector('.info .name a')
const authorUsername = authorUsernameEl.innerText.trim()
if (authorUsername === usernameToRedact) {
replaceText(authorUsernameEl, descriptor.fakeUsername)
// Also redact profile picture:
const authorAvatarEl = comment.querySelector('#comment-user')
const img = authorAvatarEl.querySelector('img')
if (img) {
authorAvatarEl.querySelector('img').remove()
const fakeProfilePicture = document.createElement('div')
Object.assign(fakeProfilePicture.style, {
float: 'left', display: 'block', marginRight: '10px',
width: '45px', height: '45px',
backgroundColor: `hsl(${descriptor.hue}deg, 100%, 50%)`
})
authorAvatarEl.appendChild(fakeProfilePicture)
}
}
// Redact "@user" replies
const a = comment.querySelector('.content a')
if (a && a.innerText.trim() === '@' + usernameToRedact) {
replaceText(a, '@' + descriptor.fakeUsername)
}
}
}
// Button-adder
const commentsContainer = document.querySelector('#comments ul.comments')
if (commentsContainer) {
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const addedNode of mutation.addedNodes) {
if (typeof addedNode.classList === 'undefined') continue
if (addedNode.classList.contains('top-level-reply') === false) continue
const topComment = addedNode
const comments = [topComment, ...topComment.querySelectorAll('.reply .comment')]
for (const comment of comments) {
const usernameEl = comment.querySelector('.info .name a')
if (usernameEl) {
const actionContainer = comment.querySelector('.actions-wrap')
const span = document.createElement('span')
const username = usernameEl.innerText.trim()
span.classList.add('actions', 'report')
span.appendChild(document.createTextNode('Redact'))
const handler = () => {
redactCommentThread(topComment, username)
replaceText(span, 'Redacted')
span.style.cursor = 'default'
span.removeEventListener('click', handler)
}
span.addEventListener('click', handler)
actionContainer.appendChild(span)
}
}
}
}
})
observer.observe(commentsContainer, {childList: true})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment