您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
An auto-typing script with customizable speed and accuracy for Nitro Type.
// ==UserScript== // @name Auto Typer for Nitro Type (NEW 2025) // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description An auto-typing script with customizable speed and accuracy for Nitro Type. // @author Anon2004 // @match https://www.nitrotype.com/race // @match https://www.nitrotype.com/race/* // @icon https://cdn2.steamgriddb.com/icon/2b16a44bb65751bb0ebe5d8b42644bc4/32/512x512.png // @license MIT // @grant none // ==/UserScript== /* =================================================================================== 🛠️ SETUP INSTRUCTIONS – PLEASE READ CAREFULLY =================================================================================== To customize the WPM (Words Per Minute) and Accuracy ranges for typing sessions: --------------------------------------------------- STEP 1: Update the configuration values --------------------------------------------------- ⬇️ DO NOT edit anything in this comment block. ⬇️ Scroll down and find the following lines in the code: AppConfig.get wpmValues AppConfig.get accuracies You’ll see something like: const MIN_WPM = 100; const MAX_WPM = 125; const MIN_ACC = 95; const MAX_ACC = 99; 🎯 Change these `MIN_` and `MAX_` values to set your preferred WPM and accuracy ranges. 💾 After making changes, save the file: File > Save (or press Ctrl+S / Cmd+S) ⚠️ Stick to updating the min/max values only — leave `SESSIONS` untouched. --------------------------------------------------- STEP 2: Clear the session cache --------------------------------------------------- WPM and accuracy values are cached in `localStorage` when sessions start. To apply your new settings, you **must** reset the cache. ⚠️ Run the following code from the browser console **on the Nitro Type race page**, not elsewhere. How to do it: 1. Go to nitrotype.com/race. 2. Open Developer Tools: - Press F12 or Ctrl+Shift+I (Windows/Linux) - Or Cmd+Option+I (Mac) 3. Go to the “Console” tab. 4. Paste and run this code: (() => { localStorage.setItem('typingWpmSessionCount', '1'); localStorage.setItem('typingAccuracySessionCount', '1'); localStorage.setItem('typingAccuracyBufferCount', '1'); localStorage.removeItem('dynamicWpmValues'); localStorage.removeItem('dynamicAccuracies'); window.location.reload(); })(); ✅ This clears cached values and resets session counters. Your updated min/max settings will take effect after the page reloads. --------------------------------------------------- 💬 Additional Notes --------------------------------------------------- ✔️ All changes are local to your browser. Nitro Type won’t detect them — unless you set unusually high or suspicious values (e.g. WPM 300+, or 100% accuracy every time). 👀 Keep the **Console tab open** while using this setup — important debug info (like current WPM, accuracy, and mode-switch instructions) is printed there. 🎮 Mode Descriptions: Auto Mode: Automatically simulates and injects keystrokes at the configured typing speed without user intervention. Manual Mode: Waits for a user trigger before injecting each keystroke, giving you step-by-step control over the typing. Normal Mode: Disables all automation hacks and behaves like a standard, unmodified typing experience. =================================================================================== */ // ===== CORE CONSTANTS AND CONFIGURATION ===== /** * Application modes with state preservation */ const AppModes = { AUTO: 'auto', MANUAL: 'manual', NORMAL: 'normal', STORAGE_KEY: 'typingAppMode' }; /** * Application-wide constants for typing parameters, events, storage keys, and behavior factors */ const Constants = { TYPING: { CHARS_PER_WORD: 5, MS_PER_MINUTE: 60 * 1000 }, EVENTS: { MODE_CHANGE: 'modeChange', METRICS_UPDATED: 'metricsUpdated', SESSION_COMPLETED: 'sessionCompleted', COUNTERS_INCREMENTED: 'countersIncremented', MANUAL_SESSION_COMPLETED: 'manualSessionCompleted', SESSION_COMPLETED_REFRESH: 'sessionCompletedRefresh' }, STORAGE: { WPM_SESSION_COUNT: 'typingWpmSessionCount', ACCURACY_SESSION_COUNT: 'typingAccuracySessionCount', ACCURACY_BUFFER_COUNT: 'typingAccuracyBufferCount', DYNAMIC_WPM_VALUES: 'dynamicWpmValues', DYNAMIC_ACCURACIES: 'dynamicAccuracies' }, BEHAVIOR: { MIN_DELAY_FACTOR: 0.4, MAX_DELAY_FACTOR: 1.8, WORD_BOUNDARY_MIN: 1.05, WORD_BOUNDARY_MAX: 0.1, COMMON_SEQ_MIN: 0.85, COMMON_SEQ_MAX: 0.05 } }; /** * Centralized application configuration with target WPM, accuracy values, and typing parameters */ const AppConfig = { // Helper to generate and cache arrays _generateDynamicArray(storageKey, count, min, max) { let arr = JSON.parse(localStorage.getItem(storageKey) || 'null'); if (!Array.isArray(arr) || arr.length !== count) { arr = Array.from( { length: count }, () => Math.floor(Math.random() * (max - min + 1)) + min ); localStorage.setItem(storageKey, JSON.stringify(arr)); } return arr; }, // Target WPM values for each session, generated once and saved to localStorage get wpmValues() { const SESSIONS = 10; const MIN_WPM = 100; const MAX_WPM = 125; return this._generateDynamicArray( Constants.STORAGE.DYNAMIC_WPM_VALUES, SESSIONS, MIN_WPM, MAX_WPM ); }, // Target accuracy values for each session, generated once and saved to localStorage get accuracies() { const SESSIONS = 10; const MIN_ACC = 95; const MAX_ACC = 99; return this._generateDynamicArray( Constants.STORAGE.DYNAMIC_ACCURACIES, SESSIONS, MIN_ACC, MAX_ACC ); }, // Accuracy buffer values that cycle accuracyBuffers: [0.7, 0.7, 0.7, 0.7, 0.7, 0, 0, 0, 0, 0], // Common sequences typed faster by humans commonSequences: ['th', 'he', 'in', 'er', 'an', 'en', 'ing', 'the', 'and'], // Word boundary characters wordBoundaries: [' ', '.', ',', ';', ':'], // Parameters controlling typing behavior typingParams: { allowedDeviation: 2, correctionStrength: 0.8, humanVariationStrength: 0.25, microPauseChance: 0.06, burstChance: 0.12, hesitationChance: 0.05, maxMicroPause: 200, feedbackInterval: 800, wpmSmoothingFactor: 0.6, overcompensationFactor: 1.5, speedBoost: 0.85, adaptationRate: 0.2, earlyBoostFactor: 0.7 } }; // ===== CORE UTILITY CLASSES ===== /** * Simple event system to enable communication between components */ class EventBus { constructor() { this.events = {}; } // Register an event listener on(event, listener) { if (!this.events[event]) this.events[event] = []; this.events[event].push(listener); return this; } // Emit an event to all listeners emit(event, ...args) { if (!this.events[event]) return false; this.events[event].forEach(listener => listener(...args)); return true; } // Remove an event listener off(event, listener) { if (!this.events[event]) return this; this.events[event] = this.events[event].filter(l => l !== listener); return this; } } /** * Centralized logging with consistent message formatting */ class Logger { // Shared logging method to standardize format static _log(prefix, message, context = '') { const contextPrefix = context ? `[${context}] ` : ''; console.log(`${contextPrefix}${prefix}${message}`); } // Standard logging methods with different prefixes static log(message, context = '') { this._log('', message, context); } static info(message, context = '') { this._log('', message, context); } static warn(message, context = '') { this._log('⚠️ ', message, context); } static error(message, context = '') { console.error(`${context ? `[${context}] ` : ''}🔴 ${message}`); } static success(message, context = '') { this._log('✅ ', message, context); } // Helper for logging metrics in consistent format static logMetric(name, value, target = null, context = '') { this.info(`${name}: ${Utils.formatNumber(value)}${target !== null ? ` (Target: ${target})` : ''}`, context); } // Helper for logging session info in consistent format static logSessionInfo(config, context = '') { this.info(`Session info: WPM #${config.wpmSessionCount}/${AppConfig.wpmValues.length} (${config.targetWPM} WPM) | Accuracy #${config.accuracySessionCount}/${AppConfig.accuracies.length} (${config.targetAccuracy}%)`, context); } } /** * Handles localStorage operations to persist session data */ class StorageService { // Get value from localStorage with default fallback static get(key, defaultValue) { return parseInt(localStorage.getItem(key) || defaultValue.toString()); } // Set value in localStorage static set(key, value) { localStorage.setItem(key, value); } /** * Increments a counter in localStorage, cycling back to 1 when reaching max */ static increment(key, maxValue, defaultValue = 0) { let count = this.get(key, defaultValue) + 1; if (count > maxValue) { Logger.info(`Completed full ${key} cycle. Resetting counter (was at ${count})`); count = 1; } this.set(key, count); return count; } } /** * General utilities for common operations */ class Utils { // Create a promise that resolves after specific time static delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Analyze content to find the longest word and its position static getLongestWord(content) { const words = content.split(/\s+/); let longest = { word: "", index: 0 }; for (let i = 0; i < words.length; i++) { if (words[i].length > longest.word.length) { longest.word = words[i]; longest.index = content.indexOf(longest.word); } } return longest; } // Format number with specified decimal places static formatNumber(num, decimals = 2) { if (num === undefined || num === null) return "0.00"; return num.toFixed(decimals); } // Apply random variation within a range static applyRandomVariation(base, minFactor, maxVariation) { return base * (minFactor + Math.random() * maxVariation); } // Check if string starts with any pattern from array static startsWithAny(str, patterns) { return patterns.some(pattern => str.startsWith(pattern)); } // Check if character is in array static isInArray(char, array) { return array.includes(char); } // Keep value within bounds static boundValue(value, min, max) { return Math.max(min, Math.min(max, value)); } // Retry operation until success static async retryUntilSuccess(operation, interval = 500) { if (operation()) return true; return new Promise(resolve => { const checkInterval = setInterval(() => { if (operation()) { clearInterval(checkInterval); resolve(true); } }, interval); }); } // Apply a pattern if condition is met, otherwise return null static applyPatternIf(condition, factorFn, baseFactor) { if (condition) return factorFn(baseFactor); return null; } } // ===== CONFIGURATION ===== /** * Manages configuration and session state */ class ConfigService { constructor(eventBus) { this.eventBus = eventBus; this.loadConfig(); } // Load configuration values from storage loadConfig() { const savedMode = localStorage.getItem(AppModes.STORAGE_KEY) || AppModes.AUTO; this._typingMode = savedMode; this._accuracySessionCount = StorageService.get(Constants.STORAGE.ACCURACY_SESSION_COUNT, 1); this._wpmSessionCount = StorageService.get(Constants.STORAGE.WPM_SESSION_COUNT, 1); this._accuracyBufferCount = StorageService.get(Constants.STORAGE.ACCURACY_BUFFER_COUNT, 1); this.typingParams = AppConfig.typingParams; } // Getters and setters for the typing mode get typingMode() { return this._typingMode; } set typingMode(value) { this._typingMode = value; localStorage.setItem(AppModes.STORAGE_KEY, value); this.eventBus.emit(Constants.EVENTS.MODE_CHANGE, value); } // Helper methods to check specific modes get isAutoTypingMode() { return this._typingMode === AppModes.AUTO; } get isManualTypingMode() { return this._typingMode === AppModes.MANUAL; } get isNormalTypingMode() { return this._typingMode === AppModes.NORMAL; } // Return the target WPM for current session get targetWPM() { return AppConfig.wpmValues[this._wpmSessionCount - 1]; } // Return the target accuracy for current session get targetAccuracy() { return AppConfig.accuracies[this._accuracySessionCount - 1]; } // Return the target accuracy buffer for current session get targetAccuracyBuffer() { return AppConfig.accuracyBuffers[this._accuracyBufferCount - 1]; } get wpmSessionCount() { return this._wpmSessionCount; } get accuracySessionCount() { return this._accuracySessionCount; } get accuracyBufferCount() { return this._accuracyBufferCount; } // Increment session counters and emit event incrementCounters() { // Skip incrementing counters in normal mode if (this.isNormalTypingMode) { return { wpmSession: this._wpmSessionCount, accuracySession: this._accuracySessionCount, accuracyBufferSession: this._accuracyBufferCount }; } // Increment all counters with a helper method this._wpmSessionCount = this._incrementCounter( Constants.STORAGE.WPM_SESSION_COUNT, AppConfig.wpmValues.length ); this._accuracySessionCount = this._incrementCounter( Constants.STORAGE.ACCURACY_SESSION_COUNT, AppConfig.accuracies.length ); this._accuracyBufferCount = this._incrementCounter( Constants.STORAGE.ACCURACY_BUFFER_COUNT, AppConfig.accuracyBuffers.length ); // If we've wrapped back to the first WPM session, clear cached arrays so they're regenerated if (this._wpmSessionCount === 1) { localStorage.removeItem(Constants.STORAGE.DYNAMIC_WPM_VALUES); localStorage.removeItem(Constants.STORAGE.DYNAMIC_ACCURACIES); } const counters = { wpmSession: this._wpmSessionCount, accuracySession: this._accuracySessionCount, accuracyBufferSession: this._accuracyBufferCount }; this.eventBus.emit(Constants.EVENTS.COUNTERS_INCREMENTED, counters); return counters; } // Helper method to increment a counter _incrementCounter(key, maxValue) { return StorageService.increment(key, maxValue); } } // ===== DOM INTERFACE ===== /** * Handles DOM interactions and provides access to typing app node */ class DOMInterface { // Extract typing app node from the DOM getTypingAppNode() { try { return Object.values(document.querySelector("div.dash-copyContainer"))[1].children._owner.stateNode; } catch (error) { Logger.error(`Could not access typing app node: ${error}`, 'DOMInterface'); return null; } } } // ===== METRICS TRACKING ===== /** * Tracks and calculates typing metrics and performance */ class MetricsService { constructor(configService, eventBus) { this.config = configService; this.eventBus = eventBus; this.reset(); } // Reset all metrics to initial values reset() { this.totalKeystrokes = 0; this.correctKeystrokes = 0; this.typingStartTime = null; this.charactersTyped = 0; this.lastFeedbackTime = 0; this.currentWPM = 0; this.smoothedWPM = 0; // Calculate target CPM and base delay this.targetCPM = this.config.targetWPM * Constants.TYPING.CHARS_PER_WORD; this.baseDelayMs = (Constants.TYPING.MS_PER_MINUTE) / this.targetCPM * this.config.typingParams.speedBoost; this.currentDelayMs = this.baseDelayMs; // Tracking variables this.typingPattern = []; this.lastKeystrokeTime = 0; this.cumulativeDeviation = 0; this.wpmHistory = []; this.adjustmentHistory = []; this.delayOffset = 0; // Startup phase tracking this.startupPhaseDone = false; this.startupCounter = 0; this.initialBoostApplied = false; this.sessionCompleted = false; } // Calculate current typing accuracy get currentAccuracy() { return this.totalKeystrokes > 0 ? (this.correctKeystrokes / this.totalKeystrokes) * 100 : 100; } // Update keystroke stats (correct or incorrect) updateKeystrokeStats(isCorrect) { this.totalKeystrokes++; if (isCorrect) this.correctKeystrokes++; this.emitMetricsUpdate(); } // Track a correct keystroke trackCorrectKeystroke() { this.updateKeystrokeStats(true); } // Track an incorrect keystroke trackIncorrectKeystroke() { this.updateKeystrokeStats(false); } // Update WPM calculation based on typing progress updateWPM(newChars) { // Initialize start time on first update if (this.typingStartTime === null) { this.typingStartTime = Date.now(); this.lastKeystrokeTime = Date.now(); return 0; } this.charactersTyped += newChars; const currentTime = Date.now(); const elapsedMinutes = (currentTime - this.typingStartTime) / Constants.TYPING.MS_PER_MINUTE; // Calculate WPM if enough time has passed if (elapsedMinutes > 0) { // Calculate instant WPM and apply smoothing const instantWPM = (this.charactersTyped / Constants.TYPING.CHARS_PER_WORD) / elapsedMinutes; this.smoothedWPM = this.smoothedWPM === 0 ? instantWPM : this.smoothedWPM * this.config.typingParams.wpmSmoothingFactor + instantWPM * (1 - this.config.typingParams.wpmSmoothingFactor); this.currentWPM = this.smoothedWPM; this._updateWpmHistory(currentTime); return this.currentWPM; } return 0; } // Update WPM history and provide feedback _updateWpmHistory(currentTime) { // Maintain a rolling window of recent WPM values if (this.wpmHistory.length > 10) this.wpmHistory.shift(); this.wpmHistory.push(this.currentWPM); // Provide periodic feedback on typing speed if (currentTime - this.lastFeedbackTime > this.config.typingParams.feedbackInterval) { Logger.logMetric('Current typing speed', this.currentWPM, this.config.targetWPM, 'WPM'); this.lastFeedbackTime = currentTime; this._handleSpeedDeviation(); } this._handleStartupPhase(); } // Handle deviations from target WPM _handleSpeedDeviation() { const deviation = this.currentWPM - this.config.targetWPM; const deviationPercent = Utils.formatNumber(Math.abs(deviation) / this.config.targetWPM * 100, 1); if (Math.abs(deviation) > this.config.typingParams.allowedDeviation) { if (deviation < 0) { Logger.warn(`TOO SLOW: ${Utils.formatNumber(Math.abs(deviation), 1)} WPM below target (${deviationPercent}%) - Increasing typing speed...`, 'WPM'); } else { Logger.warn(`TOO FAST: ${Utils.formatNumber(deviation, 1)} WPM above target (${deviationPercent}%) - Reducing typing speed...`, 'WPM'); } this._adjustForConsistentDeviation(deviation); } } // Make adjustments for consistent deviations _adjustForConsistentDeviation(deviation) { if (this.wpmHistory.length >= 5) { const recentAvg = this.wpmHistory.slice(-5).reduce((sum, wpm) => sum + wpm, 0) / 5; const avgDeviation = recentAvg - this.config.targetWPM; if (Math.abs(avgDeviation) > this.config.typingParams.allowedDeviation) { const adjustmentFactor = -avgDeviation * 0.015; this.delayOffset += adjustmentFactor; Logger.info(`🔄 Making permanent base delay adjustment: ${Utils.formatNumber(adjustmentFactor, 3)}ms (cumulative: ${Utils.formatNumber(this.delayOffset, 3)}ms)`, 'WPM'); } } } // Handle special adjustments during startup phase _handleStartupPhase() { if (!this.startupPhaseDone) { this.startupCounter++; // Apply initial boost if typing too slow if (this.startupCounter === 10 && !this.initialBoostApplied) { if (this.currentWPM < this.config.targetWPM * 0.9) { const boostFactor = 0.7; // 30% faster typing this.baseDelayMs *= boostFactor; this.currentDelayMs *= boostFactor; this.initialBoostApplied = true; Logger.info(`🚀 Initial speed boost applied! Base delay reduced to ${Utils.formatNumber(this.baseDelayMs)}ms`, 'WPM'); } } // End startup phase after sufficient keystrokes if (this.startupCounter >= 30) { this.startupPhaseDone = true; Logger.info("Startup phase complete, switching to normal control mode", 'WPM'); } } } // Emit metrics updates and log feedback emitMetricsUpdate() { // Log accuracy periodically if (this.totalKeystrokes % 20 === 0) { this._logAccuracyFeedback(); } this.eventBus.emit(Constants.EVENTS.METRICS_UPDATED, { accuracy: this.currentAccuracy, wpm: this.currentWPM, keystrokes: this.totalKeystrokes, correctKeystrokes: this.correctKeystrokes, accuracyBuffer: this.config.targetAccuracyBuffer }); } // Log accuracy feedback in consistent format _logAccuracyFeedback() { Logger.logMetric('Current accuracy', this.currentAccuracy, this.config.targetAccuracy, 'Accuracy'); Logger.info(`(${this.correctKeystrokes}/${this.totalKeystrokes})`, 'Accuracy'); } // Mark session as complete and emit event markSessionComplete() { if (this.sessionCompleted) return; this.sessionCompleted = true; this.eventBus.emit(Constants.EVENTS.SESSION_COMPLETED, { finalWPM: this.currentWPM || 0, finalAccuracy: this.currentAccuracy || 0 }); } // Log history of WPM adjustments logAdjustmentHistory() { Logger.info("WPM and delay adjustment history:", 'Metrics'); this.adjustmentHistory.forEach((item, i) => { Logger.info(`${i}: WPM=${Utils.formatNumber(item.wpm, 1)}, Target=${item.targetWpm}, Delay=${Utils.formatNumber(item.adjustedDelay, 1)}ms`, 'Metrics'); }); } } // ===== TIMING CONTROLLER ===== /** * Controls timing between keystrokes to achieve target WPM */ class TimingController { constructor(metricsService, configService) { this.metrics = metricsService; this.config = configService; } /** * Calculate delay for next keystroke based on current metrics */ calculateNextDelay(content, typedIndex) { const now = Date.now(); let delay = this.metrics.baseDelayMs; // Apply early boost during startup if (!this.metrics.startupPhaseDone) { delay *= this.config.typingParams.earlyBoostFactor; } // Adjust based on current WPM if (this.metrics.charactersTyped > 5 && this.metrics.currentWPM > 0) { delay = this._calculateSpeedAdjustedDelay(delay); } // Apply human-like variations const humanFactor = this._applyHumanTypingPatterns(content, typedIndex); delay *= humanFactor; // Keep delay within reasonable bounds delay = Utils.boundValue( delay, this.metrics.baseDelayMs * Constants.BEHAVIOR.MIN_DELAY_FACTOR, this.metrics.baseDelayMs * Constants.BEHAVIOR.MAX_DELAY_FACTOR ); this._updateDelayHistory(delay); this.metrics.currentDelayMs = delay; this.metrics.lastKeystrokeTime = now; return delay; } /** * Calculate delay adjustments based on WPM difference */ _calculateSpeedAdjustedDelay(baseDelay) { const wpmDifference = this.metrics.currentWPM - this.config.targetWPM; let correctionFactor = wpmDifference * this.config.typingParams.correctionStrength; // Apply stronger correction when below target if (wpmDifference < 0) { correctionFactor *= this.config.typingParams.overcompensationFactor; } // Apply correction and offset let delay = baseDelay * (1 + correctionFactor / 10); delay += this.metrics.delayOffset; // Track cumulative deviation this.metrics.cumulativeDeviation += wpmDifference * this.config.typingParams.adaptationRate; return this._applyTrendCorrections(delay, wpmDifference); } /** * Apply corrections based on observed typing trends */ _applyTrendCorrections(delay, wpmDifference) { // Apply cumulative corrections periodically if (this.metrics.charactersTyped % 3 === 0 && Math.abs(this.metrics.cumulativeDeviation) > 0.5) { delay *= (1 + this.metrics.cumulativeDeviation * 0.02); // Prevent overcompensation if (Math.abs(this.metrics.cumulativeDeviation) > 3) { this.metrics.cumulativeDeviation *= 0.7; } } // Analyze recent WPM trends if (this.metrics.wpmHistory.length >= 3) { const trend = this.metrics.wpmHistory.slice(-3); const avgTrend = trend.reduce((sum, wpm) => sum + wpm, 0) / trend.length; const trendDeviation = avgTrend - this.config.targetWPM; // If trend consistently deviates, apply stronger correction if (Math.abs(trendDeviation) > this.config.typingParams.allowedDeviation && Math.sign(trendDeviation) === Math.sign(wpmDifference)) { delay *= (1 - (trendDeviation * 0.01)); } } return delay; } /** * Apply human-like variations to typing rhythm */ _applyHumanTypingPatterns(content, typedIndex) { // Base human factor with random variation let humanFactor = 0.9 + (Math.random() * 0.2); const pattern = Math.random(); const params = this.config.typingParams; // Apply pattern-based variations (micro-pause, burst, hesitation) const microPause = Utils.applyPatternIf( pattern < params.microPauseChance, (factor) => factor * (1 + Math.random() * 0.3), humanFactor ); if (microPause !== null) return microPause; const burst = Utils.applyPatternIf( pattern < params.microPauseChance + params.burstChance, (factor) => factor * (0.75 + Math.random() * 0.1), humanFactor ); if (burst !== null) return burst; const hesitation = Utils.applyPatternIf( pattern < params.microPauseChance + params.burstChance + params.hesitationChance, (factor) => factor * (1.1 + Math.random() * 0.4), humanFactor ); if (hesitation !== null) return hesitation; // Apply content-based variations (word boundaries, common sequences) const currentChar = content[typedIndex]; const wordBoundary = Utils.applyPatternIf( Utils.isInArray(currentChar, AppConfig.wordBoundaries), (factor) => Utils.applyRandomVariation( factor, Constants.BEHAVIOR.WORD_BOUNDARY_MIN, Constants.BEHAVIOR.WORD_BOUNDARY_MAX ), humanFactor ); if (wordBoundary !== null) return wordBoundary; let nextFewChars = content.slice(typedIndex, typedIndex + 3); const commonSeq = Utils.applyPatternIf( Utils.startsWithAny(nextFewChars, AppConfig.commonSequences), (factor) => Utils.applyRandomVariation( factor, Constants.BEHAVIOR.COMMON_SEQ_MIN, Constants.BEHAVIOR.COMMON_SEQ_MAX ), humanFactor ); if (commonSeq !== null) return commonSeq; return humanFactor; } /** * Update delay history for analysis */ _updateDelayHistory(delay) { // Maintain limited history of patterns if (this.metrics.typingPattern.length > 15) this.metrics.typingPattern.shift(); this.metrics.typingPattern.push(delay); // Keep adjustment history for analysis if (this.metrics.adjustmentHistory.length > 10) this.metrics.adjustmentHistory.shift(); this.metrics.adjustmentHistory.push({ wpm: this.metrics.currentWPM, targetWpm: this.config.targetWPM, baseDelay: this.metrics.baseDelayMs, adjustedDelay: delay }); } /** * Add random micro-pauses for human-like typing */ async addMicroPause() { if (Math.random() < 0.07) { const pauseTime = Math.random() * this.config.typingParams.maxMicroPause; return Utils.delay(pauseTime); } return Promise.resolve(); } } // ===== ACCURACY CONTROLLER ===== /** * Controls typing accuracy to match target percentage */ class AccuracyController { constructor(metricsService, configService) { this.metrics = metricsService; this.config = configService; // Use the cyclic buffer value this.accuracyBuffer = this.config.targetAccuracyBuffer; } /** * Get the current accuracy buffer for logging purposes */ getBufferInfo() { return `+${Utils.formatNumber(this.accuracyBuffer, 2)}%`; } /** * Determine if an error should be made to maintain target accuracy */ shouldMakeError() { // Don't make errors at the beginning if (this.metrics.totalKeystrokes === 0) return false; // Target accuracy with buffer const targetWithBuffer = this.config.targetAccuracy + this.accuracyBuffer; // Dynamic error probability based on current vs. target accuracy if (this.metrics.currentAccuracy > targetWithBuffer) { return true; // More likely to make errors when above target } else if (this.metrics.currentAccuracy < targetWithBuffer) { return false; // Less likely when below target } // When at target accuracy, error probability matches adjusted target return Math.random() > (targetWithBuffer / 100); } } // ===== KEYBOARD HANDLER ===== /** * Handles keyboard input and simulates keystrokes */ class KeyboardHandler { constructor(metricsService, configService, eventBus) { this.metrics = metricsService; this.config = configService; this.eventBus = eventBus; } /** * Process keystroke and update metrics */ processKeystroke(appNode, makeCorrect) { // Update metrics based on keystroke correctness if (makeCorrect) { this.metrics.trackCorrectKeystroke(); if (this.config.isAutoTypingMode) { this.metrics.updateWPM(1); } } else { this.metrics.trackIncorrectKeystroke(); } // Simulate keystroke in the app appNode.handleKeyPress("character", new KeyboardEvent("keypress", { key: makeCorrect ? appNode.props.lessonContent[appNode.typedIndex] : "$" })); this._checkSessionComplete(appNode); } /** * Simulate pressing Enter key */ simulateEnterKey(appNode) { appNode.handleKeyPress("character", new KeyboardEvent("keypress", { key: "\n" })); this._checkSessionComplete(appNode); } /** * Check if session is complete in manual mode */ _checkSessionComplete(appNode) { if (!this.config.isAutoTypingMode && this._isSessionComplete(appNode)) { this.handleSessionCompletion(); } } /** * Check if typing session is complete */ _isSessionComplete(appNode) { return appNode.typedIndex >= appNode.props.lessonContent.length; } /** * Handle completion of a manual typing session */ handleSessionCompletion() { this.metrics.markSessionComplete(); this.eventBus.emit(Constants.EVENTS.MANUAL_SESSION_COMPLETED); } /** * Create key handlers for different typing modes */ createKeyHandlers(appNode, originalKeyHandler, accuracyController, longestWord) { return { // Normal typing mode handler createNormalHandler: () => { return this._createNormalKeyHandler(appNode, originalKeyHandler, accuracyController, longestWord); }, // Countdown phase handler createCountdownHandler: () => { return this._createCountdownKeyHandler(originalKeyHandler); } }; } /** * Create normal typing mode key handler */ _createNormalKeyHandler(appNode, originalKeyHandler, accuracyController, longestWord) { return (e, n) => { if ("character" === e) { if (appNode.typedIndex === longestWord.index) { this.simulateEnterKey(appNode); return; } this.processKeystroke(appNode, !accuracyController.shouldMakeError()); } else if (n.key === "\n") { this.simulateEnterKey(appNode); } else { return originalKeyHandler(e, n); } }; } /** * Create countdown phase key handler */ _createCountdownKeyHandler(originalKeyHandler) { return (e, n) => { // Toggle modes with different keys: 'a' for auto, 'm' for manual, 'n' for normal if ("character" === e) { if (n.key === "a") { this.config.typingMode = AppModes.AUTO; Logger.info(`Mode switched to: AUTO typing`, 'KeyboardHandler'); return; } else if (n.key === "m") { this.config.typingMode = AppModes.MANUAL; Logger.info(`Mode switched to: MANUAL typing`, 'KeyboardHandler'); return; } else if (n.key === "n") { this.config.typingMode = AppModes.NORMAL; Logger.info(`Mode switched to: NORMAL typing (script disabled)`, 'KeyboardHandler'); return; } } // Pass through specific keys if (n.key >= '1' && n.key <= '8' || n.key === 'Shift' || n.key === 'Control') { return originalKeyHandler(e, n); } }; } } // ===== SESSION MANAGER ===== /** * Manages typing session lifecycle and transitions */ class SessionManager { constructor(configService, metricsService, eventBus) { this.config = configService; this.metrics = metricsService; this.eventBus = eventBus; this.isHandlingCompletion = false; // Subscribe to session completion event this.eventBus.on(Constants.EVENTS.SESSION_COMPLETED, data => { if (!this.isHandlingCompletion) { this.handleSessionCompleted(data); } }); } /** * Handle session completion and report results */ handleSessionCompleted(sessionData) { this.isHandlingCompletion = true; // Save target values before incrementing const currentTargetWPM = this.config.targetWPM; const currentTargetAccuracy = this.config.targetAccuracy; // Get final metrics const finalWPM = sessionData?.finalWPM || 0; const finalAccuracy = sessionData?.finalAccuracy || 0; // Log completion results this._logSessionResults(finalWPM, finalAccuracy, currentTargetWPM, currentTargetAccuracy); // Increment counters for next session const newCounters = this.config.incrementCounters(); Logger.info(`New WPM session: ${newCounters.wpmSession}/${AppConfig.wpmValues.length} | New Accuracy session: ${newCounters.accuracySession}/${AppConfig.accuracies.length} | New Buffer session: ${newCounters.accuracyBufferSession}/${AppConfig.accuracyBuffers.length}`, 'SessionManager'); // Log additional metrics in auto mode if (this.config.isAutoTypingMode) { this.metrics.logAdjustmentHistory(); } Logger.info("Waiting 4 seconds before refreshing page...", 'SessionManager'); // Emit refresh notification event this.eventBus.emit(Constants.EVENTS.SESSION_COMPLETED_REFRESH, { isAutoMode: this.config.isAutoTypingMode, isManualMode: this.config.isManualTypingMode, isNormalMode: this.config.isNormalTypingMode, nextWpmSession: newCounters.wpmSession, nextAccuracySession: newCounters.accuracySession, nextBufferSession: newCounters.accuracyBufferSession }); // Refresh page after delay setTimeout(() => window.location.reload(), 4000); } /** * Log session completion results */ _logSessionResults(finalWPM, finalAccuracy, targetWPM, targetAccuracy) { if (this.config.isAutoTypingMode) { Logger.success("Auto-typing completed!", 'SessionManager'); Logger.logMetric('Final WPM', finalWPM, targetWPM, 'SessionManager'); } else { Logger.success("Manual typing session completed!", 'SessionManager'); } Logger.logMetric('Final accuracy', finalAccuracy, targetAccuracy, 'SessionManager'); } } // ===== AUTO-TYPER ===== /** * Main controller for auto-typing functionality */ class AutoTyper { constructor(services) { this.dom = services.dom; this.config = services.config; this.metrics = services.metrics; this.timingController = services.timingController; this.accuracyController = services.accuracyController; this.keyboardHandler = services.keyboardHandler; this.eventBus = services.eventBus; this.sessionManager = services.sessionManager; } /** * Initialize auto-typer and wait for page to load */ async init() { Logger.info("Waiting for page to fully load...", 'AutoTyper'); // Retry initialization until typing app is ready await Utils.retryUntilSuccess(() => { const result = this.initTypingScript(); if (result) { Logger.info("Script is now running!", 'AutoTyper'); } return result; }, 500); } /** * Set up typing script once page is loaded */ initTypingScript() { try { this.appNode = this.dom.getTypingAppNode(); if (!this.appNode) return false; // Get content and analyze const content = this.appNode.props.lessonContent; this.longestWord = Utils.getLongestWord(content); this.originalKeyHandler = this.appNode.input.keyHandler; // Log setup information Logger.info("Script successfully loaded", 'AutoTyper'); Logger.logSessionInfo(this.config, 'AutoTyper'); Logger.info(`Accuracy target buffer: ${this.accuracyController.getBufferInfo()} (Session ${this.config.accuracyBufferCount}/${AppConfig.accuracyBuffers.length})`, 'AutoTyper'); Logger.info(`Longest word detected: '${this.longestWord.word}' at position ${this.longestWord.index}`, 'AutoTyper'); let modeDisplay = "UNKNOWN"; if (this.config.isAutoTypingMode) modeDisplay = "AUTO"; else if (this.config.isManualTypingMode) modeDisplay = "MANUAL"; else if (this.config.isNormalTypingMode) modeDisplay = "NORMAL (script disabled)"; Logger.info(`Mode: ${modeDisplay} typing`, 'AutoTyper'); this.startTypingSession(); return true; } catch (error) { Logger.info("Content not ready yet, retrying...", 'AutoTyper'); return false; } } /** * Start typing session with countdown and mode selection */ async startTypingSession() { // Set up key handlers const keyHandlers = this.keyboardHandler.createKeyHandlers( this.appNode, this.originalKeyHandler, this.accuracyController, this.longestWord ); // Start with countdown handler for initial setup this.appNode.input.keyHandler = keyHandlers.createCountdownHandler(); Logger.info("Press: 'a' for AUTO typing, 'm' for MANUAL typing, 'n' for NORMAL typing", 'AutoTyper'); Logger.info("Countdown started. Auto-typing will begin in 4 seconds...", 'AutoTyper'); await Utils.delay(4000); // If in normal mode, use the original key handler and return if (this.config.isNormalTypingMode) { Logger.info("Starting in NORMAL mode. Script disabled.", 'AutoTyper'); this.appNode.input.keyHandler = this.originalKeyHandler; return; } // Switch to normal typing handler after countdown this.appNode.input.keyHandler = keyHandlers.createNormalHandler(); // Start appropriate typing mode this._startTypingMode(); } /** * Start appropriate typing mode with proper logging */ _startTypingMode() { if (this.config.isAutoTypingMode) { Logger.info(`Starting auto-typing - Target WPM: ${this.config.targetWPM} (Session ${this.config.wpmSessionCount}/${AppConfig.wpmValues.length})`, 'AutoTyper'); Logger.info(`Base delay between keystrokes: ${Utils.formatNumber(this.metrics.baseDelayMs)}ms (with ${this.config.typingParams.speedBoost.toFixed(2)}x speed boost)`, 'AutoTyper'); this._runAutoTyper(); } else if (this.config.isManualTypingMode) { Logger.info(`Starting manual typing - Target accuracy: ${this.config.targetAccuracy}% (Session ${this.config.accuracySessionCount}/${AppConfig.accuracies.length})`, 'AutoTyper'); } } /** * Run auto-typing process character by character */ async _runAutoTyper() { let currentIndex = 0; let isTyping = true; // Initialize WPM tracking this.metrics.typingStartTime = Date.now(); this.metrics.lastKeystrokeTime = Date.now(); /** * Type next character with appropriate timing */ const typeNextCharacter = async () => { // Stop if typing is cancelled or complete if (!isTyping || !this.config.isAutoTypingMode || this.config.isNormalTypingMode || this._isSessionComplete(currentIndex)) { if (this._isSessionComplete(currentIndex)) { this.metrics.markSessionComplete(); } return; } // Calculate delay for natural typing rhythm const delay = this.timingController.calculateNextDelay( this.appNode.props.lessonContent, this.appNode.typedIndex ); // Add random micro-pauses for realism await this.timingController.addMicroPause(); setTimeout(() => { if (!this.config.isAutoTypingMode || this.config.isNormalTypingMode) return; // Handle special case for longest word (press Enter) if (currentIndex === this.longestWord.index) { this.keyboardHandler.simulateEnterKey(this.appNode); currentIndex++; typeNextCharacter(); return; } // Determine whether to make an error const makeError = this.accuracyController.shouldMakeError(); this.keyboardHandler.processKeystroke(this.appNode, !makeError); // Only advance index on correct keystrokes if (!makeError) { currentIndex++; } // Continue typing next character typeNextCharacter(); }, delay); }; // Start the typing process typeNextCharacter(); } /** * Check if typing session is complete */ _isSessionComplete(currentIndex) { return currentIndex >= this.appNode.props.lessonContent.length; } } // ===== SERVICE CONTAINER ===== /** * Manages service instances and dependencies */ class ServiceContainer { constructor() { this.services = {}; } /** * Register a service in the container */ register(name, service) { this.services[name] = service; return this; } /** * Retrieve a service from the container */ get(name) { return this.services[name]; } /** * Create and register all services with dependencies */ createServices() { // Create core services const eventBus = new EventBus(); this.register('eventBus', eventBus); const configService = new ConfigService(eventBus); this.register('config', configService); const domService = new DOMInterface(); this.register('dom', domService); // Create dependent services const metricsService = new MetricsService(configService, eventBus); this.register('metrics', metricsService); const timingController = new TimingController(metricsService, configService); this.register('timingController', timingController); const accuracyController = new AccuracyController(metricsService, configService); this.register('accuracyController', accuracyController); const keyboardHandler = new KeyboardHandler(metricsService, configService, eventBus); this.register('keyboardHandler', keyboardHandler); const sessionManager = new SessionManager(configService, metricsService, eventBus); this.register('sessionManager', sessionManager); // Create main application controller const autoTyper = new AutoTyper(this.services); this.register('autoTyper', autoTyper); return this; } } // ===== MAIN APP ===== /** * Main application entry point */ class TypingApp { constructor() { // Create and initialize services this.container = new ServiceContainer().createServices(); this.autoTyper = this.container.get('autoTyper'); } /** * Start the typing application */ async start() { await this.autoTyper.init(); } } // ===== INITIALIZE THE APP ===== /** * Self-executing function to start the application */ (function() { const app = new TypingApp(); app.start(); })();