const RandomInt = max => Math.floor(Math.random() * max); // [1, max] const DerpString = (length = 20) => { const RandomDerp = () => { let n = RandomInt(4); let result = ""; if (n == 0) { result = "blah"; } else if (n == 1) { result = "durr"; } else if (n == 2) { result = "herp"; } else if (n == 3) { result = "derp"; } return result; }; return Array.from({ length: (RandomInt(length) + 1) }, () => RandomDerp()).join(" "); }; // Herp derps an comment. const DerpComment = (commentNode, textNode) => { commentNode.isDerped = true; const c = textNode; c.derpOrig = c.textContent; // Preserve the original contents. c.onclick = () => { c.derpClicked = !c.derpClicked; c.textContent = c.derpClicked ? c.derpOrig : c.derpStr; }; c.classList.add("isDerped"); c.derpStr = DerpString(); c.derpClicked = false; c.textContent = c.derpStr; // Set the derp text. }; const ValidatePreviouslyDerpedComments = comment => { const c = comment; if (c.derpClicked && c.textContent === c.derpOrig) return; if (!c.derpClicked && c.textContent === c.derpStr) return; // Fix the comment. The only case of malformed comments encountered so far // are these two cases: if (c.textContent.indexOf(c.derpStr) !== -1) { // In the case of the new comment being appended after the derp string, // just grab it and put it in the derpOrig variable const idx = c.derpStr.length; c.derpOrig = c.textContent.substring(idx); c.textContent = c.textContent.substring(0, idx); c.derpClicked = false; return; } if (c.textContent.indexOf(c.derpOrig) !== -1) { // Same issue, but the comment was appended after derpOrig. const idx = c.derpOrig.length; c.derpOrig = c.textContent.substring(idx); c.textContent = c.derpStr; c.derpClicked = false; } }; const Init = commentsSection => { const commentTextId = "#content-text"; const derpedSelector = `${commentTextId}.isDerped`; const commentRendererName = "YTD-COMMENT-RENDERER"; // Need to convert comments that were inserted before the mutation observer below runs. commentsSection.querySelectorAll(commentRendererName).forEach(node => { DerpComment(node, node.querySelector(commentTextId)); }); // Detect when comments are added to the DOM. const observer = new MutationObserver(mutations => { // Check that everything's fine with the already derped comments. // This is necessary because youtube does a lot of wizardry with comments in-between videos. document.querySelectorAll(derpedSelector).forEach(ValidatePreviouslyDerpedComments); mutations.forEach((change, index) => { console.assert(change.type === "childList"); change.addedNodes.forEach(node => { if (node.nodeName === commentRendererName) { if (!node.isDerped) { DerpComment(node, node.querySelector(commentTextId)); } } }); }); }); // Only watch for child list changes, as we're watching the comments container. observer.observe(commentsSection, { attributes: false, childList: true, subtree: true }); }; // Check every so often if comments are loaded or not. Once they are, the // timeout stops until the user leaves youtube or reloads the page. This needs // to be done since comments are added in the DOM through js at an undetermined // point through Youtube's execution. const CheckIfCommentsLoaded = () => { setTimeout(() => { const commentsSection = document.querySelector("ytd-comments #contents"); if (commentsSection !== null) { Init(commentsSection); } else { CheckIfCommentsLoaded(); } // If the timeout is <= 1000 then Chrome sometimes fails to run the mutation observer. // It'll create it and active it, but it doesn't run the callback on DOM changes... // I'm seeing this in Chrome 110. }, 2000); }; CheckIfCommentsLoaded();