Last active
January 21, 2026 15:03
-
-
Save Domiii/52cf49d780ec8c9f01771973c36197af to your computer and use it in GitHub Desktop.
This script types for you automatically on www.typingclub.com: 1. Open the website 2. Blaze past the tutorials 3. Go into a level 4. Open Console 5. Paste the script and press ENTER
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * This script types for you automatically on www.typingclub.com: | |
| * 1. Open the website | |
| * 2. Blaze past the tutorials | |
| * 3. Go into a level | |
| * 4. Open Console | |
| * 5. Paste the script and press ENTER | |
| */ | |
| // NOTE: When delay (in ms between two strokes) is too low, the site might bug out and the result page will not be shown | |
| const minDelay = 60; | |
| const maxDelay = 60; | |
| const keyOverrides = { | |
| [String.fromCharCode(160)]: ' ' // convert hardspace to normal space | |
| }; | |
| function getTargetCharacters() { | |
| const els = Array.from(document.querySelectorAll('.token span.token_unit')); | |
| const chrs = els | |
| .map(el => { | |
| // get letter to type from each letter DOM element | |
| if (el.firstChild?.classList?.contains('_enter')) { | |
| // special case: ENTER | |
| return '\n'; | |
| } | |
| let text = el.textContent[0]; | |
| return text; | |
| }) | |
| .map(c => keyOverrides.hasOwnProperty(c) ? keyOverrides[c] : c); // convert special characters | |
| return chrs; | |
| } | |
| function recordKey(chr) { | |
| // send it straight to the internal API | |
| window.core.record_keydown_time(chr); | |
| } | |
| function sleep(ms) { | |
| return new Promise(r => setTimeout(r, ms)); | |
| } | |
| async function autoPlay(finish) { | |
| const chrs = getTargetCharacters(); | |
| for (let i = 0; i < chrs.length - (!finish); ++i) { | |
| const c = chrs[i]; | |
| recordKey(c); | |
| //console.log(c, c.charCodeAt()); | |
| await sleep(Math.random() * (maxDelay - minDelay) + minDelay); | |
| } | |
| } | |
| // ############################################################################################################ | |
| // old utilities | |
| // ############################################################################################################ | |
| // /** | |
| // * @see https://stackoverflow.com/questions/8942678/keyboardevent-in-chrome-keycode-is-0/12522752#12522752 | |
| // */ | |
| // function simulateKey(chr, el) { | |
| // _simulateKey(chr, 'keydown', el); | |
| // _simulateKey(chr, 'keypress', el); | |
| // } | |
| // function _simulateKey(chr, type, el) { | |
| // var eventObj = document.createEventObject ? | |
| // document.createEventObject() : document.createEvent("Events"); | |
| // if (eventObj.initEvent) { | |
| // eventObj.initEvent(type || "keydown", true, true); | |
| // } | |
| // let keyCode = chr.charCodeAt(0); | |
| // eventObj.key = chr[0]; | |
| // eventObj.keyCode = keyCode; | |
| // eventObj.which = keyCode; | |
| // eventObj.isTrusted = true; | |
| // el = el || document.body; | |
| // // console.log(keyCode, eventObj); | |
| // el.dispatchEvent ? el.dispatchEvent(eventObj) : el.fireEvent("onkeydown", eventObj); | |
| // } | |
| // document.addEventListener("keydown", function (e) { | |
| // console.log('down', e); | |
| // }); | |
| // document.addEventListener("keypress", function (e) { | |
| // console.log('press', e); | |
| // }); | |
| //$($('.menu-btn')[0].parentNode).prepend('<button onclick=\'simulateKeyPress("c")\'>sim</button>'); | |
| // simulateKey('a', $('input')[0]); | |
| // ############################################################################################################ | |
| // go! | |
| // ############################################################################################################ | |
| autoPlay(true); |
Dang
…On Wed, Jan 21, 2026 at 6:24 AM ✿『Lɪᴏɴꜱʙᴇꜱᴛ⁹∞』✿ ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
/*
-
Typing Club Automator - AUTO START
-
Refined by: Lionsbest9
-
Idea Developed by: Edman
*/
(function() {
'use strict';
if (window.typingClubBot) {
console.log('Bot already running! Close it first with:
window.typingClubBot.close()');
return;
}
console.log('🚀 Loading Typing Club Bot v2.0 (Developed by Blaise
Adelan, Idea by Yanis)...');
const gui = document.createElement('div');
gui.id = 'typing-bot-gui';
gui.style.cssText = position: fixed; top: 20px; right: 20px; width:
380px; /* Slightly wider */ background: linear-gradient(135deg, #667eea 0%,
#764ba2 100%); border-radius: 15px; box-shadow: 0 10px 40px
rgba(0,0,0,0.5); z-index: 2147483647; font-family: 'Segoe UI', Arial,
sans-serif; /* More common system fonts */ color: white; transition: width
0.2s ease-in-out, height 0.2s ease-in-out; overflow: hidden; /* Hide
content when minimized */;
gui.innerHTML = `
🎯 Typing Club Bot
—
×
<div id="bot-content" style="padding: 20px;">
<div style="background: rgba(255,255,255,0.1); padding: 10px; border-radius: 8px; font-size: 11px; margin-bottom: 10px;">
<strong style="display: block; margin-bottom: 5px;">📍 Detected:</strong>
<span id="level-info">Waiting...</span>
</div>
<div style="margin-bottom: 18px;">
<label style="display: block; font-size: 12px; font-weight: 600; margin-bottom: 8px; text-transform: uppercase;">Speed</label>
<div style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 10px;">
<input type="range" id="speed-slider" min="30" max="250" value="70" style="width: 100%; margin: 8px 0; cursor: pointer;">
<div style="text-align: center; font-size: 20px; font-weight: 700; margin-top: 5px;"><span id="speed-value">70</span> WPM</div>
</div>
</div>
<div style="margin-bottom: 18px;">
<label style="display: block; font-size: 12px; font-weight: 600; margin-bottom: 8px; text-transform: uppercase;">Accuracy</label>
<div style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 10px;">
<input type="range" id="accuracy-slider" min="90" max="100" value="97" style="width: 100%; margin: 8px 0; cursor: pointer;">
<div style="text-align: center; font-size: 20px; font-weight: 700; margin-top: 5px;"><span id="accuracy-value">97</span>%</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.15); padding: 12px; border-radius: 10px; margin-bottom: 10px;">
<input type="checkbox" id="auto-advance" checked style="width: 20px; height: 20px; cursor: pointer;">
<label for="auto-advance" style="cursor: pointer; flex: 1; font-size: 14px;">Auto-advance to next lesson</label>
</div>
<div style="display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.15); padding: 12px; border-radius: 10px; margin-bottom: 10px;">
<input type="checkbox" id="handle-games" style="width: 20px; height: 20px; cursor: pointer;">
<label for="handle-games" style="cursor: pointer; flex: 1; font-size: 14px;">Attempt game auto-skip (unreliable)</label>
</div>
<button id="start-btn" style="width: 100%; padding: 12px; border: none; border-radius: 10px; font-size: 14px; font-weight: 700; cursor: pointer; text-transform: uppercase; background: #10ac84; color: white; margin-bottom: 10px;">▶ Start Bot</button>
<button id="stop-btn" disabled style="width: 100%; padding: 12px; border: none; border-radius: 10px; font-size: 14px; font-weight: 700; cursor: pointer; text-transform: uppercase; background: #ff6b6b; color: white; margin-bottom: 10px; opacity: 0.5;">⏹ Stop</button>
<div id="status" style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 10px; text-align: center; font-size: 13px; font-weight: 600; margin-top: 15px;">Ready</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 15px;">
<div style="background: rgba(255,255,255,0.15); padding: 10px 8px; border-radius: 8px; text-align: center;">
<div style="font-size: 18px; font-weight: 700;" id="chars-typed">0</div>
<div style="font-size: 9px; opacity: 0.8; margin-top: 3px;">CHARS</div>
</div>
<div style="background: rgba(255,255,255,0.15); padding: 10px 8px; border-radius: 8px; text-align: center;">
<div style="font-size: 18px; font-weight: 700;" id="levels-completed">0</div>
<div style="font-size: 9px; opacity: 0.8; margin-top: 3px;">LESSONS</div>
</div>
</div>
</div>
`;
document.body.appendChild(gui);
const speedSlider = document.getElementById('speed-slider');
const speedValue = document.getElementById('speed-value');
const accuracySlider = document.getElementById('accuracy-slider');
const accuracyValue = document.getElementById('accuracy-value');
const autoAdvance = document.getElementById('auto-advance');
const handleGames = document.getElementById('handle-games'); // New
checkbox
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
const status = document.getElementById('status');
const levelInfo = document.getElementById('level-info');
const charsTypedEl = document.getElementById('chars-typed');
const levelsCompletedEl = document.getElementById('levels-completed');
const closeBtn = document.getElementById('bot-close');
const minimizeBtn = document.getElementById('bot-minimize'); // New
minimize button
const header = document.getElementById('bot-header');
const botContent = document.getElementById('bot-content'); // Main
content area
let botRunning = false;
let charsTypedCount = 0;
let levelsCompleted = 0;
let isMinimized = false;
let originalGuiHeight = gui.offsetHeight; // Store initial height
speedSlider.addEventListener('input', (e) => speedValue.textContent =
e.target.value);
accuracySlider.addEventListener('input', (e) =>
accuracyValue.textContent = e.target.value);
// Draggable
let isDragging = false;
let currentX, currentY, initialX, initialY;
header.addEventListener('mousedown', (e) => {
if (!e.target.closest('button')) { // Don't drag if clicking buttons
in header
initialX = e.clientX - gui.offsetLeft;
initialY = e.clientY - gui.offsetTop;
isDragging = true;
gui.style.cursor = 'grabbing';
}
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
gui.style.left = currentX + 'px';
gui.style.top = currentY + 'px';
gui.style.right = 'auto'; // Disable right positioning once dragged
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
gui.style.cursor = 'grab';
});
minimizeBtn.addEventListener('click', () => {
isMinimized = !isMinimized;
if (isMinimized) {
originalGuiHeight = gui.offsetHeight; // Save current height before
minimizing
gui.style.height = header.offsetHeight + 'px';
botContent.style.display = 'none';
minimizeBtn.textContent = '☐'; // Change to square/max button
} else {
gui.style.height = originalGuiHeight + 'px'; // Restore saved height
botContent.style.display = 'block';
minimizeBtn.textContent = '—'; // Change back to minimize
}
});
// Key mapping for KeyboardEvent 'code' and 'key' properties
const KEY_MAP = {
' ': { key: ' ', code: 'Space', keyCode: 32 },
',': { key: ',', code: 'Comma', keyCode: 188 },
'.': { key: '.', code: 'Period', keyCode: 190 },
';': { key: ';', code: 'Semicolon', keyCode: 186 },
"'": { key: "'", code: 'Quote', keyCode: 222 },
'-': { key: '-', code: 'Minus', keyCode: 189 },
'/': { key: '/', code: 'Slash', keyCode: 191 },
'[': { key: '[', code: 'BracketLeft', keyCode: 219 },
']': { key: ']', code: 'BracketRight', keyCode: 221 },
'\': { key: '\', code: 'Backslash', keyCode: 220 },
'': { key: '', code: 'Backquote', keyCode: 192 },
'=': { key: '=', code: 'Equal', keyCode: 187 },
'{': { key: '{', code: 'BracketLeft', keyCode: 219, shiftKey: true },
'}': { key: '}', code: 'BracketRight', keyCode: 221, shiftKey: true },
'|': { key: '|', code: 'Backslash', keyCode: 220, shiftKey: true },
':': { key: ':', code: 'Semicolon', keyCode: 186, shiftKey: true },
'"': { key: '"', code: 'Quote', keyCode: 222, shiftKey: true },
'<': { key: '<', code: 'Comma', keyCode: 188, shiftKey: true },
'>': { key: '>', code: 'Period', keyCode: 190, shiftKey: true },
'?': { key: '?', code: 'Slash', keyCode: 191, shiftKey: true },
'': { key: '', code: 'Backquote', keyCode: 192, shiftKey: true },
'!': { key: '!', code: 'Digit1', keyCode: 49, shiftKey: true },
'@': { key: '@', code: 'Digit2', keyCode: 50, shiftKey: true },
'#': { key: '#', code: 'Digit3', keyCode: 51, shiftKey: true },
'$': { key: '$', code: 'Digit4', keyCode: 52, shiftKey: true },
'%': { key: '%', code: 'Digit5', keyCode: 53, shiftKey: true },
'^': { key: '^', code: 'Digit6', keyCode: 54, shiftKey: true },
'&': { key: '&', code: 'Digit7', keyCode: 55, shiftKey: true },
'*': { key: '*', code: 'Digit8', keyCode: 56, shiftKey: true },
'(': { key: '(', code: 'Digit9', keyCode: 57, shiftKey: true },
')': { key: ')', code: 'Digit0', keyCode: 48, shiftKey: true },
'*': { key: '*', code: 'Minus', keyCode: 189, shiftKey: true },
'+': { key: '+', code: 'Equal', keyCode: 187, shiftKey: true }
};
// Populate Key_MAP for a-z A-Z 0-9
for (let i = 0; i < 26; i++) {
const charLower = String.fromCharCode('a'.charCodeAt(0) + i);
const charUpper = String.fromCharCode('A'.charCodeAt(0) + i);
KEY_MAP[charLower] = { key: charLower, code: Key${charUpper}, keyCode:
charUpper.charCodeAt(0) };
KEY_MAP[charUpper] = { key: charUpper, code: Key${charUpper}, keyCode:
charUpper.charCodeAt(0), shiftKey: true };
}
for (let i = 0; i < 10; i++) {
const char = String.fromCharCode('0'.charCodeAt(0) + i);
KEY_MAP[char] = { key: char, code: Digit${char}, keyCode:
char.charCodeAt(0) };
}
function typeChar(char, targetElement) {
let eventProps = KEY_MAP[char];
if (!eventProps) {
console.warn(`Character '${char}' not found in key map, using fallback.`);
eventProps = {
key: char,
code: `Key${char.toUpperCase()}`, // Best guess
keyCode: char.charCodeAt(0),
shiftKey: char.toUpperCase() === char && char.toLowerCase() !== char // Guess for uppercase
};
}
const opts = {
key: eventProps.key,
code: eventProps.code,
keyCode: eventProps.keyCode,
which: eventProps.keyCode,
shiftKey: eventProps.shiftKey || false,
bubbles: true,
cancelable: true,
view: window
};
// Dispatch key events
targetElement.dispatchEvent(new KeyboardEvent('keydown', opts));
targetElement.dispatchEvent(new KeyboardEvent('keypress', opts)); // Some apps still listen to keypress
// Update target element's content/value
if (targetElement.isContentEditable) {
const selection = window.getSelection();
const range = document.createRange();
if (selection.focusNode) {
range.setStart(selection.focusNode, selection.focusOffset);
range.insertNode(document.createTextNode(char));
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
} else {
targetElement.textContent += char; // Fallback if no selection
}
} else if (targetElement.tagName === 'INPUT' || targetElement.tagName === 'TEXTAREA') {
const start = targetElement.selectionStart;
const end = targetElement.selectionEnd;
targetElement.value = targetElement.value.substring(0, start) + char + targetElement.value.substring(end);
targetElement.setSelectionRange(start + 1, start + 1);
}
// Dispatch input/change events for reactivity
targetElement.dispatchEvent(new Event('input', { bubbles: true }));
targetElement.dispatchEvent(new Event('change', { bubbles: true }));
targetElement.dispatchEvent(new KeyboardEvent('keyup', opts));
}
function detectLevel() {
// Look for the element that contains the text to be typed
// TypingClub uses divs with 'current-letter' or similar indicators
const currentLetterSpan =
document.querySelector('span.current-letter');
let textContainer = currentLetterSpan ?
currentLetterSpan.closest('div.sentence') ||
currentLetterSpan.closest('div.typable') : null;
if (!textContainer) {
textContainer = document.querySelector('div.word.active') || document.querySelector('div[data-lesson-text]');
}
let fullText = '';
if (textContainer) {
// Reconstruct text from child nodes, preserving spaces/structure
fullText = Array.from(textContainer.querySelectorAll('span, div')).map(el => {
if (el.classList.contains('current-cursor') || el.classList.contains('current-letter') || el.classList.contains('typed')) {
return ''; // Ignore already typed/cursor elements for full text
}
return el.textContent;
}).join('').replace(/\s+/g, ' ').trim(); // Clean up multiple spaces
if (fullText.length > 0) {
levelInfo.textContent = `${fullText.length} chars detected`;
return { text: fullText, type: 'typing' };
}
}
// Alternative for when lessons just started and there's a big block of text
const alternativeTextContainer = document.querySelector('.typable, .typing-text, [data-lesson-text]');
if (alternativeTextContainer && alternativeTextContainer.textContent.trim().length > 10) {
fullText = alternativeTextContainer.textContent.trim().replace(/\s+/g, ' ');
levelInfo.textContent = `${fullText.length} chars detected (fallback)`;
return { text: fullText, type: 'typing' };
}
// Check for games
const gameContainer = document.querySelector('.game-container, canvas, #game, [class*="game-screen"]');
if (gameContainer) {
levelInfo.textContent = 'Game detected';
return { text: null, type: 'game' };
}
levelInfo.textContent = 'No active lesson/game found.';
return { text: null, type: null };
}
async function findAndFocusInput() {
let inputField = null;
for (let attempt = 0; attempt < 50; attempt++) { // Increased attempts
// Prioritize the element indicated as the current target
inputField = document.querySelector('span.current-letter,
div.current-letter, .user-input, input[type="text"]:focus');
// Find an active input field that is currently visible or contenteditable
if (!inputField || !inputField.offsetParent) { // If not found or not visible
const potentialInputs = document.querySelectorAll('input[type="text"], textarea, [contenteditable="true"], .typingArea');
for (const inp of potentialInputs) {
// Check if it's visible and interactable, or if it's the `contenteditable` container
if ( (inp.offsetParent !== null && inp.checkVisibility && inp.checkVisibility()) || inp.isContentEditable) {
inputField = inp;
break;
}
}
}
if (inputField) {
console.log('✅ Found active input field:', inputField);
// Try to focus it if it's not contenteditable (contenteditable can cause issues with focus)
if (!inputField.isContentEditable) {
inputField.focus();
}
// Clear any pre-existing value if it's an input
if (inputField.tagName === 'INPUT' || inputField.tagName === 'TEXTAREA') {
inputField.value = '';
} else if (inputField.isContentEditable) {
inputField.textContent = ''; // Clear contenteditable
}
return inputField;
}
await new Promise(r => setTimeout(r, 100));
}
return null;
}
async function processLevel() {
if (!botRunning) {
console.log("Bot stopped, ceasing level processing.");
return;
}
const level = detectLevel();
if (!level.text && level.type !== 'game') {
status.textContent = '⚠️ No lesson text or game found. Retrying in 3s...';
console.log('❌ Could not find text or game. Retrying...');
await new Promise(r => setTimeout(r, 3000));
if (botRunning) processLevel(); // Retry
return;
}
// Handle games
if (level.type === 'game') {
status.textContent = '🎮 Game detected!';
console.log('🎮 Game detected.');
if (handleGames.checked) {
status.textContent = '🎮 Attempting to skip game...';
console.warn('Attempting to skip game is highly unreliable and may not work.');
// Try to find common "Skip" or "Next" buttons immediately
const skipBtn = document.querySelector('button.skip, a.skip, .skip-button, button.next, a.next, .next-button, .go-button, .btn-primary, [role="button"]'); // Added more skip options
if (skipBtn && skipBtn.offsetParent !== null && skipBtn.checkVisibility()) {
console.log('Found a potential skip/next button, clicking it.');
skipBtn.click();
await new Promise(r => setTimeout(r, 2000)); // Give time for transition
levelsCompleted++;
levelsCompletedEl.textContent = levelsCompleted;
if (botRunning) processLevel();
return;
} else {
status.textContent = '🎮 Game found, no clear skip button. Stopped.';
stopBot(); // Stop if no skip button found
return;
}
} else {
status.textContent = '🎮 Game found (game handling disabled). Stopped.';
stopBot();
return;
}
}
// Handle typing (existing code)
const text = level.text;
status.textContent = '👆 Starting lesson...';
// Focus on the typing area initially
let typingAreaClickTarget = document.querySelector('.typing-widget, [data-lesson-text], .wrapper span.current-letter');
if (typingAreaClickTarget) {
console.log('Attempting to click typing area:', typingAreaClickTarget);
typingAreaClickTarget.click();
} else {
document.body.click(); // Fallback general click
}
await new Promise(r => setTimeout(r, 500)); // Small delay for focus to take effect
const input = await findAndFocusInput();
if (!input) {
status.textContent = '⚠️ Input field not found. Retrying in 3s...';
console.log('❌ No active input field found. Retrying...');
await new Promise(r => setTimeout(r, 3000));
if (botRunning) processLevel(); // Retry
return;
}
console.log('🚀 Typing into:', input);
status.textContent = `⌨️ Typing...`;
const wpm = parseInt(speedSlider.value);
const accuracy = parseInt(accuracySlider.value);
const baseDelay = 60000 / (wpm * 5); // Time per char in ms
for (let i = 0; i < text.length; i++) {
if (!botRunning) return;
const char = text[i];
const shouldError = Math.random() * 100 > accuracy;
// Simulate mistype and backspace for realism
if (shouldError && /[a-zA-Z0-9]/.test(char)) { // Only mistype letters and numbers
const wrongCharAscii = char.charCodeAt(0) + (Math.random() > 0.5 ? 1 : -1);
const wrong = String.fromCharCode(wrongCharAscii);
if (KEY_MAP[wrong]) { // Ensure the wrong character can be mapped
typeChar(wrong, input);
charsTypedCount++; // Count the wrong char
charsTypedEl.textContent = charsTypedCount;
await new Promise(r => setTimeout(r, baseDelay * (0.2 + Math.random() * 0.2))); // Delay for typing error
// Simulate backspace key
const backspaceOpts = {
key: 'Backspace',
code: 'Backspace',
keyCode: 8,
which: 8,
bubbles: true,
cancelable: true,
view: window
};
input.dispatchEvent(new KeyboardEvent('keydown', backspaceOpts));
if (input.isContentEditable) {
const selection = window.getSelection();
if (selection.focusNode && selection.focusOffset > 0) {
const range = document.createRange();
range.setStart(selection.focusNode, selection.focusOffset - 1);
range.setEnd(selection.focusNode, selection.focusOffset);
range.deleteContents();
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
} else {
input.textContent = input.textContent.slice(0, -1);
}
} else if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
input.value = input.value.slice(0, -1);
input.setSelectionRange(input.value.length, input.value.length);
}
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new KeyboardEvent('keyup', backspaceOpts));
await new Promise(r => setTimeout(r, baseDelay * (0.2 + Math.random() * 0.2))); // Delay for backspace
}
}
typeChar(char, input);
charsTypedCount++;
charsTypedEl.textContent = charsTypedCount;
let delay = baseDelay * (0.8 + Math.random() * 0.4); // Randomize delay a bit
if (['.', '!', '?', '\n'].includes(char)) delay *= 2.5; // Longer pause for punctuation/newlines
else if (char === ' ') delay *= 1.3; // Slightly longer for spaces
await new Promise(r => setTimeout(r, delay));
}
console.log('✅ Typing complete for this lesson!');
levelsCompleted++;
levelsCompletedEl.textContent = levelsCompleted;
status.textContent = '✅ Lesson complete! Checking next step...';
if (autoAdvance.checked) {
// Give the page some time to process results and show next button
await new Promise(r => setTimeout(r, 4000));
// Look for common "Continue", "Next Lesson", "Go" buttons or links
const advanceButtonSelectors = [
'button.continue, a.continue, .continue-button, [data-action="next-lesson"]',
'button.next, a.next, .next-button, [data-action="next"]',
'.go-button, .btn-primary, [role="button"]' // General buttons
];
let advanceButton = null;
for (const selector of advanceButtonSelectors) {
advanceButton = document.querySelector(selector);
if (advanceButton && advanceButton.offsetParent !== null && advanceButton.checkVisibility()) {
console.log('Found auto-advance button:', advanceButton);
advanceButton.click();
break;
} else if (advanceButton) {
console.log('Found button, but not visible or interactable yet:', advanceButton);
}
advanceButton = null; // Reset for next selector
}
if (!advanceButton) {
console.log('No specific advance button found, trying generic click/Enter...');
document.body.click(); // Click body as a fallback
await new Promise(r => setTimeout(r, 500));
// Try sending Enter key
const enterOpts = KEY_MAP['\n']; // Or specific enter key code
if (enterOpts) {
document.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
document.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
}
}
// Wait a bit for the next lesson to load
await new Promise(r => setTimeout(r, 2000));
if (botRunning) processLevel(); // Start processing the next lesson
} else {
status.textContent = '⏹ Auto-advance disabled. Stopped after lesson.';
stopBot();
}
}
function startBot() {
botRunning = true;
charsTypedCount = 0; // Reset metrics on new start
levelsCompleted = 0;
charsTypedEl.textContent = '0';
levelsCompletedEl.textContent = '0';
startBtn.disabled = true;
stopBtn.disabled = false;
stopBtn.style.opacity = '1';
startBtn.style.background = '#4CAF50'; // Green
stopBtn.style.background = '#ff6b6b'; // Red
console.log('🤖 Bot started');
processLevel();
}
function stopBot() {
botRunning = false;
startBtn.disabled = false;
stopBtn.disabled = true;
stopBtn.style.opacity = '0.5';
startBtn.style.background = '#10ac84'; // Original green
stopBtn.style.background = '#ff6b6b'; // Red
status.textContent = '⏹ Stopped';
console.log('🤖 Bot stopped.');
}
function closeBot() {
stopBot();
gui.remove();
window.typingClubBot = null;
console.log('Bot GUI closed and removed.');
}
startBtn.addEventListener('click', startBot);
stopBtn.addEventListener('click', stopBot);
closeBtn.addEventListener('click', closeBot); // Corrected this line
window.typingClubBot = { start: startBot, stop: stopBot, close:
closeBot, gui: gui }; // Expose gui for external control
// Initial detection after a short delay
setTimeout(detectLevel, 1000); // Give page a bit more time to render
initially
console.log('✅ Bot loaded! Adjust settings and click "▶ Start Bot" to
begin.');
})();
—
Reply to this email directly, view it on GitHub
<https://gist.github.com/Domiii/52cf49d780ec8c9f01771973c36197af#gistcomment-5949842>
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BZQKLH2P3Z7WSX6UFIEAIOT4H6DYNBFHORZGSZ3HMVZKMY3SMVQXIZNMON2WE2TFMN2F65DZOBS2WR3JON2EG33NNVSW45FGORXXA2LDOOIYFJDUPFYGLJDHNFZXJJLWMFWHKZNIHE3DEOBTHA3DBKTBOR2HE2LCOV2GK44TQKSXMYLMOVS2SMRRGU2TEOBUGUYKI3TBNVS2QYLDORXXEX3JMSBKK5TBNR2WLJDUOJ2WLJDOMFWWLO3UNBZGKYLEL5YGC4TUNFRWS4DBNZ2F6YLDORUXM2LUPGBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DF>
.
You are receiving this email because you commented on the thread.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
/*
Typing Club Automator - AUTO START
Refined by: Lionsbest9
Idea Developed by: Edman
*/
(function() {
'use strict';
if (window.typingClubBot) {
console.log('Bot already running! Close it first with: window.typingClubBot.close()');
return;
}
console.log('🚀 Loading Typing Club Bot v2.0 (Developed by Blaise Adelan, Idea by Yanis)...');
const gui = document.createElement('div');
gui.id = 'typing-bot-gui';
gui.style.cssText =
position: fixed; top: 20px; right: 20px; width: 380px; /* Slightly wider */ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); z-index: 2147483647; font-family: 'Segoe UI', Arial, sans-serif; /* More common system fonts */ color: white; transition: width 0.2s ease-in-out, height 0.2s ease-in-out; overflow: hidden; /* Hide content when minimized */;gui.innerHTML = `
—
×
`;
document.body.appendChild(gui);
const speedSlider = document.getElementById('speed-slider');
const speedValue = document.getElementById('speed-value');
const accuracySlider = document.getElementById('accuracy-slider');
const accuracyValue = document.getElementById('accuracy-value');
const autoAdvance = document.getElementById('auto-advance');
const handleGames = document.getElementById('handle-games'); // New checkbox
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
const status = document.getElementById('status');
const levelInfo = document.getElementById('level-info');
const charsTypedEl = document.getElementById('chars-typed');
const levelsCompletedEl = document.getElementById('levels-completed');
const closeBtn = document.getElementById('bot-close');
const minimizeBtn = document.getElementById('bot-minimize'); // New minimize button
const header = document.getElementById('bot-header');
const botContent = document.getElementById('bot-content'); // Main content area
let botRunning = false;
let charsTypedCount = 0;
let levelsCompleted = 0;
let isMinimized = false;
let originalGuiHeight = gui.offsetHeight; // Store initial height
speedSlider.addEventListener('input', (e) => speedValue.textContent = e.target.value);
accuracySlider.addEventListener('input', (e) => accuracyValue.textContent = e.target.value);
// Draggable
let isDragging = false;
let currentX, currentY, initialX, initialY;
header.addEventListener('mousedown', (e) => {
if (!e.target.closest('button')) { // Don't drag if clicking buttons in header
initialX = e.clientX - gui.offsetLeft;
initialY = e.clientY - gui.offsetTop;
isDragging = true;
gui.style.cursor = 'grabbing';
}
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
gui.style.left = currentX + 'px';
gui.style.top = currentY + 'px';
gui.style.right = 'auto'; // Disable right positioning once dragged
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
gui.style.cursor = 'grab';
});
minimizeBtn.addEventListener('click', () => {
isMinimized = !isMinimized;
if (isMinimized) {
originalGuiHeight = gui.offsetHeight; // Save current height before minimizing
gui.style.height = header.offsetHeight + 'px';
botContent.style.display = 'none';
minimizeBtn.textContent = '☐'; // Change to square/max button
} else {
gui.style.height = originalGuiHeight + 'px'; // Restore saved height
botContent.style.display = 'block';
minimizeBtn.textContent = '—'; // Change back to minimize
}
});
// Key mapping for KeyboardEvent 'code' and 'key' properties
const KEY_MAP = {
' ': { key: ' ', code: 'Space', keyCode: 32 },
',': { key: ',', code: 'Comma', keyCode: 188 },
'.': { key: '.', code: 'Period', keyCode: 190 },
';': { key: ';', code: 'Semicolon', keyCode: 186 },
"'": { key: "'", code: 'Quote', keyCode: 222 },
'-': { key: '-', code: 'Minus', keyCode: 189 },
'/': { key: '/', code: 'Slash', keyCode: 191 },
'[': { key: '[', code: 'BracketLeft', keyCode: 219 },
']': { key: ']', code: 'BracketRight', keyCode: 221 },
'\': { key: '\', code: 'Backslash', keyCode: 220 },
'
': { key: '', code: 'Backquote', keyCode: 192 },'=': { key: '=', code: 'Equal', keyCode: 187 },
'{': { key: '{', code: 'BracketLeft', keyCode: 219, shiftKey: true },
'}': { key: '}', code: 'BracketRight', keyCode: 221, shiftKey: true },
'|': { key: '|', code: 'Backslash', keyCode: 220, shiftKey: true },
':': { key: ':', code: 'Semicolon', keyCode: 186, shiftKey: true },
'"': { key: '"', code: 'Quote', keyCode: 222, shiftKey: true },
'<': { key: '<', code: 'Comma', keyCode: 188, shiftKey: true },
'>': { key: '>', code: 'Period', keyCode: 190, shiftKey: true },
'?': { key: '?', code: 'Slash', keyCode: 191, shiftKey: true },
'
': { key: '', code: 'Backquote', keyCode: 192, shiftKey: true },'!': { key: '!', code: 'Digit1', keyCode: 49, shiftKey: true },
'@': { key: '@', code: 'Digit2', keyCode: 50, shiftKey: true },
'#': { key: '#', code: 'Digit3', keyCode: 51, shiftKey: true },
'$': { key: '$', code: 'Digit4', keyCode: 52, shiftKey: true },
'%': { key: '%', code: 'Digit5', keyCode: 53, shiftKey: true },
'^': { key: '^', code: 'Digit6', keyCode: 54, shiftKey: true },
'&': { key: '&', code: 'Digit7', keyCode: 55, shiftKey: true },
'': { key: '', code: 'Digit8', keyCode: 56, shiftKey: true },
'(': { key: '(', code: 'Digit9', keyCode: 57, shiftKey: true },
')': { key: ')', code: 'Digit0', keyCode: 48, shiftKey: true },
'': { key: '', code: 'Minus', keyCode: 189, shiftKey: true },
'+': { key: '+', code: 'Equal', keyCode: 187, shiftKey: true }
};
// Populate Key_MAP for a-z A-Z 0-9
for (let i = 0; i < 26; i++) {
const charLower = String.fromCharCode('a'.charCodeAt(0) + i);
const charUpper = String.fromCharCode('A'.charCodeAt(0) + i);
KEY_MAP[charLower] = { key: charLower, code:
Key${charUpper}, keyCode: charUpper.charCodeAt(0) };KEY_MAP[charUpper] = { key: charUpper, code:
Key${charUpper}, keyCode: charUpper.charCodeAt(0), shiftKey: true };}
for (let i = 0; i < 10; i++) {
const char = String.fromCharCode('0'.charCodeAt(0) + i);
KEY_MAP[char] = { key: char, code:
Digit${char}, keyCode: char.charCodeAt(0) };}
function typeChar(char, targetElement) {
let eventProps = KEY_MAP[char];
}
function detectLevel() {
// Look for the element that contains the text to be typed
// TypingClub uses divs with 'current-letter' or similar indicators
const currentLetterSpan = document.querySelector('span.current-letter');
let textContainer = currentLetterSpan ? currentLetterSpan.closest('div.sentence') || currentLetterSpan.closest('div.typable') : null;
}
async function findAndFocusInput() {
let inputField = null;
for (let attempt = 0; attempt < 50; attempt++) { // Increased attempts
// Prioritize the element indicated as the current target
inputField = document.querySelector('span.current-letter, div.current-letter, .user-input, input[type="text"]:focus');
}
async function processLevel() {
if (!botRunning) {
console.log("Bot stopped, ceasing level processing.");
return;
}
}
function startBot() {
botRunning = true;
charsTypedCount = 0; // Reset metrics on new start
levelsCompleted = 0;
charsTypedEl.textContent = '0';
levelsCompletedEl.textContent = '0';
startBtn.disabled = true;
stopBtn.disabled = false;
stopBtn.style.opacity = '1';
startBtn.style.background = '#4CAF50'; // Green
stopBtn.style.background = '#ff6b6b'; // Red
console.log('🤖 Bot started');
processLevel();
}
function stopBot() {
botRunning = false;
startBtn.disabled = false;
stopBtn.disabled = true;
stopBtn.style.opacity = '0.5';
startBtn.style.background = '#10ac84'; // Original green
stopBtn.style.background = '#ff6b6b'; // Red
status.textContent = '⏹ Stopped';
console.log('🤖 Bot stopped.');
}
function closeBot() {
stopBot();
gui.remove();
window.typingClubBot = null;
console.log('Bot GUI closed and removed.');
}
startBtn.addEventListener('click', startBot);
stopBtn.addEventListener('click', stopBot);
closeBtn.addEventListener('click', closeBot); // Corrected this line
window.typingClubBot = { start: startBot, stop: stopBot, close: closeBot, gui: gui }; // Expose gui for external control
// Initial detection after a short delay
setTimeout(detectLevel, 1000); // Give page a bit more time to render initially
console.log('✅ Bot loaded! Adjust settings and click "▶ Start Bot" to begin.');
})();