123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- // ==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('Anonymize'))
- 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})
- }
|