Compare commits

...

2 Commits

Author SHA1 Message Date
6745b375a0 Speed things up 2023-02-15 16:11:42 -05:00
829ebd0736 Update README.md 2021-03-24 14:08:40 -04:00
6 changed files with 133 additions and 98 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
node_modules node_modules
.web-extension-id .web-extension-id
sign.sh
web-ext-artifacts/ web-ext-artifacts/
sign-firefox-extension.sh

View File

@ -1,30 +1,49 @@
Replaces YouTube comments with random herp derps. Click on the comment text to reveal the original. Replaces YouTube comments with random herp derps. Click on the comment text to reveal the original.
This is modification of github.com/twstokes/herpderp This is a fork of [twstokes/herpderp](https://github.com/twstokes/herpderp) with some modifications.
Note: Ignores iframe embedded videos, including the chat replay iframe on YouTube livestream vids. Note: Ignores iframe embedded videos, including the chat replay iframe on YouTube livestream vids.
**Known bugs**
1. As of 2021-03-24, sometimes when showing the original comment the first few lines are missing.
This tends to happen when you've transitioned to the page from another video. Refreshing the page
tends to fix the problem. I'll eventually try to fix this.
## Install ## Install
**Firefox** **Firefox**
* I have a signed xpi that you can immediately install. Open `build/` and drag the xpi into 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://git.michael.is/michael/youtube-herp-derp-browser-extension/releases).
**Chrome** **Chrome**
* Unzip the Chrome addon file in the `build` folder. * Go to Chrome extensions page.
* Go to Chrome extensions page. Toggle the developer mode (top-right of page). Click `Load * Toggle the developer mode (top-right of page).
unpacked`. * Click `Load unpacked`.
* Select the unzipped folder. * Select this project's root folder.
## Development ## Development
### Local Testing ### Local Testing
In Firefox, open about:debugging and click `Load Temporary Add-on...` then select `manifest.json`. * Firefox: open about:debugging and click `Load Temporary Add-on...` then select `manifest.json` file.
file. * Chrome: follow the install steps from above.
### Signing and Building ## Signing and Building
### Firefox
* 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 <your JWT issuer> --api-secret <your JWT 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.
* Install web-ext with `$ npm install --global web-ext`
* Generate an unlisted xpi with:
'web-ext sign --channel unlisted --api-key <your add-on signing key> --api-secret <your add-on signing secret>`.
Those keys can be obtained from https://addons.mozilla.org/en-US/developers/addon/api/key/
* Drag downloaded xpi into Firefox.

Binary file not shown.

171
index.js
View File

@ -1,88 +1,97 @@
// [1, max] const RandomInt = max => Math.floor(Math.random() * max); // [1, max]
const RandomInt = max => Math.floor(Math.random() * max);
const DerpString = (length = 20) => { const DerpString = (length = 20) => {
const RandomDerp = () => { const RandomDerp = () => {
let n = RandomInt(4); let n = RandomInt(4);
let result = ""; let result = "";
if (n == 0) if (n == 0) {
result = "blah"; result = "blah";
else if (n == 1) }
result = "durr"; else if (n == 1) {
else if (n == 2) result = "durr";
result = "herp"; }
else if (n == 3) else if (n == 2) {
result = "derp"; result = "herp";
return result; }
}; else if (n == 3) {
return Array.from({ length: (RandomInt(length) + 1) }, () => RandomDerp()).join(" "); result = "derp";
}
return result;
};
return Array.from({ length: (RandomInt(length) + 1) }, () => RandomDerp()).join(" ");
}; };
// Herp derps an element. // Herp derps an comment.
const DerpElement = element => { const DerpComment = (commentNode, textNode) => {
const c = element; commentNode.isDerped = true;
c.derpOriginal = c.textContent; // Preserve the original contents. const c = textNode;
c.onclick = () => { c.derpOrig = c.textContent; // Preserve the original contents.
c.clicked = !c.clicked; c.onclick = () => {
c.textContent = c.clicked ? c.derpOriginal : c.derp_str; c.derpClicked = !c.derpClicked;
}; c.textContent = c.derpClicked ? c.derpOrig : c.derpStr;
c.classList.add("derped"); };
c.derp_str = DerpString(); c.classList.add("isDerped");
c.textContent = c.derp_str; c.derpStr = DerpString();
c.clicked = false; c.derpClicked = false;
c.textContent = c.derpStr; // Set the derp text.
}; };
const ValidatePreviouslyDerpedComments = comment => { const ValidatePreviouslyDerpedComments = comment => {
const c = comment; const c = comment;
if (c.clicked && c.textContent === c.derpOriginal) return; if (c.derpClicked && c.textContent === c.derpOrig) return;
if (!c.clicked && c.textContent === c.derp_str) return; if (!c.derpClicked && c.textContent === c.derpStr) return;
// Fix the comment. The only case of malformed comments encountered so far // Fix the comment. The only case of malformed comments encountered so far
// are these two cases: // are these two cases:
if (c.textContent.indexOf(c.derp_str) !== -1) { if (c.textContent.indexOf(c.derpStr) !== -1) {
// In the case of the new comment being appended after the derp string, // In the case of the new comment being appended after the derp string,
// just grab it and put it in the derpOriginal variable // just grab it and put it in the derpOrig variable
const idx = c.derp_str.length; const idx = c.derpStr.length;
c.derpOriginal = c.textContent.substring(idx); c.derpOrig = c.textContent.substring(idx);
c.textContent = c.textContent.substring(0, idx); c.textContent = c.textContent.substring(0, idx);
c.clicked = false; c.derpClicked = false;
return; return;
} }
if (c.textContent.indexOf(c.derpOriginal) !== -1) { if (c.textContent.indexOf(c.derpOrig) !== -1) {
// Same issue, but the comment was appended after derpOriginal. // Same issue, but the comment was appended after derpOrig.
const idx = c.derpOriginal.length; const idx = c.derpOrig.length;
c.derpOriginal = c.textContent.substring(idx); c.derpOrig = c.textContent.substring(idx);
c.textContent = c.derp_str; c.textContent = c.derpStr;
c.clicked = false; c.derpClicked = false;
} }
}; };
const Init = commentsSection => { 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 // Need to convert comments that were inserted before the mutation observer below runs.
.map(sel => `${sel}:not(.derped)`) commentsSection.querySelectorAll(commentRendererName).forEach(node => {
.join(", "); 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 mutations.forEach((change, index) => {
// container. console.assert(change.type === "childList");
const mutationConfig = { attributes: false, childList: true, subtree: true }; change.addedNodes.forEach(node => {
if (node.nodeName === commentRendererName) {
if (!node.isDerped) {
DerpComment(node, node.querySelector(commentTextId));
}
}
});
});
});
// Detect when comments are added to the DOM. // Only watch for child list changes, as we're watching the comments container.
const observer = new MutationObserver(() => { observer.observe(commentsSection, { attributes: false, childList: true, subtree: true });
// 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);
}; };
// Check every so often if comments are loaded or not. Once they are, the // 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 // to be done since comments are added in the DOM through js at an undetermined
// point through Youtube's execution. // point through Youtube's execution.
const CheckIfCommentsLoaded = () => { const CheckIfCommentsLoaded = () => {
const commentSectionSelector = "html body ytd-app ytd-comments ytd-item-section-renderer #contents"; setTimeout(() => {
const commentsSection = document.querySelector("ytd-comments #contents");
setTimeout(() => { if (commentsSection !== null) {
// This selector is awful, but Youtube re-uses a lot of the DOM (the Init(commentsSection);
// selector for the comments is re-used across a bunch of pages) so we need }
// the exact path to the comments to match else {
const commentsSection = document.querySelector(commentSectionSelector); CheckIfCommentsLoaded();
if (commentsSection !== null) }
Init(commentsSection); // If the timeout is <= 1000 then Chrome sometimes fails to run the mutation observer.
else // It'll create it and active it, but it doesn't run the callback on DOM changes...
CheckIfCommentsLoaded(); // I'm seeing this in Chrome 110.
}, 500); }, 2000);
}; };
CheckIfCommentsLoaded(); CheckIfCommentsLoaded();

View File

@ -1,17 +1,22 @@
{ {
"manifest_version": 2, "manifest_version": 3,
"name": "YouTube Herp Derp", "name": "YouTube Herp Derp",
"description": "Replaces YouTube comments with herp derps. Forked from github.com/twstokes/herpderp", "description": "Replaces YouTube comments with herp derps. Forked from github.com/twstokes/herpderp",
"homepage_url": "https://michael.is", "homepage_url": "https://git.michael.is/michael/youtube-herp-derp-browser-extension",
"version": "1.0.3", "version": "1.0.4.2",
"icons": { "icons": {
"48": "icons/herp48.png", "48": "icons/herp48.png",
"128": "icons/herp128.png" "128": "icons/herp128.png"
}, },
"browser_specific_settings": {
"gecko": {
"id": "{8460883e-a350-4e55-8c07-eed5eefdc2b5}"
}
},
"content_scripts": [ "content_scripts": [
{ {
"run_at": "document_idle", "run_at": "document_idle",
"all_frames": false, // Ignore iframes. "all_frames": false,
"matches": [ "matches": [
"https://apis.google.com/*", "https://apis.google.com/*",
"https://plus.googleapis.com/*", "https://plus.googleapis.com/*",