// ==UserScript== // @name DuoSolver // @name:es DuoSolver // @name:es-419 DuoSolver // @name:fr DuoSolver // @name:vi DuoSolver // @name:fr-CA DuoSolver // @namespace Violentmonkey Scripts // @match https://*.duolingo.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @require https://cdn.tailwindcss.com // @version 1.1.1 // @author DuoSolverGrinder, has as base DuoPower modified totally. // @description It solves practice lessons automatically or manually, to increase xp at different speed use DuoSolverGrinder tool (https://duosolver.is-great.net). // @description:es Soluciona lecciones de práctica automáticamente o manualmente, para incrementar xp a velocidades diferentes usa la herramienta DuoSolverGrinder (https://duosolver.is-great.net). // @description:es-419 Soluciona lecciones de práctica automáticamente o manualmente, para incrementar xp a velocidades diferentes usa la herramienta DuoSolverGrinder (https://duosolver.is-great.net). // @description:fr Résolvez les leçons pratiques automatiquement ou manuellement, pour augmenter l'XP à différentes vitesses, utilisez l'outil DuoSolverGrinder (https://duosolver.is-great.net). // @description:fr-CA Résolvez les leçons pratiques automatiquement ou manuellement, pour augmenter l'XP à différentes vitesses, utilisez l'outil DuoSolverGrinder (https://duosolver.is-great.net). // @description:vi Giải bài thực hành tự động hoặc thủ công, để tăng xp ở các tốc độ khác nhau hãy sử dụng công cụ DuoSolverGrinder (https://duosolver.is-great.net). // @downloadURL none // ==/UserScript== let solveTimerId; let isAutoMode = GM_getValue('isAutoMode', false); let isPanelShow = GM_getValue('isPanelShow', true); let solveSpeedList = {'speedSlow': 2000, 'speedMedium': 1000, 'speedFast': 500, 'speedFastest': 0 } let solveSpeed = GM_getValue('solveSpeed', 'speedMedium'); const duoSolverGrinderUrl = "https://duosolver.is-great.net"; const version = '1.1.1'; const mainLessonFormClass = "[id='root'] > div > div > div > div > div:first-child._3v4ux"; let panelHtml = `
`; let solvesBttnHtml = ` `; let infoMssg = `Accounts created using google or facebook services are not succesfully logged in DuoSolverGrinder. For these types of accounts signing with duolingo's token is possible now! Just click on the icon "DSG" and a new tab will be opened to confirm the logged in DuoSolverGrinder tool. For those who Need For Speed, DuoSolverGrinder is offering different amount to increase xp, packages with more than 100 xp, from the slowest to the fastest as possible!`; function insertPanelAndBttns() { let panel = document.getElementById('panelDuoSolver'); if(!panel) { document.body.insertAdjacentHTML('beforeend', panelHtml); document.getElementById('startBttn').addEventListener('click', startStopMain ); document.getElementById('panelShowBttn').addEventListener('click', toggleShowHidePanel ); document.getElementById('panelHideBttn').addEventListener('click', toggleShowHidePanel ); document.getElementById('bttnInfo').addEventListener('click', ()=> alert(infoMssg)); document.getElementById('bttnLoginWithToken').addEventListener('click', signInWithToken); document.querySelectorAll('.speedSelector').forEach((element)=> element.addEventListener('click', speedSolveChange)); updatePanelDisplay(); updateSpeedBttnsActive(); } if (window.location.pathname === '/lesson' || window.location.pathname === '/practice') { addButtons(); panel ? panel.children[1].style.display = 'none' : null; return; } panel ? panel.children[1].style.display = '': null; } window.onload = (event) => { GM_addStyle('img { max-width: none}'); } setInterval(insertPanelAndBttns, 1000); function signInWithToken() { let jwtToken = document.cookie.split(';').find(cookie => cookie.includes('jwt_token')).split('=')[1]; window.open(`${duoSolverGrinderUrl}/loginToken?signIn=${jwtToken}`, '_blank').focus() } function speedSolveChange() { solveSpeed = this.id; GM_setValue('solveSpeed', solveSpeed); updateSpeedBttnsActive(); } function updateSpeedBttnsActive() { const speedBttns = document.querySelectorAll('.speedSelector'); speedBttns.forEach((element)=> { if(element.id == solveSpeed) { element.classList.remove('bg-white', 'text-gray-900', 'font-semibold', 'dark:bg-gray-800'); element.classList.add('bg-gray-900', 'text-white', 'font-bold', 'dark:bg-black'); return; } element.classList.add('bg-white', 'text-gray-900', 'font-semibold', 'dark:bg-gray-800'); element.classList.remove('bg-gray-900', 'text-white', 'font-bold', 'dark:bg-black'); }); } function updatePanelDisplay(display) { const panelShowBttns = document.getElementById('panelShowBttns'); const panel = document.getElementById('panelDuoSolver'); if(isPanelShow) { panel.classList.remove('collapse'); panelShowBttns.classList.add('hidden'); return; } GM_setValue('isPanelShow', false); panel.classList.add('collapse'); panelShowBttns.classList.remove('hidden'); return; } function toggleShowHidePanel() { isPanelShow = !GM_getValue('isPanelShow'); GM_setValue('isPanelShow', isPanelShow ) updatePanelDisplay(); } function setAutoMode(state) { isAutoMode = state; GM_setValue('isAutoMode', state); } function startStopMain() { setAutoMode(!isAutoMode); updateBttnsCaptions(); if(isAutoMode) { window.location.assign('/practice'); } } function addButtons() { const checkBttn = document.querySelectorAll('[data-test="player-next"]')[0]; if(!checkBttn) { return; } let solveAllBttn = document.getElementById("solveAllBttn"); if (solveAllBttn !== null) { return; } checkBttn.parentElement.classList.add('flex', 'gap-x-8'); checkBttn.parentElement.insertAdjacentHTML('beforeend',solvesBttnHtml); const solveBttn = document.getElementById("solveBttn"); solveAllBttn = document.getElementById("solveAllBttn"); solveBttn.addEventListener('click', solveOne); solveAllBttn.addEventListener('click', solvingAll); updateBttnsCaptions(); resetTimerAutoMode(); } function updateBttnsCaptions() { const solveAllBttn = document.getElementById("solveAllBttn"); const startBttn = document.getElementById("startBttn"); if (isAutoMode) { solveAllBttn ? solveAllBttn.innerText = "PAUSE ALL" : null; startBttn ? startBttn.innerText = "Stop" : null; } else { solveAllBttn ? solveAllBttn.innerText = "SOLVE ALL" : null; startBttn ? startBttn.innerText = "Start" : null; } } function solvingAll() { setAutoMode(!isAutoMode); updateBttnsCaptions(); resetTimerAutoMode(); } function solveOne() { const practiceAgain = document.querySelector('[data-test="player-practice-again"]'); if (practiceAgain !== null && isAutoMode) { practiceAgain.click(); return; } if(document.querySelector('[data-test="session-complete-slide"]') && isAutoMode && !practiceAgain && window.innerWidth <= 768 && window.location.pathname === '/practice') { window.location.assign('/practice'); return; } let subType = ""; try { window.sol = findReact(document.querySelectorAll(mainLessonFormClass)[0]).props.currentChallenge; subType = window.sol.challengeGeneratorIdentifier.specificType; } catch { let next = document.querySelector('[data-test="player-next"]'); if (next) { next.click(); } resetTimerAutoMode(); return; } if (!window.sol) { resetTimerAutoMode(); return; } let nextButton = document.querySelector('[data-test="player-next"]'); if (!nextButton) { resetTimerAutoMode(); return; } switch(window.sol.type) { case "listenMatch": case "listenIsolation": case "listenTap": case "speak": const buttonSkip = document.querySelector('button[data-test="player-skip"]'); if (buttonSkip) { buttonSkip.click(); } break; case "translate": switch(subType) { case "reverse_translate": const elm = document.querySelector('textarea[data-test="challenge-translate-input"]'); if(elm) { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; nativeInputValueSetter.call(elm, window.sol.correctSolutions ? window.sol.correctSolutions[0] : window.sol.prompt); let inputEvent = new Event('input', { bubbles: true }); elm.dispatchEvent(inputEvent); } break; case "tap": case "reverse_tap": translateTapReverseTapSolve(); break; default: null; } break; case "assist": case "gapFill": document.querySelectorAll('[data-test="challenge-choice"]')[window.sol.correctIndex]?.click(); break; case "name": let textInput = document.querySelector('[data-test="challenge-text-input"]'); if(textInput) { if(window.sol.correctSolutions) { let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; nativeInputValueSetter.call(textInput, window.sol.correctSolutions[0]); let inputEvent = new Event('input', { bubbles: true }); textInput.dispatchEvent(inputEvent); } } break; case "partialReverseTranslate": let elm = document.querySelector('[data-test*="challenge-partialReverseTranslate"]')?.querySelector("span[contenteditable]"); if(elm) { let nativeInputNodeTextSetter = Object.getOwnPropertyDescriptor(Node.prototype, "textContent").set; nativeInputNodeTextSetter.call(elm, window.sol?.displayTokens?.filter(t => t.isBlank)?.map(t => t.text)?.join()?.replaceAll(',', '') ); let inputEvent = new Event('input', { bubbles: true }); elm.dispatchEvent(inputEvent); } break; default: null; } nextButton.click(); resetTimerAutoMode(); } function translateTapReverseTapSolve() { const all_tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]'); const correct_tokens = window.sol.correctTokens; const clicked_tokens = []; correct_tokens.forEach(correct_token => { const matching_elements = Array.from(all_tokens).filter(element => element.textContent.trim() === correct_token.trim()); if (matching_elements.length > 0) { const match_index = clicked_tokens.filter(token => token.textContent.trim() === correct_token.trim()).length; if (match_index < matching_elements.length) { matching_elements[match_index].click(); clicked_tokens.push(matching_elements[match_index]); } else { clicked_tokens.push(matching_elements[0]); } } }); } function resetTimerAutoMode() { if(isAutoMode) { clearTimeout(solveTimerId); solveTimerId = setTimeout(solveOne, solveSpeedList[solveSpeed]); } } function findReact(dom) { let reactProps = Object.keys(dom.parentElement).find((key) => key.startsWith("__reactProps$")); let child = dom?.parentElement?.[reactProps]?.children; return child?.props?.children?._owner?.stateNode ?? child?._owner?.stateNode; }