MonkeyType AutoTyper Bot (2025new)

A Bot that automatically types for you in MonkeyType (Fixed Version)

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MonkeyType AutoTyper Bot (2025new)
// @namespace    https://greasyfork.org/users/1546585
// @version      3.1
// @description  A Bot that automatically types for you in MonkeyType (Fixed Version)
// @author       Liksss
// @match        *://monkeytype.com/*
// @icon         https://th.bing.com/th/id/R.c8397fb766c4397fea8a8b499c15a453?rik=aROX42RoH7HhXw&pid=ImgRaw&r=0
// @run-at       document-idle
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";

    class AutoTyper {
        constructor() {
            this.isTyping = false;
            this.timeoutId = null;
            this.config = {
                minDelay: 100,
                maxDelay: 333,
                accuracy: 0.95,
                wpm: 50,
                pauseDelay: 100,
                mode: 'basic' // 'basic' or 'advanced'
            };

            this.TOGGLE_KEY = "ArrowRight";
            this.init();
        }

        init() {
            this.setupEventListeners();
            this.createGUI();
            this.loadConfig();

            console.log("MonkeyType AutoTyper Bot (Fixed) loaded. Press ArrowRight to toggle.");
        }

        setupEventListeners() {
            // 切换打字状态
            window.addEventListener("keydown", (event) => {
                if (event.code === this.TOGGLE_KEY && !event.repeat) {
                    event.preventDefault();
                    this.toggleTyping();
                }
            });

            // 监听页面卸载,清理定时器
            window.addEventListener("beforeunload", () => {
                this.stopTyping();
            });
        }

        toggleTyping() {
            this.isTyping = !this.isTyping;

            if (this.isTyping) {
                console.log("STARTED TYPING TEST");
                this.startTyping();
            } else {
                console.log("STOPPED TYPING TEST");
                this.stopTyping();
            }
        }

        canType() {
            const typingTest = document.getElementById("typingTest");
            if (!typingTest) return false;

            const isHidden = typingTest.classList.contains("hidden");
            if (isHidden) {
                this.isTyping = false;
                return false;
            }

            return this.isTyping;
        }

        getNextCharacter() {
            const currentWord = document.querySelector(".word.active");
            if (!currentWord) return " ";

            for (const letter of currentWord.children) {
                if (letter.className === "") {
                    return letter.textContent;
                }
            }

            return " ";
        }

        // 完整的键盘布局映射(包括数字、符号、大写)
        getAdjacentKey(key) {
            const keyboardLayout = {
                // 数字行
                '`': ['1', '~'],
                '1': ['`', '2', 'q', '!'],
                '2': ['1', '3', 'q', 'w', '@'],
                '3': ['2', '4', 'w', 'e', '#'],
                '4': ['3', '5', 'e', 'r', '$'],
                '5': ['4', '6', 'r', 't', '%'],
                '6': ['5', '7', 't', 'y', '^'],
                '7': ['6', '8', 'y', 'u', '&'],
                '8': ['7', '9', 'u', 'i', '*'],
                '9': ['8', '0', 'i', 'o', '('],
                '0': ['9', '-', 'o', 'p', ')'],
                '-': ['0', '=', 'p', '[', '_'],
                '=': ['-', ']', '[', '\\', '+'],

                // 第一行(字母)
                'q': ['1', '2', 'w', 'a'],
                'w': ['q', 'e', 'a', 's', '2', '3'],
                'e': ['w', 'r', 's', 'd', '3', '4'],
                'r': ['e', 't', 'd', 'f', '4', '5'],
                't': ['r', 'y', 'f', 'g', '5', '6'],
                'y': ['t', 'u', 'g', 'h', '6', '7'],
                'u': ['y', 'i', 'h', 'j', '7', '8'],
                'i': ['u', 'o', 'j', 'k', '8', '9'],
                'o': ['i', 'p', 'k', 'l', '9', '0'],
                'p': ['o', '[', 'l', ';', '0', '-'],

                // 第二行
                'a': ['q', 'w', 's', 'z'],
                's': ['w', 'e', 'a', 'd', 'z', 'x'],
                'd': ['e', 'r', 's', 'f', 'x', 'c'],
                'f': ['r', 't', 'd', 'g', 'c', 'v'],
                'g': ['t', 'y', 'f', 'h', 'v', 'b'],
                'h': ['y', 'u', 'g', 'j', 'b', 'n'],
                'j': ['u', 'i', 'h', 'k', 'n', 'm'],
                'k': ['i', 'o', 'j', 'l', 'm', ','],
                'l': ['o', 'p', 'k', ';', ',', '.'],

                // 第三行
                'z': ['a', 's', 'x'],
                'x': ['s', 'd', 'z', 'c'],
                'c': ['d', 'f', 'x', 'v'],
                'v': ['f', 'g', 'c', 'b'],
                'b': ['g', 'h', 'v', 'n'],
                'n': ['h', 'j', 'b', 'm'],
                'm': ['j', 'k', 'n', ','],

                // 符号
                '[': ['p', ']', ';', "'"],
                ']': ['[', '\\', "'", 'Enter'],
                '\\': [']', 'Enter'],
                ';': ['l', "'", 'p', '[', '.', '/'],
                "'": [';', 'Enter', '[', ']'],
                ',': ['m', '.', 'k', 'l'],
                '.': [',', '/', 'l', ';'],
                '/': ['.', ';'],

                // 大写字母映射到小写
                'Q': ['1', '2', 'w', 'a'],
                'W': ['q', 'e', 'a', 's', '2', '3'],
                'E': ['w', 'r', 's', 'd', '3', '4'],
                'R': ['e', 't', 'd', 'f', '4', '5'],
                'T': ['r', 'y', 'f', 'g', '5', '6'],
                'Y': ['t', 'u', 'g', 'h', '6', '7'],
                'U': ['y', 'i', 'h', 'j', '7', '8'],
                'I': ['u', 'o', 'j', 'k', '8', '9'],
                'O': ['i', 'p', 'k', 'l', '9', '0'],
                'P': ['o', '[', 'l', ';', '0', '-'],
                'A': ['q', 'w', 's', 'z'],
                'S': ['w', 'e', 'a', 'd', 'z', 'x'],
                'D': ['e', 'r', 's', 'f', 'x', 'c'],
                'F': ['r', 't', 'd', 'g', 'c', 'v'],
                'G': ['t', 'y', 'f', 'h', 'v', 'b'],
                'H': ['y', 'u', 'g', 'j', 'b', 'n'],
                'J': ['u', 'i', 'h', 'k', 'n', 'm'],
                'K': ['i', 'o', 'j', 'l', 'm', ','],
                'L': ['o', 'p', 'k', ';', ',', '.'],
                'Z': ['a', 's', 'x'],
                'X': ['s', 'd', 'z', 'c'],
                'C': ['d', 'f', 'x', 'v'],
                'V': ['f', 'g', 'c', 'b'],
                'B': ['g', 'h', 'v', 'n'],
                'N': ['h', 'j', 'b', 'm'],
                'M': ['j', 'k', 'n', ','],

                // 空格特殊处理
                ' ': [' ']
            };

            const lowerKey = key.toLowerCase();
            const mappingKey = keyboardLayout.hasOwnProperty(key) ? key :
                              keyboardLayout.hasOwnProperty(lowerKey) ? lowerKey : null;

            if (mappingKey && keyboardLayout[mappingKey]) {
                const adjacentKeys = keyboardLayout[mappingKey];
                const randomIndex = Math.floor(Math.random() * adjacentKeys.length);
                return adjacentKeys[randomIndex];
            }

            // 对于未定义的键,返回原字符
            return key;
        }

        pressKey(key) {
            const wordsInput = document.getElementById("wordsInput");
            if (!wordsInput) return;

            // 创建并触发 keydown 事件
            const keydownEvent = new KeyboardEvent('keydown', {
                key: key,
                code: key === ' ' ? 'Space' : `Key${key.toUpperCase()}`,
                keyCode: key.charCodeAt(0),
                which: key.charCodeAt(0),
                bubbles: true,
                cancelable: true,
                composed: true
            });
            wordsInput.dispatchEvent(keydownEvent);

            // 更新输入框的值
            wordsInput.value += key;

            // 创建并触发 input 事件
            const inputEvent = new InputEvent('input', {
                inputType: 'insertText',
                data: key,
                bubbles: true,
                cancelable: false,
                composed: true
            });
            wordsInput.dispatchEvent(inputEvent);

            // 创建并触发 keyup 事件
            const keyupEvent = new KeyboardEvent('keyup', {
                key: key,
                code: key === ' ' ? 'Space' : `Key${key.toUpperCase()}`,
                keyCode: key.charCodeAt(0),
                which: key.charCodeAt(0),
                bubbles: true,
                cancelable: true,
                composed: true
            });
            wordsInput.dispatchEvent(keyupEvent);

            // 触发 change 事件
            const changeEvent = new Event('change', { bubbles: true });
            wordsInput.dispatchEvent(changeEvent);
        }

        typeCharacter() {
            if (!this.canType()) {
                this.stopTyping();
                return;
            }

            const nextChar = this.getNextCharacter();
            const randomValue = Math.random();
            const accuracy = parseFloat(this.config.accuracy);

            // 修复的错误模拟逻辑 - 使用单一随机数
            if (randomValue > accuracy) {
                // 发生错误
                const errorType = Math.random();

                if (errorType < 0.33) {
                    // 跳过字符
                    console.log(`[ERROR] Skipped character: "${nextChar}"`);
                } else if (errorType < 0.66) {
                    // 重复字符
                    console.log(`[ERROR] Repeated character: "${nextChar}"`);
                    this.pressKey(nextChar);
                    this.pressKey(nextChar);
                } else {
                    // 输入相邻错误字符
                    const wrongChar = this.getAdjacentKey(nextChar);
                    console.log(`[ERROR] Typed "${wrongChar}" instead of "${nextChar}"`);
                    this.pressKey(wrongChar);

                    // 模拟退格修正错误(可选)
                    // 注意:MonkeyType可能不需要退格,因为它会标记错误
                }
            } else {
                // 正确输入
                this.pressKey(nextChar);
            }

            // 计算下一个字符的延迟
            let delay;
            if (this.config.mode === 'basic') {
                // 基础模式:根据WPM计算延迟
                // WPM = (字符数/5) / (分钟)
                // 每个字符的延迟 = 60000 / (WPM * 5)
                delay = 60000 / (this.config.wpm * 5);
            } else {
                // 高级模式:随机延迟
                delay = this.random(this.config.minDelay, this.config.maxDelay);
            }

            // 单词间的额外暂停
            if (nextChar === " ") {
                delay += this.config.pauseDelay;
            }

            // 安排下一个字符
            this.timeoutId = setTimeout(() => {
                this.typeCharacter();
            }, delay);
        }

        random(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        startTyping() {
            if (!this.canType()) return;
            this.typeCharacter();
        }

        stopTyping() {
            this.isTyping = false;
            if (this.timeoutId) {
                clearTimeout(this.timeoutId);
                this.timeoutId = null;
            }
        }

        createGUI() {
            // 移除已存在的GUI
            const existingGUI = document.getElementById('monkeytype-autotyper-gui');
            if (existingGUI) {
                existingGUI.remove();
            }

            const gui = document.createElement('div');
            gui.id = 'monkeytype-autotyper-gui';
            gui.style.cssText = `
                position: fixed;
                bottom: 30%;
                right: 0;
                transform: translateY(50%);
                padding: 10px;
                background: rgba(0, 0, 0, 0.85);
                color: white;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                font-size: 12px;
                z-index: 10000;
                border-radius: 8px 0 0 8px;
                border: 1px solid #444;
                box-shadow: -2px 2px 10px rgba(0,0,0,0.5);
                min-width: 200px;
                backdrop-filter: blur(5px);
            `;

            gui.innerHTML = `
                <div style="margin-bottom: 8px; font-weight: bold; border-bottom: 1px solid #555; padding-bottom: 5px;">
                    🐵 AutoTyper Bot 
                </div>

                <div style="display: flex; flex-direction: column; gap: 8px;">
                    <div style="display: flex; gap: 5px; margin-bottom: 5px;">
                        <button id="basicBtn" class="mode-btn active">Basic</button>
                        <button id="advancedBtn" class="mode-btn">Advanced</button>
                    </div>

                    <div id="basicSection" class="section">
                        <div class="slider-group">
                            <label>WPM: <span id="wpmValue">50</span></label>
                            <input type="range" id="wpmSlider" min="10" max="150" step="5" value="50" class="slider">
                        </div>
                    </div>

                    <div id="advancedSection" class="section" style="display: none;">
                        <div class="slider-group">
                            <label>Min Delay: <span id="minDelayValue">100ms</span></label>
                            <input type="range" id="minDelaySlider" min="0" max="500" step="10" value="100" class="slider">
                        </div>
                        <div class="slider-group">
                            <label>Max Delay: <span id="maxDelayValue">333ms</span></label>
                            <input type="range" id="maxDelaySlider" min="0" max="1000" step="10" value="333" class="slider">
                        </div>
                        <div class="slider-group">
                            <label>Pause Delay: <span id="pauseDelayValue">100ms</span></label>
                            <input type="range" id="pauseDelaySlider" min="0" max="500" step="10" value="100" class="slider">
                        </div>
                    </div>

                    <div class="slider-group">
                        <label>Accuracy: <span id="accuracyValue">95%</span></label>
                        <input type="range" id="accuracySlider" min="0.5" max="1" step="0.01" value="0.95" class="slider">
                    </div>

                    <div style="display: flex; justify-content: space-between; margin-top: 10px;">
                        <button id="resetBtn" class="btn">Reset</button>
                        <div style="color: #aaa; font-size: 10px;">
                            Toggle: ArrowRight
                        </div>
                    </div>

                    <div id="status" style="margin-top: 5px; font-size: 10px; color: #4CAF50;">
                        Ready
                    </div>
                </div>

                <style>
                    .slider {
                        width: 100%;
                        height: 6px;
                        background: #333;
                        outline: none;
                        -webkit-appearance: none;
                        border-radius: 3px;
                    }

                    .slider::-webkit-slider-thumb {
                        -webkit-appearance: none;
                        width: 16px;
                        height: 16px;
                        background: #4CAF50;
                        border-radius: 50%;
                        cursor: pointer;
                    }

                    .btn, .mode-btn {
                        background: #4CAF50;
                        color: white;
                        border: none;
                        padding: 5px 10px;
                        border-radius: 4px;
                        cursor: pointer;
                        font-size: 11px;
                        transition: background 0.2s;
                    }

                    .btn:hover, .mode-btn:hover {
                        background: #45a049;
                    }

                    .mode-btn.active {
                        background: #2196F3;
                    }

                    .slider-group {
                        margin-bottom: 8px;
                    }

                    .slider-group label {
                        display: block;
                        margin-bottom: 3px;
                        font-size: 11px;
                    }

                    .section {
                        transition: all 0.3s ease;
                    }
                </style>
            `;

            document.body.appendChild(gui);
            this.setupGUIListeners();
        }

        setupGUIListeners() {
            const debounce = (func, wait) => {
                let timeout;
                return function executedFunction(...args) {
                    const later = () => {
                        clearTimeout(timeout);
                        func(...args);
                    };
                    clearTimeout(timeout);
                    timeout = setTimeout(later, wait);
                };
            };

            // 模式切换
            document.getElementById('basicBtn').addEventListener('click', () => {
                this.config.mode = 'basic';
                document.getElementById('basicBtn').classList.add('active');
                document.getElementById('advancedBtn').classList.remove('active');
                document.getElementById('basicSection').style.display = 'block';
                document.getElementById('advancedSection').style.display = 'none';
                this.saveConfig();
            });

            document.getElementById('advancedBtn').addEventListener('click', () => {
                this.config.mode = 'advanced';
                document.getElementById('advancedBtn').classList.add('active');
                document.getElementById('basicBtn').classList.remove('active');
                document.getElementById('basicSection').style.display = 'none';
                document.getElementById('advancedSection').style.display = 'block';
                this.saveConfig();
            });

            // 滑块事件监听器(使用防抖)
            const updateStatus = () => {
                const status = document.getElementById('status');
                status.textContent = 'Settings saved';
                status.style.color = '#4CAF50';
                setTimeout(() => {
                    status.textContent = this.isTyping ? 'Typing...' : 'Ready';
                }, 2000);
            };

            const saveConfigDebounced = debounce(() => {
                this.saveConfig();
                updateStatus();
            }, 500);

            // WPM 滑块
            const wpmSlider = document.getElementById('wpmSlider');
            const wpmValue = document.getElementById('wpmValue');
            wpmSlider.addEventListener('input', () => {
                this.config.wpm = parseInt(wpmSlider.value);
                wpmValue.textContent = wpmSlider.value;
                saveConfigDebounced();
            });

            // 最小延迟滑块
            const minDelaySlider = document.getElementById('minDelaySlider');
            const minDelayValue = document.getElementById('minDelayValue');
            minDelaySlider.addEventListener('input', () => {
                this.config.minDelay = parseInt(minDelaySlider.value);
                minDelayValue.textContent = `${this.config.minDelay}ms`;
                saveConfigDebounced();
            });

            // 最大延迟滑块
            const maxDelaySlider = document.getElementById('maxDelaySlider');
            const maxDelayValue = document.getElementById('maxDelayValue');
            maxDelaySlider.addEventListener('input', () => {
                this.config.maxDelay = parseInt(maxDelaySlider.value);
                maxDelayValue.textContent = `${this.config.maxDelay}ms`;
                saveConfigDebounced();
            });

            // 暂停延迟滑块
            const pauseDelaySlider = document.getElementById('pauseDelaySlider');
            const pauseDelayValue = document.getElementById('pauseDelayValue');
            pauseDelaySlider.addEventListener('input', () => {
                this.config.pauseDelay = parseInt(pauseDelaySlider.value);
                pauseDelayValue.textContent = `${this.config.pauseDelay}ms`;
                saveConfigDebounced();
            });

            // 准确率滑块
            const accuracySlider = document.getElementById('accuracySlider');
            const accuracyValue = document.getElementById('accuracyValue');
            accuracySlider.addEventListener('input', () => {
                this.config.accuracy = parseFloat(accuracySlider.value);
                accuracyValue.textContent = `${Math.round(this.config.accuracy * 100)}%`;
                saveConfigDebounced();
            });

            // 重置按钮
            document.getElementById('resetBtn').addEventListener('click', () => {
                this.resetConfig();
            });
        }

        resetConfig() {
            this.config = {
                minDelay: 100,
                maxDelay: 333,
                accuracy: 0.95,
                wpm: 50,
                pauseDelay: 100,
                mode: 'basic'
            };

            // 更新滑块
            document.getElementById('wpmSlider').value = this.config.wpm;
            document.getElementById('wpmValue').textContent = this.config.wpm;

            document.getElementById('minDelaySlider').value = this.config.minDelay;
            document.getElementById('minDelayValue').textContent = `${this.config.minDelay}ms`;

            document.getElementById('maxDelaySlider').value = this.config.maxDelay;
            document.getElementById('maxDelayValue').textContent = `${this.config.maxDelay}ms`;

            document.getElementById('pauseDelaySlider').value = this.config.pauseDelay;
            document.getElementById('pauseDelayValue').textContent = `${this.config.pauseDelay}ms`;

            document.getElementById('accuracySlider').value = this.config.accuracy;
            document.getElementById('accuracyValue').textContent = `${Math.round(this.config.accuracy * 100)}%`;

            // 切换回基础模式
            this.config.mode = 'basic';
            document.getElementById('basicBtn').click();

            this.saveConfig();

            const status = document.getElementById('status');
            status.textContent = 'Settings reset to default';
            status.style.color = '#FF9800';
            setTimeout(() => {
                status.textContent = 'Ready';
                status.style.color = '#4CAF50';
            }, 2000);
        }

        saveConfig() {
            localStorage.setItem('monkeytype-autotyper-config', JSON.stringify(this.config));
        }

        loadConfig() {
            try {
                const savedConfig = localStorage.getItem('monkeytype-autotyper-config');
                if (savedConfig) {
                    const config = JSON.parse(savedConfig);
                    this.config = { ...this.config, ...config };

                    // 更新GUI
                    if (document.getElementById('wpmSlider')) {
                        document.getElementById('wpmSlider').value = this.config.wpm;
                        document.getElementById('wpmValue').textContent = this.config.wpm;

                        document.getElementById('minDelaySlider').value = this.config.minDelay;
                        document.getElementById('minDelayValue').textContent = `${this.config.minDelay}ms`;

                        document.getElementById('maxDelaySlider').value = this.config.maxDelay;
                        document.getElementById('maxDelayValue').textContent = `${this.config.maxDelay}ms`;

                        document.getElementById('pauseDelaySlider').value = this.config.pauseDelay;
                        document.getElementById('pauseDelayValue').textContent = `${this.config.pauseDelay}ms`;

                        document.getElementById('accuracySlider').value = this.config.accuracy;
                        document.getElementById('accuracyValue').textContent = `${Math.round(this.config.accuracy * 100)}%`;

                        // 设置模式
                        if (this.config.mode === 'advanced') {
                            document.getElementById('advancedBtn').click();
                        } else {
                            document.getElementById('basicBtn').click();
                        }
                    }
                }
            } catch (error) {
                console.error('Failed to load config:', error);
            }
        }
    }

    // 等待页面加载完成后初始化
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                new AutoTyper();
            });
        } else {
            new AutoTyper();
        }
    }

    // 防止重复加载
    if (!window.monkeyTypeAutoTyperInstance) {
        window.monkeyTypeAutoTyperInstance = true;
        init();
    }
})();