SolverDuo

It solves practice lessons automatically or manually, to increase xp use DuoSolverGrinder tool. To grind xp automatically visit https://duosolver.is-great.net

2024-12-19 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        SolverDuo
// @name:es     SolverDuo
// @namespace   Violentmonkey Scripts
// @match       https://*.duolingo.com/*
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_addStyle
// @require     https://cdn.tailwindcss.com
// @version     1.0.8
// @author      DuoSolverGrinder, has as base DuoPower modified totally.
// @description It solves practice lessons automatically or manually, to increase xp use DuoSolverGrinder tool. To grind xp automatically visit https://duosolver.is-great.net
// @description:es Soluciona lecciones de práctica automáticamente o manualmente, para incrementar xp usa la herramienta DuoSolverGrinder. Para acumular xp automáticamente visita https://duosolver.is-great.net
// ==/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 mainLessonFormClass = "[id='root'] > div > div > div > div > div:first-child._3v4ux";

let panelHtml = `
<div id="panelShowBttns" class="flex flex-col gap-y-2 z-[111] fixed xl:bottom-4 right-5 bottom-48 hidden">
  <button id="panelShowBttn" class="font-bold text-gray-800 shadow-lg rounded-full px-2 bg-indigo-200 border border-2" >+</button>
  <a target="_blank"class="px-1" href="https://duosolver.is-great.net/">
       <img class="rounded-md w-7 h-7" src="https://duosolvergrinder.github.io/DuoSolverGrinder/dsg.png" >
  </a>
</div>
<div id="panelDuoSolver" class="inline-flex flex-col gap-y-4 justify-end rounded-xl fixed xl:bottom-4 right-5 bottom-28 z-[111] border border-1 p-4 bg-indigo-400 dark:bg-gray-900">
  <button id="panelHideBttn" title="hide" class="w-6 self-center rounded-lg  bg-gray-600 text-gray-200">-</button>
  <button id="startBttn" class="px-4 py-2 rounded-md border border-2 bg-blue-600 text-gray-200  dark:bg-gray-300 dark:text-gray-800">Start</button>
  <section class="inline-flex rounded-md shadow-sm" role="group">
      <button id="speedSlow"  type="button"  class="speedSelector px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-s-lg   dark:bg-gray-800 dark:border-gray-700 dark:text-white  dark:focus:ring-blue-500 dark:focus:text-white">
          Slow
      </button>
      <button id="speedMedium" type="button"   class="speedSelector px-4 py-2 text-sm font-medium text-gray-900 bg-white border-t border-b border-gray-200   dark:bg-gray-800 dark:border-gray-700 dark:text-white  dark:focus:ring-blue-500 dark:focus:text-white">
          Medium
      </button>
      <button id="speedFast" type="button"  class="speedSelector px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200   dark:bg-gray-800 dark:border-gray-700 dark:text-white  dark:focus:ring-blue-500 dark:focus:text-white">
          Fast
      </button>
      <button id="speedFastest" type="button"  class="speedSelector px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-e-lg   dark:bg-gray-800 dark:border-gray-700 dark:text-white  dark:focus:ring-blue-500 dark:focus:text-white">
          Fastest
      </button>
  </section>
  <p class="dark:text-gray-200">To grind higher xp, use next link:</p>
  <div class="flex justify-center gap-x-2">
      <a target="_blank" href="https://github.com/DuoSolverGrinder/DuoSolverGrinder/">
         <svg  class="dark:hidden text-red-500 w-8 h-8" fill="none" viewBox="0 0 120 120"
            stroke="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/>
        </svg>
        <svg class="hidden dark:block w-8 h-8" fill="none" viewBox="0 0 120 120" stroke="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/>
        </svg>
      </a>
      <a target="_blank" href="https://duosolver.is-great.net/">
          <img class="rounded-md w-7 h-7" src="https://duosolvergrinder.github.io/DuoSolverGrinder/dsg.png" >
      </a>
  </div>

</div>
`;


let solvesBttnHtml = `
    <button id="solveBttn" style="--web-ui_button-background-color: rgb(var(--color-gold)); --web-ui_button-border-color: rgb(var(--color-gold))"  class="_1rcV8 _1VYyp _1ursp _7jW2t _3DbUj _38g3s lg:block hidden">Solve</button>
    <button id="solveAllBttn" class="_1rcV8 _1VYyp _1ursp _7jW2t _3DbUj _38g3s _2oGJR">Solve All</button>
`;

function insertPanelAndBttns()
{
  let panel = document.getElementById('panelDuoSolver');
  if(!panel) {
     document.body.insertAdjacentHTML('beforeend', panelHtml);
     let bttn = document.getElementById('startBttn');
     bttn.addEventListener('click', startStopMain );
     const showBttn = document.getElementById('panelShowBttn');
     const hideBttn = document.getElementById('panelHideBttn');
     showBttn.addEventListener('click', toggleShowHidePanel );
     hideBttn.addEventListener('click', toggleShowHidePanel );
     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 speedSolveChange()
{
  solveSpeed = this.id;
  GM_setValue('solveSpeed', solveSpeed);
  updateSpeedBttnsActive();
}

function updateSpeedBttnsActive()
{
  const speedBttns = document.querySelectorAll('.speedSelector');
  speedBttns.forEach((element)=> element.classList.remove('bg-gray-900','text-white','font-bold'));
  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":
            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;
}