SolverDuo

Duolingo solves practice lessons automatically or manually.

目前为 2024-11-30 提交的版本。查看 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址