MonkeyType AutoTyper Bot

Combines the working V6.2 engine with V6.6 features and anti-cheat bypass.

您需要先安裝使用者腳本管理器擴展,如 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
// @namespace    https://greasyfork.org/users/1546585
// @version      6.9
// @description  Combines the working V6.2 engine with V6.6 features and anti-cheat bypass.
// @author       greedism
// @match        *://monkeytype.com/*
// @run-at       document-end
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";

    class AutoTyper {
        constructor() {
            this.isTyping = false;
            this.timeoutId = null;
            this.isHidden = false;
            this.wpmHistory = [];
            this.stats = { charsTyped: 0, errors: 0, startTime: null };
            
            this.loadSettings();
            this.init();
        }

        loadSettings() {
            // grab saved stuff, or use defaults if first time
            const sWpm = localStorage.getItem('mt-bot-wpm');
            const sAcc = localStorage.getItem('mt-bot-acc');
            const sDelay = localStorage.getItem('mt-bot-delay');
            
            this.config = {
                accuracy: sAcc ? parseFloat(sAcc) : 0.97,
                wpm: sWpm ? parseInt(sWpm) : 80,
                startDelay: sDelay ? parseInt(sDelay) : 500,
                humanMode: true
            };
        }

        init() {
            this.createGUI();
            this.makeDraggable(document.getElementById('monkeytype-autotyper-gui'));
            console.log("MT-Bot: Engine V6.9 Ready.");
        }

        getNextCharacter() {
            const word = document.querySelector(".word.active");
            if (!word) return " ";
            // loop through letters to find the one without a status class
            const letters = word.children;
            for (let i = 0; i < letters.length; i++) {
                if (letters[i].className === "" || letters[i].classList.contains("letter")) {
                    return letters[i].textContent;
                }
            }
            return " ";
        }

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

            const evInit = {
                key: k,
                code: k === " " ? "Space" : `Key${k.toUpperCase()}`,
                bubbles: true,
                cancelable: true,
                composed: true,
                which: k.charCodeAt(0),
                keyCode: k.charCodeAt(0)
            };

            input.dispatchEvent(new KeyboardEvent('keydown', evInit));
            
            // update the hidden input field so monkeytype sees it
            if (k !== " ") {
                input.value += k;
            } else {
                input.value = "";
            }

            input.dispatchEvent(new InputEvent('input', { 
                inputType: 'insertText', 
                data: k, 
                bubbles: true 
            }));
            
            input.dispatchEvent(new KeyboardEvent('keyup', evInit));
        }

        getHumanDelay(base, char) {
            const sd = base * 0.25;
            const u1 = Math.random();
            const u2 = Math.random();
            // normal distribution randomizer
            const z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
            let d = base + z * sd;

            // Speed up common bigrams - feels more like a real typist
            const common = ['th', 'he', 'in', 'er', 'an', 're', 'on', 'at', 'nd'];
            if (common.includes((this.lastChar || '') + char)) {
                d *= 0.62; 
            }

            // Random micro-pause (distraction/stumble simulation)
            if (Math.random() < 0.012) {
                d += (Math.random() * 250) + 150;
            }

            // Fatigue logic: slow down slightly as the test goes on
            const fatigue = 1 + (this.stats.charsTyped / 2200); 
            d *= fatigue;

            if (char === " ") d += (Math.random() * 140) + 45;

            this.lastChar = char;
            return Math.max(d, 24); 
        }

        typeCharacter() {
            if (!this.canType()) { this.stopTyping(); return; }
            
            const char = this.getNextCharacter();
            this.stats.charsTyped++;

            // Mistake Simulation
            if (Math.random() > this.config.accuracy) {
                this.stats.errors++;
                const chars = "abcdefghijklmnopqrstuvwxyz";
                const wrong = chars.charAt(Math.floor(Math.random() * chars.length));
                
                this.pressKey(wrong);
                
                // human-like reaction time to realize a mistake happened
                this.timeoutId = setTimeout(() => {
                    this.pressKey("Backspace");
                    this.timeoutId = setTimeout(() => this.typeCharacter(), this.getHumanDelay(115, char));
                }, 90 + Math.random() * 110);
                return;
            } else {
                this.pressKey(char);
            }

            let bDelay = (60000 / (this.config.wpm * 5));
            let finalD = this.config.humanMode ? this.getHumanDelay(bDelay, char) : bDelay;

            this.updateStats();
            this.timeoutId = setTimeout(() => this.typeCharacter(), finalD);
        }

        toggleTyping() {
            const btn = document.getElementById('mainStartBtn');
            if (!this.isTyping) {
                this.updateStatusText('Preparing...', '#e2b714');
                setTimeout(() => {
                    this.isTyping = true;
                    this.wpmHistory = [];
                    this.stats = { charsTyped: 0, errors: 0, startTime: Date.now() };
                    btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><rect x="4" y="4" width="16" height="16" rx="2"></rect></svg> STOP`;
                    btn.style.color = "#ff4444";
                    this.typeCharacter();
                }, this.config.startDelay);
            } else {
                this.stopTyping();
            }
        }

        stopTyping() {
            this.isTyping = false;
            clearTimeout(this.timeoutId);
            const btn = document.getElementById('mainStartBtn');
            if (btn) {
                btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg> START`;
                btn.style.color = "#fff";
            }
            this.updateStatusText('Ready', '#444');
        }

        updateStats() {
            const diff = (Date.now() - this.stats.startTime) / 1000;
            const wpm = Math.round((this.stats.charsTyped / 5) / (diff / 60)) || 0;
            document.getElementById('w-val').innerText = wpm;
            
            if (Math.floor(diff) > this.wpmHistory.length) {
                this.wpmHistory.push(wpm);
                if (this.wpmHistory.length > 25) this.wpmHistory.shift();
                this.drawGraph();
            }
            this.updateStatusText('<span class="pulse">●</span> Typing...', '#fff');
        }

        drawGraph() {
            const svg = document.getElementById('wpm-graph');
            if (!svg || this.wpmHistory.length < 2) return;
            const max = Math.max(...this.wpmHistory, 150);
            const pts = this.wpmHistory.map((w, i) => `${(i / (this.wpmHistory.length - 1)) * 240},${40 - (w / max) * 40}`).join(' ');
            svg.innerHTML = `<polyline points="${pts}" fill="none" stroke="#e2b714" stroke-width="2" stroke-linejoin="round" />`;
        }

        updateStatusText(t, c) { 
            const s = document.getElementById('status');
            if (s) { s.innerHTML = t; s.style.color = c; }
        }

        canType() { return !!document.querySelector('.word.active'); }

        makeDraggable(el) {
            let p1 = 0, p2 = 0, p3 = 0, p4 = 0;
            const h = document.getElementById('gui-header');
            h.onmousedown = (e) => {
                if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return;
                p3 = e.clientX; p4 = e.clientY;
                document.onmouseup = () => { document.onmouseup = null; document.onmousemove = null; };
                document.onmousemove = (ev) => {
                    p1 = p3 - ev.clientX; p2 = p4 - ev.clientY;
                    p3 = ev.clientX; p4 = ev.clientY;
                    el.style.top = (el.offsetTop - p2) + "px"; 
                    el.style.left = (el.offsetLeft - p1) + "px";
                    el.style.bottom = "auto"; el.style.right = "auto";
                };
            };
        }

        createGUI() {
            const gui = document.createElement('div');
            gui.id = 'monkeytype-autotyper-gui';
            gui.style.cssText = `position:fixed;top:100px;right:20px;width:280px;background:rgba(10,10,10,0.98);border:1px solid #222;border-radius:12px;color:#fff;font-family:'Lexend Deca',sans-serif;z-index:100000;backdrop-filter:blur(10px);box-shadow:0 10px 40px #000;`;

            gui.innerHTML = `
                <div id="gui-header" style="padding:12px;background:#111;border-radius:12px 12px 0 0;cursor:move;display:flex;justify-content:space-between;border-bottom:1px solid #222;font-size:11px;color:#e2b714;font-weight:bold;align-items:center;">
                    <span>MONKEY BOT STEALTH V6.9</span>
                    <button id="hideBtn" style="background:none;border:none;color:#444;cursor:pointer;font-size:9px;">HIDE</button>
                </div>
                <div style="padding:15px;">
                    <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:15px;">
                        <div class="stat-card">WPM <b id="w-val">0</b></div>
                        <div class="stat-card">ACC <b id="a-val">${Math.round(this.config.accuracy*100)}%</b></div>
                    </div>
                    
                    <div class="ctrl">
                        <label>Target WPM: <span id="v-wpm">${this.config.wpm}</span></label>
                        <input type="range" id="s-wpm" min="10" max="350" value="${this.config.wpm}">
                    </div>

                    <div class="ctrl">
                        <label>Start Delay:</label>
                        <div style="display:flex;gap:5px;margin-top:5px;">
                            <button class="d-btn ${this.config.startDelay === 0 ? 'active' : ''}" data-ms="0">Instant</button>
                            <button class="d-btn ${this.config.startDelay === 500 ? 'active' : ''}" data-ms="500">Normal</button>
                            <button class="d-btn ${this.config.startDelay === 2000 ? 'active' : ''}" data-ms="2000">Slow</button>
                        </div>
                    </div>

                    <button id="mainStartBtn"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg> START</button>
                    
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px;">
                        <button id="exportBtn" class="sec-btn">EXPORT</button>
                        <button id="importBtn" class="sec-btn">IMPORT</button>
                    </div>

                    <div style="background:#000; border: 1px solid #111; border-radius: 8px; height: 40px; margin-top: 15px; position:relative; overflow:hidden;">
                        <svg id="wpm-graph" width="240" height="40" style="position:absolute;left:5px;"></svg>
                    </div>

                    <div id="status" style="text-align:center;font-size:10px;color:#444;margin-top:10px;">Ready</div>
                </div>
                <style>
                    .stat-card { background:#000; padding:10px; border-radius:8px; border:1px solid #1a1a1a; text-align:center; font-size:10px; color:#555; }
                    .stat-card b { display:block; font-size:16px; color:#fff; }
                    .ctrl { margin-bottom:12px; }
                    .ctrl label { font-size:10px; color:#888; display:flex; justify-content:space-between; }
                    .ctrl label span { color:#e2b714; }
                    input[type=range] { width:100%; accent-color:#e2b714; }
                    #mainStartBtn { width:100%; background:#111; color:#fff; border:1px solid #333; padding:10px; border-radius:8px; font-weight:bold; cursor:pointer; display:flex; align-items:center; justify-content:center; gap:8px; }
                    .sec-btn { background:#080808; border:1px solid #222; color:#666; padding:6px; border-radius:6px; cursor:pointer; font-size:9px; font-weight:bold; }
                    .d-btn { flex:1; background:#111; border:1px solid #222; color:#555; font-size:9px; padding:6px; border-radius:4px; cursor:pointer; }
                    .d-btn.active { border-color:#e2b714; color:#fff; }
                    .pulse { color:#ff4444; animation: blink 1s infinite; }
                    @keyframes blink { 50% { opacity: 0; } }
                </style>
            `;

            const tab = document.createElement('div');
            tab.id = 'mt-restore-tab'; 
            tab.innerHTML = 'BOT';
            tab.style.cssText = `position:fixed;top:50%;right:0;transform:translateY(-50%);background:#e2b714;color:#000;padding:15px 8px;border-radius:10px 0 0 10px;cursor:pointer;font-size:10px;font-weight:900;writing-mode:vertical-rl;display:none;z-index:100000;`;

            document.body.appendChild(gui);
            document.body.appendChild(tab);

            // Click handlers
            document.getElementById('mainStartBtn').onclick = () => this.toggleTyping();
            document.getElementById('hideBtn').onclick = () => { gui.style.display='none'; tab.style.display='flex'; };
            tab.onclick = () => { gui.style.display='block'; tab.style.display='none'; };
            document.getElementById('exportBtn').onclick = () => this.exportConfig();
            document.getElementById('importBtn').onclick = () => this.importConfig();
            
            document.getElementById('s-wpm').oninput = (e) => {
                this.config.wpm = e.target.value;
                document.getElementById('v-wpm').innerText = e.target.value;
                localStorage.setItem('mt-bot-wpm', e.target.value);
            };

            document.querySelectorAll('.d-btn').forEach(btn => {
                btn.onclick = () => {
                    this.config.startDelay = parseInt(btn.dataset.ms);
                    document.querySelectorAll('.d-btn').forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                    localStorage.setItem('mt-bot-delay', btn.dataset.ms);
                };
            });
        }

        exportConfig() {
            const data = JSON.stringify({ wpm: this.config.wpm, acc: this.config.accuracy });
            navigator.clipboard.writeText(data).then(() => {
                this.updateStatusText('Copied!', '#e2b714');
            });
        }

        importConfig() {
            const raw = prompt("Paste JSON Config:");
            if (raw) {
                try {
                    const parsed = JSON.parse(raw);
                    this.config.wpm = parsed.wpm; 
                    this.config.accuracy = parsed.acc;
                    localStorage.setItem('mt-bot-wpm', parsed.wpm); 
                    localStorage.setItem('mt-bot-acc', parsed.acc);
                    location.reload();
                } catch(e) { alert("Invalid JSON!"); }
            }
        }
    }

    // Startup
    new AutoTyper();
})();