Human-Typer - Google Docs

Types your text in a human-like manner so the edit history shows the progress. https://greasyfork.org/en/users/449798-ace-dx

Versión del día 27/8/2023. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Human-Typer - Google Docs
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Types your text in a human-like manner so the edit history shows the progress. https://greasyfork.org/en/users/449798-ace-dx
// @author       ∫(Ace)³dx
// @match        https://docs.google.com/*
// @icon         https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Google_Docs_logo_%282014-2020%29.svg/1481px-Google_Docs_logo_%282014-2020%29.svg.png
// @grant        none
// @license MIT
// ==/UserScript==

//This was a better option than using match in the script as otherwise you would need to reload the document page if you open a document from going first to docs.google.com.
if (window.location.href.includes("docs.google.com/document/d")) {
    // Your script code here
    console.log("Document opened, Human-Typer available!");
    // Add your script logic here

// Create the "Human-Typer" button
const humanTyperButton = document.createElement("div");
humanTyperButton.textContent = "Human-Typer";
humanTyperButton.classList.add("menu-button", "goog-control", "goog-inline-block");
humanTyperButton.style.userSelect = "none";
humanTyperButton.setAttribute("aria-haspopup", "true");
humanTyperButton.setAttribute("aria-expanded", "false");
humanTyperButton.setAttribute("aria-disabled", "false");
humanTyperButton.setAttribute("role", "menuitem");
humanTyperButton.id = "human-typer-button";
humanTyperButton.style.transition = "color 0.3s";

// Create the "Stop" button
const stopButton = document.createElement("div");
stopButton.textContent = "Stop";
stopButton.classList.add("menu-button", "goog-control", "goog-inline-block");
stopButton.style.userSelect = "none";
stopButton.style.color = "red";
stopButton.style.cursor = "pointer";
stopButton.style.transition = "color 0.3s";
stopButton.id = "stop-button";
stopButton.style.display = "none";

// Insert the buttons into the page
const helpMenu = document.getElementById("docs-help-menu");
helpMenu.parentNode.insertBefore(humanTyperButton, helpMenu);
humanTyperButton.parentNode.insertBefore(stopButton, humanTyperButton.nextSibling);

let cancelTyping = false;
let typingInProgress = false;
let lowerBoundValue = 200; // Default lower bound value
let upperBoundValue = 400; // Default upper bound value

// Function to create and show the overlay
function showOverlay() {
  const overlay = document.createElement("div");
  overlay.style.position = "fixed";
  overlay.style.top = "50%";
  overlay.style.left = "50%";
  overlay.style.transform = "translate(-50%, -50%)";
  overlay.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
  overlay.style.padding = "20px";
  overlay.style.borderRadius = "8px";
  overlay.style.boxShadow = "0px 2px 10px rgba(0, 0, 0, 0.1)";
  overlay.style.zIndex = "9999";
  overlay.style.display = "flex";
  overlay.style.flexDirection = "column";
  overlay.style.alignItems = "center";
  overlay.style.width = "320px";

  const textField = document.createElement("textarea");
  textField.rows = "5";
  textField.cols = "40";
  textField.placeholder = "Enter your text...";
  textField.style.marginBottom = "10px";
  textField.style.width = "100%";
  textField.style.padding = "8px";
  textField.style.border = "1px solid #ccc";
  textField.style.borderRadius = "4px";
  textField.style.resize = "vertical";

  const description = document.createElement("p");
  description.textContent = "It's necessary to keep this tab open; otherwise, the script will pause and will resume once you return to it (this behavior is caused by the way the browser functions). Lower bound is the minimum time in milliseconds per character. Upper bound is the maximum time in milliseconds per character. A random delay value will be selected between these bounds for every character in your text, ensuring that the typing appears natural and human-like.";
  description.style.fontSize = "14px";
  description.style.marginBottom = "15px";

  const randomDelayLabel = document.createElement("div");
  randomDelayLabel.style.marginBottom = "5px";

  const lowerBoundLabel = document.createElement("label");
  lowerBoundLabel.textContent = "Lower Bound (ms): ";
  const lowerBoundInput = document.createElement("input");
  lowerBoundInput.type = "number";
  lowerBoundInput.min = "0";
  lowerBoundInput.value = lowerBoundValue; // Set the value from the stored variable
  lowerBoundInput.style.marginRight = "10px";
  lowerBoundInput.style.padding = "6px";
  lowerBoundInput.style.border = "1px solid #ccc";
  lowerBoundInput.style.borderRadius = "4px";

  const upperBoundLabel = document.createElement("label");
  upperBoundLabel.textContent = "Upper Bound (ms): ";
  const upperBoundInput = document.createElement("input");
  upperBoundInput.type = "number";
  upperBoundInput.min = "0";
  upperBoundInput.value = upperBoundValue; // Set the value from the stored variable
  upperBoundInput.style.marginRight = "10px";
  upperBoundInput.style.padding = "6px";
  upperBoundInput.style.border = "1px solid #ccc";
  upperBoundInput.style.borderRadius = "4px";

  const confirmButton = document.createElement("button");
  confirmButton.textContent = textField.value.trim() === "" ? "Cancel" : "Confirm"; // Change button text based on textfield
  confirmButton.style.padding = "8px 16px";
  confirmButton.style.backgroundColor = "#1a73e8";
  confirmButton.style.color = "white";
  confirmButton.style.border = "none";
  confirmButton.style.borderRadius = "4px";
  confirmButton.style.cursor = "pointer";
  confirmButton.style.transition = "background-color 0.3s";

  overlay.appendChild(description);
  overlay.appendChild(textField);
  overlay.appendChild(randomDelayLabel);
  overlay.appendChild(lowerBoundLabel);
  overlay.appendChild(lowerBoundInput);
  overlay.appendChild(upperBoundLabel);
  overlay.appendChild(upperBoundInput);
  overlay.appendChild(document.createElement("br"));
  overlay.appendChild(confirmButton);
  document.body.appendChild(overlay);

  return new Promise((resolve) => {
    const updateRandomDelayLabel = () => {
      const charCount = textField.value.length;
      const etaLowerBound = Math.ceil((charCount * parseInt(lowerBoundInput.value)) / 60000);
      const etaUpperBound = Math.ceil((charCount * parseInt(upperBoundInput.value)) / 60000);
      randomDelayLabel.textContent = `ETA: ${etaLowerBound} - ${etaUpperBound} minutes`;
    };

    const handleCancelClick = () => {
      cancelTyping = true;
      stopButton.style.display = "none";
    };

    confirmButton.addEventListener("click", () => {
      const userInput = textField.value.trim();
      lowerBoundValue = parseInt(lowerBoundInput.value); // Store the updated value
      upperBoundValue = parseInt(upperBoundInput.value); // Store the updated value

      if (userInput === "") {
        document.body.removeChild(overlay);
        return;
      }

      if (isNaN(lowerBoundValue) || isNaN(upperBoundValue) || lowerBoundValue < 0 || upperBoundValue < lowerBoundValue) return;

      typingInProgress = true; // Typing has started
      stopButton.style.display = "inline"; // Show the stop button
      document.body.removeChild(overlay);
      resolve({ userInput });
    });

    textField.addEventListener("input", () => {
      confirmButton.textContent = textField.value.trim() === "" ? "Cancel" : "Confirm"; // Change button text based on textfield
      updateRandomDelayLabel();
    });

    lowerBoundInput.addEventListener("input", updateRandomDelayLabel);
    upperBoundInput.addEventListener("input", updateRandomDelayLabel);

    stopButton.addEventListener("click", handleCancelClick);
  });
}

humanTyperButton.addEventListener("mouseenter", () => {
  humanTyperButton.classList.add("goog-control-hover");
});

humanTyperButton.addEventListener("mouseleave", () => {
  humanTyperButton.classList.remove("goog-control-hover");
});

stopButton.addEventListener("mouseenter", () => {
  stopButton.classList.add("goog-control-hover");
});

stopButton.addEventListener("mouseleave", () => {
  stopButton.classList.remove("goog-control-hover");
});

humanTyperButton.addEventListener("click", async () => {
  if (typingInProgress) {
    console.log("Typing in progress, please wait...");
    return;
  }

  cancelTyping = false;
  stopButton.style.display = "none"; // Hide the stop button

  const { userInput } = await showOverlay();

  if (userInput !== "") {
    const input = document.querySelector(".docs-texteventtarget-iframe").contentDocument.activeElement;

    async function simulateTyping(inputElement, char, delay) {
      return new Promise((resolve) => {
        if (cancelTyping) {
          stopButton.style.display = "none";
          console.log("Typing cancelled");
          resolve();
          return;
        }

        setTimeout(() => {
          let eventObj;
          if (char === "\n") {
            eventObj = new KeyboardEvent("keydown", {
              bubbles: true,
              key: "Enter",
              code: "Enter",
              keyCode: 13,
              which: 13,
              charCode: 13,
            });
          } else {
            eventObj = new KeyboardEvent("keypress", {
              bubbles: true,
              key: char,
              charCode: char.charCodeAt(0),
              keyCode: char.charCodeAt(0),
              which: char.charCodeAt(0),
            });
          }

          inputElement.dispatchEvent(eventObj);
          console.log(`Typed: ${char}, Delay: ${delay}ms`);
          resolve();
        }, delay);
      });
    }

    async function typeStringWithRandomDelay(inputElement, string) {
      for (let i = 0; i < string.length; i++) {
        const char = string[i];
        const randomDelay = Math.floor(Math.random() * (upperBoundValue - lowerBoundValue + 1)) + lowerBoundValue; // Random delay within the specified range
        await simulateTyping(inputElement, char, randomDelay);
      }

      typingInProgress = false; // Typing has finished
      stopButton.style.display = "none"; // Hide the stop button
    }

    typeStringWithRandomDelay(input, userInput);
  }
});



    }

else {
    console.log("Document not open, Human-Typer not available.");
}