« get me outta code hell

scratch-userscripts - Handy userscripts for the Scratch website - issues/ideas: https://notabug.org/towerofnix/scratch-userscripts/issues
about summary refs log tree commit diff
path: root/scratch-comment-redactor.js
diff options
context:
space:
mode:
Diffstat (limited to 'scratch-comment-redactor.js')
-rw-r--r--scratch-comment-redactor.js111
1 files changed, 111 insertions, 0 deletions
diff --git a/scratch-comment-redactor.js b/scratch-comment-redactor.js
new file mode 100644
index 0000000..76c7190
--- /dev/null
+++ b/scratch-comment-redactor.js
@@ -0,0 +1,111 @@
+// ==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})
+}