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 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Human-Typer (Advanced) - Google Docs & Slides
// @namespace    http://tampermonkey.net/
// @version      1.3
// @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")) {
    // Your script code here
    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");
    helpMenu.parentNode.insertBefore(humanTyperButton, helpMenu);
    humanTyperButton.parentNode.insertBefore(stopButton, humanTyperButton.nextSibling);
    
    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() {
        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);
        
        return new Promise((resolve) => {
            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); // Store the updated value
                upperBoundWPM = parseInt(upperBoundInput.value); // Store the updated value
                typingDuration = parseInt(durationSelect.value); // Store the updated value
                introduceTypos = typoCheckbox.checked;
                fluctuateSpeed = fluctuationCheckbox.checked;
                
                if (userInput === "") {
                    document.body.removeChild(overlay);
                    return;
                }
                
                if (isNaN(lowerBoundWPM) || isNaN(upperBoundWPM) || lowerBoundWPM < 0 || upperBoundWPM < lowerBoundWPM) 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.");
}