Human-Typer (Advanced) - Google Docs & Slides

Types your text in a human-like manner with typos and variable speed. https://greasyfork.org/en/users/449798-ace-dx

目前为 2024-09-07 提交的版本。查看 最新版本

// ==UserScript==
// @name         Human-Typer (Advanced) - Google Docs & Slides
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Types your text in a human-like manner with typos and variable speed. https://greasyfork.org/en/users/449798-ace-dx
// @author       ∫(Ace)³dx
// @match        https://docs.google.com/*
// @icon         https://i.imgur.com/z2gxKWZ.png
// @grant        none
// @license MIT
// ==/UserScript==

if (window.location.href.includes("docs.google.com/document/d") || window.location.href.includes("docs.google.com/presentation/d")) {
    console.log("Document opened, Human-Typer available!");

    // 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");
    if (helpMenu) {
        helpMenu.parentNode.insertBefore(humanTyperButton, helpMenu);
        humanTyperButton.parentNode.insertBefore(stopButton, humanTyperButton.nextSibling);
    } else {
        console.error("Help menu not found. Ensure you're on a Google Docs or Slides page.");
    }
    
    let cancelTyping = false;
    let typingInProgress = false;
    let lowerBoundWPM = 20; // Default lower bound WPM
    let upperBoundWPM = 60; // Default upper bound WPM
    let typingDuration = 60; // Default typing duration in minutes
    let introduceTypos = false;
    let fluctuateSpeed = false;
    
    // Function to create and show the overlay
    function showOverlay() {
        return new Promise((resolve) => {
            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 = "Keep this tab open; otherwise, the script will pause and resume when you return. Customize typing speed and duration.";
            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 WPM: ";
            const lowerBoundInput = document.createElement("input");
            lowerBoundInput.type = "number";
            lowerBoundInput.min = "0";
            lowerBoundInput.value = lowerBoundWPM; // 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 WPM: ";
            const upperBoundInput = document.createElement("input");
            upperBoundInput.type = "number";
            upperBoundInput.min = "0";
            upperBoundInput.value = upperBoundWPM; // 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 durationLabel = document.createElement("label");
            durationLabel.textContent = "Duration (minutes): ";
            const durationSelect = document.createElement("select");
            [60, 120, 180, 240].forEach((min) => {
                const option = document.createElement("option");
                option.value = min;
                option.textContent = `${min / 60} hour${min > 60 ? 's' : ''}`;
                durationSelect.appendChild(option);
            });
            durationSelect.value = typingDuration; // Set the value from the stored variable
            
            const typoCheckbox = document.createElement("input");
            typoCheckbox.type = "checkbox";
            typoCheckbox.id = "introduce-typos";
            typoCheckbox.checked = introduceTypos;
            const typoLabel = document.createElement("label");
            typoLabel.htmlFor = "introduce-typos";
            typoLabel.textContent = "Introduce typos";
            
            const fluctuationCheckbox = document.createElement("input");
            fluctuationCheckbox.type = "checkbox";
            fluctuationCheckbox.id = "fluctuate-speed";
            fluctuationCheckbox.checked = fluctuateSpeed;
            const fluctuationLabel = document.createElement("label");
            fluctuationLabel.htmlFor = "fluctuate-speed";
            fluctuationLabel.textContent = "Fluctuate typing speed";
            
            const confirmButton = document.createElement("button");
            confirmButton.textContent = "Confirm";
            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(durationLabel);
            overlay.appendChild(durationSelect);
            overlay.appendChild(document.createElement("br"));
            overlay.appendChild(typoCheckbox);
            overlay.appendChild(typoLabel);
            overlay.appendChild(document.createElement("br"));
            overlay.appendChild(fluctuationCheckbox);
            overlay.appendChild(fluctuationLabel);
            overlay.appendChild(document.createElement("br"));
            overlay.appendChild(confirmButton);
            document.body.appendChild(overlay);
            
            const updateRandomDelayLabel = () => {
                const charCount = textField.value.length;
                const eta = Math.ceil((charCount / (lowerBoundWPM * 5 / 60)) / 60); // Estimate in hours
                randomDelayLabel.textContent = `ETA: ${eta} hour(s)`;
            };
            
            const handleConfirmClick = () => {
                const userInput = textField.value.trim();
                lowerBoundWPM = parseInt(lowerBoundInput.value);
                upperBoundWPM = parseInt(upperBoundInput.value);
                typingDuration = parseInt(durationSelect.value);
                introduceTypos = typoCheckbox.checked;
                fluctuateSpeed = fluctuationCheckbox.checked;
                
                if (userInput === "" || isNaN(lowerBoundWPM) || isNaN(upperBoundWPM) || lowerBoundWPM < 0 || upperBoundWPM < lowerBoundWPM) {
                    document.body.removeChild(overlay);
                    return;
                }
                
                typingInProgress = true; // Typing has started
                stopButton.style.display = "inline"; // Show the stop button
                document.body.removeChild(overlay);
                resolve({ userInput });
            };
            
            confirmButton.addEventListener("click", handleConfirmClick);
            textField.addEventListener("input", updateRandomDelayLabel);
            lowerBoundInput.addEventListener("input", updateRandomDelayLabel);
            upperBoundInput.addEventListener("input", updateRandomDelayLabel);
            durationSelect.addEventListener("change", updateRandomDelayLabel);
        });
    }
    
    function introduceTypingErrors(text) {
        // Introduce random typos
        let typoText = "";
        for (let i = 0; i < text.length; i++) {
            if (Math.random() < 0.05) { // 5% chance of typo
                typoText += String.fromCharCode(Math.floor(Math.random() * 26) + 97); // Random lowercase letter
            } else {
                typoText += text[i];
            }
        }
        return typoText;
    }
    
    function correctTypos(text, originalText) {
        // Correct typos
        let correctedText = "";
        let typoIndex = 0;
        for (let i = 0; i < originalText.length; i++) {
            if (typoIndex < text.length && text[typoIndex] !== originalText[i]) {
                correctedText += originalText[i];
                typoIndex++;
            } else {
                correctedText += text[typoIndex] || "";
                typoIndex++;
            }
        }
        return correctedText;
    }
    
    function calculateTypingDelay(wpm) {
        const charsPerWord = 5; // Average word length
        const minutesPerHour = 60;
        const msPerMinute = 60000;
        const totalCharsPerMinute = wpm * charsPerWord;
        return msPerMinute / totalCharsPerMinute;
    }
    
    function getRandomDelay() {
        const baseDelay = calculateTypingDelay((Math.random() * (upperBoundWPM - lowerBoundWPM)) + lowerBoundWPM);
        return fluctuateSpeed ? baseDelay * (0.5 + Math.random()) : baseDelay;
    }
    
    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) {
        const startTime = Date.now();
        const endTime = startTime + (typingDuration * 60 * 60 * 1000); // Convert duration to milliseconds
        let currentText = "";
        
        while (Date.now() < endTime) {
            for (let i = 0; i < string.length; i++) {
                const char = string[i];
                const delay = getRandomDelay();
                
                if (cancelTyping) {
                    stopButton.style.display = "none";
                    console.log("Typing cancelled");
                    return;
                }
                
                await simulateTyping(inputElement, char, delay);
                
                if (introduceTypos && Math.random() < 0.05) {
                    currentText = introduceTypingErrors(currentText);
                }
                
                if (correctTypos(currentText, string) === string) {
                    continue;
                }
            }
        }
    
        typingInProgress = false; // Typing has finished
        stopButton.style.display = "none"; // Hide the stop button
    }
    
    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;
    
            typeStringWithRandomDelay(input, userInput);
        }
    });
} else {
    console.log("Document not open, Human-Typer not available.");
}