| |
|
| |
|
| |
|
| | (function () {
|
| | console.log("%c MnkLightning v3 Active ", "background: #222; color: #00ff00; font-size: 16px");
|
| |
|
| |
|
| | const CONFIG = {
|
| | min_wpm: 100,
|
| | max_wpm: 140,
|
| |
|
| | min_key_hold: 0.01,
|
| | max_key_hold: 0.04,
|
| |
|
| |
|
| | wrong_char_rate: 0.005,
|
| | adjacent_key_rate: 0.005,
|
| | double_letter_rate: 0.001,
|
| | skip_letter_rate: 0.001,
|
| |
|
| | hesitation_rate: 0.02,
|
| | burst_rate: 0.08,
|
| | insane_burst_rate: 0.001,
|
| |
|
| |
|
| | hesitation_multiplier: 2.5,
|
| | burst_speed_multiplier: 0.6,
|
| | post_mistake_pause: 0.4,
|
| | word_start_slowdown: 1.2,
|
| | };
|
| |
|
| |
|
| | const ADJACENT_KEYS = {
|
| | 'a': 'qwsz', 'b': 'vghn', 'c': 'xdfv', 'd': 'serfcx', 'e': 'wsdr',
|
| | 'f': 'drtgvc', 'g': 'ftyhbv', 'h': 'gyujnb', 'i': 'ujko', 'j': 'huikmn',
|
| | 'k': 'jiolm', 'l': 'kop', 'm': 'njk', 'n': 'bhjm', 'o': 'iklp',
|
| | 'p': 'ol', 'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
|
| | 'u': 'yhji', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc', 'y': 'tghu',
|
| | 'z': 'asx'
|
| | };
|
| |
|
| |
|
| | let isTyping = false;
|
| | let abortTyping = false;
|
| |
|
| |
|
| |
|
| | const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
| | const randomUniform = (min, max) => Math.random() * (max - min) + min;
|
| | const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
| | const randomChoice = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
| | const randomChar = () => "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)];
|
| |
|
| | function getKeystrokeDelay(wpm, inBurst = false) {
|
| | const cpm = wpm * 5;
|
| | let baseDelay = 60 / cpm;
|
| |
|
| | if (inBurst) {
|
| | baseDelay *= CONFIG.burst_speed_multiplier;
|
| | }
|
| |
|
| | const variance = baseDelay * 0.20;
|
| | const noise = randomUniform(-variance, variance);
|
| | return Math.max(0.005, baseDelay + noise) * 1000;
|
| | }
|
| |
|
| | function getAdjacentKey(char) {
|
| | const charLower = char.toLowerCase();
|
| | if (ADJACENT_KEYS[charLower]) {
|
| | const adj = randomChoice(ADJACENT_KEYS[charLower]);
|
| | return char === char.toUpperCase() ? adj.toUpperCase() : adj;
|
| | }
|
| | return char;
|
| | }
|
| |
|
| |
|
| |
|
| | const CODE_MAP = {
|
| | ' ': 'Space',
|
| | 'Enter': 'Enter',
|
| | 'Backspace': 'Backspace',
|
| | 'Escape': 'Escape',
|
| | ',': 'Comma',
|
| | '.': 'Period',
|
| | '/': 'Slash',
|
| | ';': 'Semicolon',
|
| | "'": 'Quote',
|
| | '[': 'BracketLeft',
|
| | ']': 'BracketRight',
|
| | '\\': 'Backslash',
|
| | '-': 'Minus',
|
| | '=': 'Equal',
|
| | '`': 'Backquote',
|
| | '1': 'Digit1', '2': 'Digit2', '3': 'Digit3', '4': 'Digit4', '5': 'Digit5',
|
| | '6': 'Digit6', '7': 'Digit7', '8': 'Digit8', '9': 'Digit9', '0': 'Digit0'
|
| | };
|
| |
|
| | function dispatchKey(key, type) {
|
| | let code;
|
| |
|
| |
|
| | if (CODE_MAP[key]) {
|
| | code = CODE_MAP[key];
|
| | }
|
| |
|
| | else if (key.length === 1 && /[a-zA-Z]/.test(key)) {
|
| | code = `Key${key.toUpperCase()}`;
|
| | }
|
| |
|
| | else if (key === '!') code = 'Digit1';
|
| | else if (key === '@') code = 'Digit2';
|
| | else if (key === '#') code = 'Digit3';
|
| | else if (key === '$') code = 'Digit4';
|
| | else if (key === '%') code = 'Digit5';
|
| | else if (key === '^') code = 'Digit6';
|
| | else if (key === '&') code = 'Digit7';
|
| | else if (key === '*') code = 'Digit8';
|
| | else if (key === '(') code = 'Digit9';
|
| | else if (key === ')') code = 'Digit0';
|
| | else if (key === '_') code = 'Minus';
|
| | else if (key === '+') code = 'Equal';
|
| | else if (key === '{') code = 'BracketLeft';
|
| | else if (key === '}') code = 'BracketRight';
|
| | else if (key === '|') code = 'Backslash';
|
| | else if (key === ':') code = 'Semicolon';
|
| | else if (key === '"') code = 'Quote';
|
| | else if (key === '<') code = 'Comma';
|
| | else if (key === '>') code = 'Period';
|
| | else if (key === '?') code = 'Slash';
|
| | else if (key === '~') code = 'Backquote';
|
| | else {
|
| | code = key;
|
| | }
|
| |
|
| | const event = new KeyboardEvent(type, {
|
| | key: key,
|
| | code: code,
|
| | bubbles: true,
|
| | cancelable: true,
|
| | view: window,
|
| | which: key.charCodeAt(0),
|
| | keyCode: key.charCodeAt(0)
|
| | });
|
| |
|
| | const target = document.activeElement || document.body;
|
| | target.dispatchEvent(event);
|
| | }
|
| |
|
| | async function typeChar(char) {
|
| | dispatchKey(char, 'keydown');
|
| | dispatchKey(char, 'keypress');
|
| |
|
| | const holdTime = randomUniform(CONFIG.min_key_hold, CONFIG.max_key_hold) * 1000;
|
| | await sleep(holdTime);
|
| | dispatchKey(char, 'keyup');
|
| | }
|
| |
|
| | async function simulateBackspace(count = 1) {
|
| | for (let i = 0; i < count; i++) {
|
| | dispatchKey('Backspace', 'keydown');
|
| | await sleep(randomUniform(CONFIG.min_key_hold, CONFIG.max_key_hold) * 1000);
|
| | dispatchKey('Backspace', 'keyup');
|
| | await sleep(randomUniform(50, 100));
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| | function getVisibleText() {
|
| | const words = document.querySelectorAll('.word');
|
| | if (words.length === 0) return null;
|
| |
|
| | let text = "";
|
| | words.forEach((word) => {
|
| | word.querySelectorAll('letter').forEach(letter => {
|
| | text += letter.textContent;
|
| | });
|
| | text += " ";
|
| | });
|
| | return text.trim();
|
| | }
|
| |
|
| | async function startTyping() {
|
| | if (isTyping) return;
|
| |
|
| | const text = getVisibleText();
|
| | if (!text) {
|
| | console.error("MnkLightning: No text found! Are you in a test?");
|
| | return;
|
| | }
|
| |
|
| |
|
| | const gameWords = document.getElementById('words');
|
| | if (gameWords) {
|
| | gameWords.click();
|
| | }
|
| |
|
| | isTyping = true;
|
| | abortTyping = false;
|
| |
|
| | const wpm = randomInt(CONFIG.min_wpm, CONFIG.max_wpm);
|
| | console.log(`MnkLightning: Starting... Target WPM: ${wpm}, Length: ${text.length}`);
|
| |
|
| | let idx = 0;
|
| | let burstRemaining = 0;
|
| |
|
| | try {
|
| | while (idx < text.length) {
|
| | if (abortTyping) break;
|
| |
|
| | const char = text[idx];
|
| | let delay = getKeystrokeDelay(wpm, burstRemaining > 0);
|
| |
|
| | if (burstRemaining > 0) burstRemaining--;
|
| |
|
| |
|
| |
|
| | if (Math.random() < CONFIG.hesitation_rate) {
|
| | delay *= CONFIG.hesitation_multiplier;
|
| | }
|
| |
|
| | if (burstRemaining === 0 && Math.random() < CONFIG.burst_rate) {
|
| | burstRemaining = 5;
|
| | }
|
| |
|
| |
|
| | if (Math.random() < CONFIG.insane_burst_rate) {
|
| | const burstLen = randomInt(4, 5);
|
| | for (let i = 0; i < burstLen; i++) {
|
| | if (idx >= text.length || abortTyping) break;
|
| | dispatchKey(text[idx], 'keydown');
|
| | dispatchKey(text[idx], 'keyup');
|
| | idx++;
|
| | await sleep(2);
|
| | }
|
| | continue;
|
| | }
|
| |
|
| |
|
| | if (Math.random() < CONFIG.wrong_char_rate && /[a-zA-Z0-9]/.test(char)) {
|
| | const wrong = randomChar();
|
| | await typeChar(wrong);
|
| | await sleep(delay * 2);
|
| | await simulateBackspace(1);
|
| | await sleep(delay);
|
| | }
|
| |
|
| | await typeChar(char);
|
| | idx++;
|
| |
|
| | if (char === ' ') {
|
| | delay *= CONFIG.word_start_slowdown;
|
| | }
|
| |
|
| | await sleep(delay);
|
| | }
|
| | } catch (e) {
|
| | console.error(e);
|
| | }
|
| |
|
| | console.log("MnkLightning: Finished.");
|
| | isTyping = false;
|
| | }
|
| |
|
| |
|
| |
|
| | window.addEventListener('keydown', (e) => {
|
| | if (e.key === 'Insert') {
|
| | e.preventDefault();
|
| | if (isTyping) {
|
| | console.log("MnkLightning: Already running!");
|
| | return;
|
| | }
|
| | startTyping();
|
| | }
|
| | if (e.key === 'Escape') {
|
| | abortTyping = true;
|
| | if (isTyping) {
|
| | console.log("MnkLightning: Aborting...");
|
| | isTyping = false;
|
| | }
|
| | }
|
| | });
|
| |
|
| | })();
|
| |
|