diff --git a/README.md b/README.md index 8ec342a..c5325e4 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ Note: Ignores iframe embedded videos, including the chat replay iframe on YouTub **Firefox** * I have a signed xpi that you can immediately install. Open `build/` and drag the xpi into Firefox. It may take a few seconds for the browser to display the extension installation dialog box. -* You can also find the signed Firefox files in the project's [Releases page](https://github.com/sir-pinecone/youtube-herp-derp-browser-extension/releases). +* You can also find the signed Firefox files in the project's [Releases page](https://git.michael.is/michael/youtube-herp-derp-browser-extension/releases). **Chrome** * Go to Chrome extensions page. -* Toggle the developer mode (top-right of page). +* Toggle the developer mode (top-right of page). * Click `Load unpacked`. * Select this project's root folder. @@ -31,10 +31,19 @@ Note: Ignores iframe embedded videos, including the chat replay iframe on YouTub ## Signing and Building ### Firefox -* Install web-ext with `$ npm install --global web-ext` -* Generate an unlisted xpi with: - `web-ext sign --api-key --api-secret ` - * You can obtain these keys from https://addons.mozilla.org/en-US/developers/addon/api/key/ -* The signed xpi will be in `web-ext-artifacts/`. Drag this into Firefox to install it. -* Alternatively use the private sign-firefox-extension.sh script (not included in the repo) which places the xpi in `build/`. + +* Can either sign and upload from the CLI or manually upload. The former is easy but it triggers a human review? + +* CLI Upload + * Install web-ext with `$ npm install --global web-ext` + * Generate an unlisted xpi with: + `web-ext sign --api-key --api-secret ` + * You can obtain these keys from https://addons.mozilla.org/en-US/developers/addon/api/key/ + * The signed xpi will be in `web-ext-artifacts/`. Drag this into Firefox to install it. + * Alternatively use the private sign-firefox-extension.sh script (not included in the repo) which places the xpi in `build/`. + +* Manual Upload + * Go to https://addons.mozilla.org/en-US/developers, open the extension and then click on `Upload new version`. + * Pack the manifest, js script and icons directory into a zip and upload this file. + * To download the signed xpi, click on `view all` (versions), then the name of the new version, then the xpi filename. diff --git a/build/Firefox_youtube_herp_derp-1.0.4.2.xpi b/build/Firefox_youtube_herp_derp-1.0.4.2.xpi new file mode 100644 index 0000000..6e25535 Binary files /dev/null and b/build/Firefox_youtube_herp_derp-1.0.4.2.xpi differ diff --git a/index.js b/index.js index 26e4ea5..daa51e7 100644 --- a/index.js +++ b/index.js @@ -1,88 +1,97 @@ -// [1, max] -const RandomInt = max => Math.floor(Math.random() * max); +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(" "); + 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 element. -const DerpElement = element => { - const c = element; - c.derpOriginal = c.textContent; // Preserve the original contents. - c.onclick = () => { - c.clicked = !c.clicked; - c.textContent = c.clicked ? c.derpOriginal : c.derp_str; - }; - c.classList.add("derped"); - c.derp_str = DerpString(); - c.textContent = c.derp_str; - c.clicked = false; +// 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.clicked && c.textContent === c.derpOriginal) return; - if (!c.clicked && c.textContent === c.derp_str) return; + 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.derp_str) !== -1) { - // In the case of the new comment being appended after the derp string, - // just grab it and put it in the derpOriginal variable - const idx = c.derp_str.length; - c.derpOriginal = c.textContent.substring(idx); - c.textContent = c.textContent.substring(0, idx); - c.clicked = false; - 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.derpOriginal) !== -1) { - // Same issue, but the comment was appended after derpOriginal. - const idx = c.derpOriginal.length; - c.derpOriginal = c.textContent.substring(idx); - c.textContent = c.derp_str; - c.clicked = false; - } + 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 commentContentSelector = ["#content-text"]; // Comment text content. + const commentTextId = "#content-text"; + const derpedSelector = `${commentTextId}.isDerped`; + const commentRendererName = "YTD-COMMENT-RENDERER"; - const notDerpedSelector = commentContentSelector - .map(sel => `${sel}:not(.derped)`) - .join(", "); + // Need to convert comments that were inserted before the mutation observer below runs. + commentsSection.querySelectorAll(commentRendererName).forEach(node => { + DerpComment(node, node.querySelector(commentTextId)); + }); - const derpedSelector = commentContentSelector.map(sel => `${sel}.derped`).join(", "); + // 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); - // Only watch for child list changes, as we're watching the comments - // container. - const mutationConfig = { attributes: false, childList: true, subtree: true }; + 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)); + } + } + }); + }); + }); - // Detect when comments are added to the DOM. - const observer = new MutationObserver(() => { - // 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); - - // Derp all un-derped comments. - document.querySelectorAll(notDerpedSelector).forEach(DerpElement); - }); - - observer.observe(commentsSection, mutationConfig); + // 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 @@ -90,18 +99,20 @@ const Init = commentsSection => { // to be done since comments are added in the DOM through js at an undetermined // point through Youtube's execution. const CheckIfCommentsLoaded = () => { - const commentSectionSelector = "html body ytd-app ytd-comments ytd-item-section-renderer #contents"; + setTimeout(() => { + const commentsSection = document.querySelector("ytd-comments #contents"); - setTimeout(() => { - // This selector is awful, but Youtube re-uses a lot of the DOM (the - // selector for the comments is re-used across a bunch of pages) so we need - // the exact path to the comments to match - const commentsSection = document.querySelector(commentSectionSelector); - if (commentsSection !== null) - Init(commentsSection); - else - CheckIfCommentsLoaded(); - }, 500); + 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(); + diff --git a/manifest.json b/manifest.json index 64b23ef..0276015 100644 --- a/manifest.json +++ b/manifest.json @@ -1,13 +1,18 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "YouTube Herp Derp", "description": "Replaces YouTube comments with herp derps. Forked from github.com/twstokes/herpderp", - "homepage_url": "https://github.com/sir-pinecone/youtube-herp-derp-browser-extension", - "version": "1.0.3", + "homepage_url": "https://git.michael.is/michael/youtube-herp-derp-browser-extension", + "version": "1.0.4.2", "icons": { "48": "icons/herp48.png", "128": "icons/herp128.png" }, + "browser_specific_settings": { + "gecko": { + "id": "{8460883e-a350-4e55-8c07-eed5eefdc2b5}" + } + }, "content_scripts": [ { "run_at": "document_idle",