SolverDuo

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

当前为 2024-12-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name SolverDuo
  3. // @name:es SolverDuo
  4. // @namespace Violentmonkey Scripts
  5. // @match https://*.duolingo.com/*
  6. // @grant GM_getValue
  7. // @grant GM_setValue
  8. // @grant GM_addStyle
  9. // @require https://cdn.tailwindcss.com
  10. // @version 1.1.0
  11. // @author DuoSolverGrinder, has as base DuoPower modified totally.
  12. // @description It solves practice lessons automatically or manually, to increase xp use DuoSolverGrinder tool (https://duosolver.is-great.net).
  13. // @description:es Soluciona lecciones de práctica automáticamente o manualmente, para incrementar xp usa la herramienta DuoSolverGrinder (https://duosolver.is-great.net).
  14. // ==/UserScript==
  15.  
  16. let solveTimerId;
  17. let isAutoMode = GM_getValue('isAutoMode', false);
  18. let isPanelShow = GM_getValue('isPanelShow', true);
  19. let solveSpeedList = {'speedSlow': 2000, 'speedMedium': 1000, 'speedFast': 500, 'speedFastest': 0 }
  20. let solveSpeed = GM_getValue('solveSpeed', 'speedMedium');
  21.  
  22. const version = '1.1.0';
  23. const mainLessonFormClass = "[id='root'] > div > div > div > div > div:first-child._3v4ux";
  24.  
  25. let panelHtml = `
  26. <div id="panelShowBttns" class="flex flex-col gap-y-2 z-[111] fixed xl:bottom-4 right-5 bottom-48 hidden">
  27. <button id="panelShowBttn" class="font-bold text-gray-800 shadow-lg rounded-full px-2 bg-indigo-200 border border-2" >+</button>
  28. <a target="_blank"class="px-1" href="https://duosolver.is-great.net/">
  29. <img class="rounded-md w-7 h-7" src="https://duosolvergrinder.github.io/DuoSolverGrinder/dsg.png" >
  30. </a>
  31. </div>
  32. <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">
  33. <button id="panelHideBttn" title="hide" class="w-6 self-center rounded-lg bg-gray-600 text-gray-200">-</button>
  34. <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>
  35. <section class="inline-flex rounded-md shadow-sm" role="group">
  36. <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">
  37. Slow
  38. </button>
  39. <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">
  40. Medium
  41. </button>
  42. <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">
  43. Fast
  44. </button>
  45. <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">
  46. Fastest
  47. </button>
  48. </section>
  49. <p class="dark:text-gray-200">To grind higher xp, use next link:</p>
  50. <div class="flex justify-center gap-x-2">
  51. <a target="_blank" href="https://github.com/DuoSolverGrinder/DuoSolverGrinder/">
  52. <svg class="dark:hidden text-red-500 w-8 h-8" fill="none" viewBox="0 0 120 120"
  53. 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"/>
  54. </svg>
  55. <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"/>
  56. </svg>
  57. </a>
  58. <a target="_blank" href="https://duosolver.is-great.net/">
  59. <img class="rounded-md w-7 h-7" src="https://duosolvergrinder.github.io/DuoSolverGrinder/dsg.png" >
  60. </a>
  61. </div>
  62. <div class="flex justify-center">
  63. <span class="dark:text-gray-200">v${version}</span>
  64. </div>
  65. </div>
  66. `;
  67.  
  68.  
  69. let solvesBttnHtml = `
  70. <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>
  71. <button id="solveAllBttn" class="_1rcV8 _1VYyp _1ursp _7jW2t _3DbUj _38g3s _2oGJR">Solve All</button>
  72. `;
  73.  
  74. function insertPanelAndBttns()
  75. {
  76. let panel = document.getElementById('panelDuoSolver');
  77. if(!panel) {
  78. document.body.insertAdjacentHTML('beforeend', panelHtml);
  79. let bttn = document.getElementById('startBttn');
  80. bttn.addEventListener('click', startStopMain );
  81. const showBttn = document.getElementById('panelShowBttn');
  82. const hideBttn = document.getElementById('panelHideBttn');
  83. showBttn.addEventListener('click', toggleShowHidePanel );
  84. hideBttn.addEventListener('click', toggleShowHidePanel );
  85. document.querySelectorAll('.speedSelector').forEach((element)=> element.addEventListener('click', speedSolveChange));
  86. updatePanelDisplay();
  87. updateSpeedBttnsActive();
  88. }
  89. if (window.location.pathname === '/lesson' || window.location.pathname === '/practice') {
  90. addButtons();
  91. panel ? panel.children[1].style.display = 'none' : null;
  92. return;
  93. }
  94. panel ? panel.children[1].style.display = '': null;
  95. }
  96.  
  97. window.onload = (event) => {
  98. GM_addStyle('img { max-width: none}');
  99. }
  100.  
  101. setInterval(insertPanelAndBttns, 1000);
  102.  
  103.  
  104. function speedSolveChange()
  105. {
  106. solveSpeed = this.id;
  107. GM_setValue('solveSpeed', solveSpeed);
  108. updateSpeedBttnsActive();
  109. }
  110.  
  111. function updateSpeedBttnsActive()
  112. {
  113. const speedBttns = document.querySelectorAll('.speedSelector');
  114. speedBttns.forEach((element)=> {
  115. if(element.id == solveSpeed) {
  116. element.classList.remove('bg-white', 'text-gray-900', 'font-semibold', 'dark:bg-gray-800');
  117. element.classList.add('bg-gray-900', 'text-white', 'font-bold', 'dark:bg-black');
  118. return;
  119. }
  120. element.classList.add('bg-white', 'text-gray-900', 'font-semibold', 'dark:bg-gray-800');
  121. element.classList.remove('bg-gray-900', 'text-white', 'font-bold', 'dark:bg-black');
  122. });
  123.  
  124. }
  125.  
  126. function updatePanelDisplay(display)
  127. {
  128. const panelShowBttns = document.getElementById('panelShowBttns');
  129. const panel = document.getElementById('panelDuoSolver');
  130. if(isPanelShow) {
  131. panel.classList.remove('collapse');
  132. panelShowBttns.classList.add('hidden');
  133. return;
  134. }
  135. GM_setValue('isPanelShow', false);
  136. panel.classList.add('collapse');
  137. panelShowBttns.classList.remove('hidden');
  138. return;
  139.  
  140. }
  141.  
  142. function toggleShowHidePanel()
  143. {
  144. isPanelShow = !GM_getValue('isPanelShow');
  145. GM_setValue('isPanelShow', isPanelShow )
  146. updatePanelDisplay();
  147. }
  148.  
  149. function setAutoMode(state)
  150. {
  151. isAutoMode = state;
  152. GM_setValue('isAutoMode', state);
  153. }
  154.  
  155. function startStopMain()
  156. {
  157. setAutoMode(!isAutoMode);
  158. updateBttnsCaptions();
  159. if(isAutoMode) {
  160. window.location.assign('/practice');
  161. }
  162.  
  163. }
  164.  
  165.  
  166. function addButtons()
  167. {
  168. const checkBttn = document.querySelectorAll('[data-test="player-next"]')[0];
  169. if(!checkBttn) {
  170. return;
  171. }
  172. let solveAllBttn = document.getElementById("solveAllBttn");
  173. if (solveAllBttn !== null) {
  174. return;
  175. }
  176. checkBttn.parentElement.classList.add('flex', 'gap-x-8');
  177. checkBttn.parentElement.insertAdjacentHTML('beforeend',solvesBttnHtml);
  178.  
  179. const solveBttn = document.getElementById("solveBttn");
  180. solveAllBttn = document.getElementById("solveAllBttn");
  181. solveBttn.addEventListener('click', solveOne);
  182. solveAllBttn.addEventListener('click', solvingAll);
  183.  
  184. updateBttnsCaptions();
  185. resetTimerAutoMode();
  186. }
  187.  
  188.  
  189.  
  190. function updateBttnsCaptions()
  191. {
  192. const solveAllBttn = document.getElementById("solveAllBttn");
  193. const startBttn = document.getElementById("startBttn");
  194. if (isAutoMode) {
  195. solveAllBttn ? solveAllBttn.innerText = "PAUSE ALL" : null;
  196. startBttn ? startBttn.innerText = "Stop" : null;
  197. } else {
  198. solveAllBttn ? solveAllBttn.innerText = "SOLVE ALL" : null;
  199. startBttn ? startBttn.innerText = "Start" : null;
  200. }
  201. }
  202.  
  203.  
  204. function solvingAll()
  205. {
  206. setAutoMode(!isAutoMode);
  207. updateBttnsCaptions();
  208. resetTimerAutoMode();
  209. }
  210.  
  211. function solveOne()
  212. {
  213. const practiceAgain = document.querySelector('[data-test="player-practice-again"]');
  214. if (practiceAgain !== null && isAutoMode) {
  215. practiceAgain.click();
  216. return;
  217. }
  218. if(document.querySelector('[data-test="session-complete-slide"]') && isAutoMode && !practiceAgain && window.innerWidth <= 768 && window.location.pathname === '/practice') {
  219. window.location.assign('/practice');
  220. return;
  221. }
  222.  
  223. let subType = "";
  224. try {
  225. window.sol = findReact(document.querySelectorAll(mainLessonFormClass)[0]).props.currentChallenge;
  226. subType = window.sol.challengeGeneratorIdentifier.specificType;
  227. } catch {
  228. let next = document.querySelector('[data-test="player-next"]');
  229. if (next) {
  230. next.click();
  231. }
  232. resetTimerAutoMode();
  233. return;
  234. }
  235. if (!window.sol) {
  236. resetTimerAutoMode();
  237. return;
  238. }
  239.  
  240. let nextButton = document.querySelector('[data-test="player-next"]');
  241. if (!nextButton) {
  242. resetTimerAutoMode();
  243. return;
  244. }
  245.  
  246.  
  247. switch(window.sol.type) {
  248. case "listenMatch":
  249. case "listenIsolation":
  250. case "listenTap":
  251. case "speak":
  252. const buttonSkip = document.querySelector('button[data-test="player-skip"]');
  253. if (buttonSkip) {
  254. buttonSkip.click();
  255. }
  256. break;
  257. case "translate":
  258. switch(subType)
  259. {
  260. case "reverse_translate":
  261. const elm = document.querySelector('textarea[data-test="challenge-translate-input"]');
  262. if(elm) {
  263. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
  264. nativeInputValueSetter.call(elm, window.sol.correctSolutions ? window.sol.correctSolutions[0] : window.sol.prompt);
  265. let inputEvent = new Event('input', {
  266. bubbles: true
  267. });
  268. elm.dispatchEvent(inputEvent);
  269. }
  270. break;
  271. case "tap":
  272. case "reverse_tap":
  273. translateTapReverseTapSolve();
  274. break;
  275. default:
  276. null;
  277. }
  278. break;
  279. case "assist":
  280. case "gapFill":
  281. document.querySelectorAll('[data-test="challenge-choice"]')[window.sol.correctIndex]?.click();
  282. break;
  283. case "name":
  284. let textInput = document.querySelector('[data-test="challenge-text-input"]');
  285. if(textInput) {
  286. if(window.sol.correctSolutions) {
  287. let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  288. nativeInputValueSetter.call(textInput, window.sol.correctSolutions[0]);
  289. let inputEvent = new Event('input', {
  290. bubbles: true
  291. });
  292. textInput.dispatchEvent(inputEvent);
  293. }
  294. }
  295. break;
  296. case "partialReverseTranslate":
  297. let elm = document.querySelector('[data-test*="challenge-partialReverseTranslate"]')?.querySelector("span[contenteditable]");
  298. if(elm) {
  299. let nativeInputNodeTextSetter = Object.getOwnPropertyDescriptor(Node.prototype, "textContent").set;
  300. nativeInputNodeTextSetter.call(elm, window.sol?.displayTokens?.filter(t => t.isBlank)?.map(t => t.text)?.join()?.replaceAll(',', '') );
  301. let inputEvent = new Event('input', {
  302. bubbles: true
  303. });
  304. elm.dispatchEvent(inputEvent);
  305. }
  306. break;
  307. default:
  308. null;
  309. }
  310.  
  311. nextButton.click();
  312. resetTimerAutoMode();
  313. }
  314.  
  315. function translateTapReverseTapSolve() {
  316. const all_tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
  317. const correct_tokens = window.sol.correctTokens;
  318. const clicked_tokens = [];
  319. correct_tokens.forEach(correct_token => {
  320. const matching_elements = Array.from(all_tokens).filter(element => element.textContent.trim() === correct_token.trim());
  321. if (matching_elements.length > 0) {
  322. const match_index = clicked_tokens.filter(token => token.textContent.trim() === correct_token.trim()).length;
  323. if (match_index < matching_elements.length) {
  324. matching_elements[match_index].click();
  325. clicked_tokens.push(matching_elements[match_index]);
  326. } else {
  327. clicked_tokens.push(matching_elements[0]);
  328. }
  329. }
  330. });
  331. }
  332.  
  333. function resetTimerAutoMode()
  334. {
  335. if(isAutoMode) {
  336. clearTimeout(solveTimerId);
  337. solveTimerId = setTimeout(solveOne, solveSpeedList[solveSpeed]);
  338. }
  339. }
  340.  
  341. function findReact(dom) {
  342. let reactProps = Object.keys(dom.parentElement).find((key) => key.startsWith("__reactProps$"));
  343. let child = dom?.parentElement?.[reactProps]?.children;
  344. return child?.props?.children?._owner?.stateNode ?? child?._owner?.stateNode;
  345. }