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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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.");
}