Auto answer Duolingo script!
// ==UserScript== // @name Duolingo-Cheat-Tool // @namespace http://tampermonkey.net/ // @version 0.2 // @description Auto answer Duolingo script! // @author tranphuquy19 // @match https://www.duolingo.com/lesson* // @match https://www.duolingo.com/learn* // @icon https://www.google.com/s2/favicons?domain=duolingo.com // @grant none // @run-at document-end // ==/UserScript== // WARNING/DISCLAIMER: Cheating can lead to account ban. The script is research-oriented in programming. // Enjoy learning new languages. Thank you! // Update 2023-Aug-07: Fix bug, add new challenge types const DEBUG = true; // let AUTO_PRACTICE = localStorage.getItem('AUTO_PRACTICE') === 'true' ? true : false; // earn exp by practice let AUTO_PRACTICE = false; let mainInterval; let practiceInterval; const dataTestComponentClassName = 'e4VJZ'; const TIME_OUT = 1000; // Challenge types const CHARACTER_SELECT_TYPE = 'characterSelect'; const CHARACTER_MATCH_TYPE = 'characterMatch'; const MATCH_TYPE = 'match'; const TRANSLATE_TYPE = 'translate'; const LISTEN_TAP_TYPE = 'listenTap'; const NAME_TYPE = 'name'; const COMPLETE_REVERSE_TRANSLATION_TYPE = 'completeReverseTranslation'; const LISTEN_TYPE = 'listen'; const SELECT_TYPE = 'select'; const JUDGE_TYPE = 'judge'; const FORM_TYPE = 'form'; const LISTEN_COMPREHENSION_TYPE = 'listenComprehension'; const READ_COMPREHENSION_TYPE = 'readComprehension'; const CHARACTER_INTRO_TYPE = 'characterIntro'; const DIALOGUE_TYPE = 'dialogue'; const SELECT_TRANSCRIPTION_TYPE = 'selectTranscription'; const SPEAK_TYPE = 'speak'; const SELECT_PRONUNCIATION_TYPE = 'selectPronunciation'; const ASSIST_TYPE = 'assist'; const LISTEN_MATCH_TYPE = 'listenMatch'; const LISTEN_COMPLETE_TYPE = 'listenComplete'; // Query DOM keys const CHALLENGE_CHOICE_CARD = '[data-test="challenge-choice"]'; const CHALLENGE_CHOICE = '[data-test="challenge-choice"]'; const CHALLENGE_TRANSLATE_INPUT = '[data-test="challenge-translate-input"]'; const CHALLENGE_LISTEN_TAP = '[data-test="challenge-listenTap"]'; const CHALLENGE_JUDGE_TEXT = '[data-test="challenge-judge-text"]'; const CHALLENGE_TEXT_INPUT = '[data-test="challenge-text-input"]'; const CHALLENGE_TAP_TOKEN = '[data-test*="-challenge-tap-token"]'; const PLAYER_NEXT = '[data-test="player-next"]'; const PLAYER_SKIP = '[data-test="player-skip"]'; const BLAME_INCORRECT = '[data-test="blame blame-incorrect"]'; const CHARACTER_MATCH = '[data-test="challenge challenge-characterMatch"]'; const PRACTICE_BTN = '[data-test="global-practice"]'; const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); function getChallengeObj(theObject) { let r###lt = null; if (theObject instanceof Array) { for (let i = 0; i < theObject.length; i++) { r###lt = getChallengeObj(theObject[i]); if (r###lt) { break; } } } else { for (let prop in theObject) { if (prop == 'challenge') { if (typeof theObject[prop] == 'object') { return theObject; } } if (theObject[prop] instanceof Object || theObject[prop] instanceof Array) { r###lt = getChallengeObj(theObject[prop]); if (r###lt) { break; } } } } return r###lt; } function getChallenge() { // const dataTestComponentClassName = 'e4VJZ'; const dataTestDOM = document.getElementsByClassName(dataTestComponentClassName)[0]; if (!dataTestDOM) { document.querySelectorAll(PLAYER_NEXT)[0].dispatchEvent(clickEvent); return null; } else { const dataTestAtrr = Object.keys(dataTestDOM).filter(att => /^__reactProps/g.test(att))[0]; const childDataTestProps = dataTestDOM[dataTestAtrr]; const { challenge } = getChallengeObj(childDataTestProps); if (DEBUG) console.log('challenge', challenge); return challenge; } } function nextQuestion() { // document.dispatchEvent(new KeyboardEvent('keydown', { 'keyCode': 13, 'which': 13 })); document.querySelectorAll(PLAYER_NEXT)[0].dispatchEvent(clickEvent); } function dynamicInput(element, msg) { let input = element; let lastValue = input.value; input.value = msg; let event = new Event('input', { bubbles: true }); // hack React15 event.simulated = true; // hack React16 内部定义了descriptor拦截value,此处重置状态 let tracker = input._valueTracker; if (tracker) { tracker.setValue(lastValue); } input.dispatchEvent(event); } function classify() { const challenge = getChallenge(); if (!challenge) return; if (DEBUG) console.log(`${challenge.type}`, challenge); switch (challenge.type) { case SELECT_PRONUNCIATION_TYPE: case READ_COMPREHENSION_TYPE: case LISTEN_COMPREHENSION_TYPE: case ASSIST_TYPE: case FORM_TYPE: { // trắc nghiệm 1 đáp án const { choices, correctIndex } = challenge; if (DEBUG) console.log('READ_COMPREHENSION LISTEN_COMPREHENSION FORM ASSIST', { choices, correctIndex }); document.querySelectorAll(CHALLENGE_CHOICE)[correctIndex].dispatchEvent(clickEvent); return { choices, correctIndex }; } case SELECT_TYPE: case CHARACTER_SELECT_TYPE: { // trắc nghiệm 1 đáp án const { choices, correctIndex } = challenge; if (DEBUG) console.log('SELECT CHARACTER_SELECT', { choices, correctIndex }); document.querySelectorAll(CHALLENGE_CHOICE_CARD)[correctIndex].dispatchEvent(clickEvent); return { choices, correctIndex }; } case MATCH_TYPE: { const { pairs } = challenge; // remove all span tag element const tokens = Array.from(document.querySelectorAll(CHALLENGE_TAP_TOKEN)).filter(token => token.tagName !== 'SPAN'); pairs.forEach((pair) => { for (let i = 0; i < tokens.length; i++) { const _innerText = tokens[i].innerText.split('\n')[1]; if (_innerText === pair.fromToken || _innerText === pair.learningToken) { tokens[i].dispatchEvent(clickEvent); } } }) return { pairs }; } case LISTEN_MATCH_TYPE: { const { pairs } = challenge; const tokens = Array.from(document.querySelectorAll(CHALLENGE_TAP_TOKEN)); // click 2 token has same attribute 'data-test' for (let i = 0; i < tokens.length; i++) { const firstToken = tokens[i]; for (let j = i + 1; j < tokens.length; j++) { const secondToken = tokens[j]; if (firstToken.getAttribute('data-test') === secondToken.getAttribute('data-test')) { firstToken.dispatchEvent(clickEvent); secondToken.dispatchEvent(clickEvent); } } } return { pairs }; } case CHARACTER_MATCH_TYPE: { // tập hợp các cặp thẻ const { pairs } = challenge; const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN); pairs.forEach((pair) => { for (let i = 0; i < tokens.length; i++) { const _innerText = tokens[i].innerText.split('\n')[1]; if (_innerText === pair.transliteration || _innerText === pair.character) { tokens[i].dispatchEvent(clickEvent); } } }) return { pairs }; } case TRANSLATE_TYPE: { const { correctTokens, correctSolutions } = challenge; if (correctTokens) { if (DEBUG) console.log('TRANSLATE_correctTokens', { correctTokens }); const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN); let ignoreTokeIndexes = []; for (let correctTokenIndex in correctTokens) { for (let tokenIndex in tokens) { const token = tokens[tokenIndex]; if (ignoreTokeIndexes.includes(tokenIndex)) continue; if (token.innerText === correctTokens[correctTokenIndex]) { token.dispatchEvent(clickEvent); ignoreTokeIndexes.push(tokenIndex); if (DEBUG) console.log(`correctTokenIndex [${correctTokens[correctTokenIndex]}] - tokenIndex [${token.innerText}]`); break; }; } } } else if (!!correctSolutions) { if (DEBUG) console.log('TRANSLATE_correctSolutions', { correctSolutions }); let textInputElement = document.querySelectorAll(CHALLENGE_TRANSLATE_INPUT)[0]; dynamicInput(textInputElement, correctSolutions[0]); } return { correctTokens }; } case NAME_TYPE: { // nhập đán án const { correctSolutions } = challenge; if (DEBUG) console.log('NAME', { correctSolutions }); let textInputElement = document.querySelectorAll(CHALLENGE_TEXT_INPUT)[0]; let correctSolution = correctSolutions[0]; dynamicInput(textInputElement, correctSolution); return { correctSolutions }; } case COMPLETE_REVERSE_TRANSLATION_TYPE: { // điền vào từ còn thiếu const { displayTokens } = challenge; if (DEBUG) console.log('COMPLETE_REVERSE_TRANLATION', { displayTokens }); const { text } = displayTokens.filter(token => token.isBlank)[0]; let textInputElement = document.querySelectorAll(CHALLENGE_TEXT_INPUT)[0]; dynamicInput(textInputElement, text); return { displayTokens }; } case LISTEN_TAP_TYPE: { const { correctTokens } = challenge; if (DEBUG) console.log('LISTEN_TAP', { correctTokens }); const tokens = document.querySelectorAll(CHALLENGE_TAP_TOKEN); const ignoreTokens = []; for (let i = 0; i < correctTokens.length; i++) { for (let j = 0; j < tokens.length; j++) { if (tokens[j].innerText === correctTokens[i] && !ignoreTokens.includes(j)) { ignoreTokens.push(j); tokens[j].dispatchEvent(clickEvent); break; } } } return { correctTokens }; } case LISTEN_COMPLETE_TYPE: { const { displayTokens } = challenge; if (DEBUG) console.log('LISTEN_COMPLETE', { displayTokens }); let textInputElement = document.querySelectorAll(CHALLENGE_TEXT_INPUT)[0]; const correctAnswer = displayTokens.find(token => token.isBlank).text; dynamicInput(textInputElement, correctAnswer); return { displayTokens }; } case LISTEN_TYPE: { // nghe và điền vào ô input const { prompt } = challenge; if (DEBUG) console.log('LISTEN', { prompt }); let textInputElement = document.querySelectorAll(CHALLENGE_TRANSLATE_INPUT)[0]; dynamicInput(textInputElement, prompt); return { prompt }; } case JUDGE_TYPE: { // trắc nghiệm 1 đáp án const { correctIndices } = challenge; if (DEBUG) console.log('JUDGE', { correctIndices }); document.querySelectorAll(CHALLENGE_JUDGE_TEXT)[correctIndices[0]].dispatchEvent(clickEvent); return { correctIndices }; } case DIALOGUE_TYPE: case CHARACTER_INTRO_TYPE: { // trắc nghiệm 1 đáp án const { choices, correctIndex } = challenge; if (DEBUG) console.log('DIALOGUE CHARACTER_INTRO', { choices, correctIndex }); document.querySelectorAll(CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent); return { choices, correctIndex }; } case SELECT_TRANSCRIPTION_TYPE: { const { choices, correctIndex } = challenge; if (DEBUG) console.log('DIALOGUE CHARACTER_INTRO', { choices, correctIndex }); document.querySelectorAll(CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent); return { choices, correctIndex }; } case SPEAK_TYPE: { const { prompt } = challenge; if (DEBUG) console.log('SPEAK', { prompt }); document.querySelectorAll(PLAYER_SKIP)[0].dispatchEvent(clickEvent); return { prompt }; } default: break; } } function breakWhenIncorrect() { const isBreak = document.querySelectorAll(BLAME_INCORRECT).length > 0; if (isBreak) { console.log('Incorrect, stopped'); clearInterval(mainInterval); }; } function clickPracticeButton() { try { document.querySelectorAll(PRACTICE_BTN)[0].dispatchEvent(clickEvent); } catch (e) { console.log(e); } } function main() { try { let isPlayerNext = document.querySelectorAll(PLAYER_NEXT)[0].textContent.toUpperCase(); if (isPlayerNext.valueOf() !== 'CONTINUE') { classify(); breakWhenIncorrect() nextQuestion(); } setTimeout(nextQuestion, 150); } catch (e) { console.log(e); } } function solveChallenge() { AUTO_PRACTICE = localStorage.getItem('AUTO_PRACTICE') || false; // earn exp by practice if (AUTO_PRACTICE && window.location.href.endsWith('/learn')) { // click practice button practiceInterval = setTimeout(clickPracticeButton, 5000); } mainInterval = setInterval(main, TIME_OUT); console.log(`to stop run this command clearInterval(${mainInterval})`); } // solveChallenge(); (solveChallenge)();