diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d7b6b77
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.web-extension-id
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..264d3ea
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Tanner Stokes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..158032a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+Replaces YouTube comments with random herp derps. This is modification of github.com/twstokes/herpderp
+
+Note: Ignores iframe embedded videos, including the chat replay iframe on YouTube livestream vids.
+
+## Install
+
+* I have a signed xpi that you can immediately install. Open `build/` and drag the xpi into Firefox.
+
+## Development
+
+### Local Testing
+
+In Firefox, open about:debugging and click `Load Temporary Add-on...` then select `manifest.json`.
+file.
+
+### Signing and Building
+
+* 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.
diff --git a/icons/herp128.png b/icons/herp128.png
new file mode 100644
index 0000000..d7e82e3
Binary files /dev/null and b/icons/herp128.png differ
diff --git a/icons/herp48.png b/icons/herp48.png
new file mode 100644
index 0000000..856f87a
Binary files /dev/null and b/icons/herp48.png differ
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..1eb2bf7
--- /dev/null
+++ b/index.js
@@ -0,0 +1,103 @@
+// returns a random int from 1 to max
+const randomInt = max => Math.floor(Math.random() * max);
+
+// builds a string with random herps and derps
+const derpString = (length = 20) => {
+  const randomLength = randomInt(length) + 1;
+  const randomDerp = randomInt(2) ? "herp" : "derp";
+
+  return Array.from({ length: randomLength }, () => randomDerp).join(" ");
+};
+
+// herp derps an element
+const derpElement = element => {
+  const c = element;
+  // preserve the original contents
+  c.derpOriginal = c.textContent;
+  // swap between the two when clicked
+  c.onclick = () => {
+    c.clicked = !c.clicked;
+    c.textContent = c.clicked ? c.derpOriginal : c.derpString;
+  };
+
+  // add derped class
+  c.classList.add("derped");
+  // create a derp string for this comment
+  c.derpString = derpString();
+  // change the contents
+  c.textContent = c.derpString;
+  c.clicked = false;
+};
+
+const checkComment = commentElement => {
+  const c = commentElement;
+  // if everything is fine, return
+  if (c.clicked && c.textContent === c.derpOriginal) return;
+  if (!c.clicked && c.textContent === c.derpString) return;
+
+  // otherwise, fix the comment
+  // the only case of malformed comment encountered so far are these two cases:
+  if (c.textContent.indexOf(c.derpString) !== -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.derpString.length;
+    c.derpOriginal = c.textContent.substring(idx);
+    c.textContent = c.textContent.substring(0, idx);
+    c.clicked = 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.derpString;
+    c.clicked = false;
+  }
+};
+
+const init = commentsSection => {
+  // selectors for comments
+  const selectors = ["#content-text"];
+  // build the full selector string
+  const derpSelectorString = selectors
+    .map(sel => `${sel}:not(.derped)`)
+    .join(", ");
+  const checkSelectorString = selectors.map(sel => `${sel}.derped`).join(", ");
+
+  // Only watch for child list changes, as we're watching the comments container
+  const mutationConfig = { attributes: false, childList: true, subtree: true };
+
+  // Create a MutationObserver
+  // This object will monitor the comments for DOM changes
+  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(checkSelectorString).forEach(checkComment);
+    // Derp all un-derped comments
+    document.querySelectorAll(derpSelectorString).forEach(derpElement);
+  });
+
+  observer.observe(commentsSection, mutationConfig);
+};
+
+// 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 checkCommentsLoaded = () => {
+  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("html body ytd-app ytd-comments ytd-item-section-renderer #contents");
+    if (commentsSection !== null) {
+      init(commentsSection);
+    }
+    else {
+      checkCommentsLoaded();
+    }
+  }, 500);
+};
+
+checkCommentsLoaded();
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..7f2b2c8
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,23 @@
+{
+  "manifest_version": 2,
+  "name": "YouTube Herp Derp",
+  "description": "Replaces YouTube comments with herp derps. Forked from github.com/twstokes/herpderp",
+  "homepage_url": "https://michael.is",
+  "version": "1.0",
+  "icons": {
+    "48": "icons/herp48.png",
+    "128": "icons/herp128.png"
+  },
+  "content_scripts": [
+    {
+      "run_at": "document_idle",
+      "all_frames": false, // Ignore iframes.
+      "matches": [
+        "https://apis.google.com/*",
+        "https://plus.googleapis.com/*",
+        "https://www.youtube.com/*"
+      ],
+      "js": ["./index.js"]
+    }
+  ]
+}