SolverDuo

It solves practice lessons automatically or manually, to increase xp use DuoSolverGrinder.

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

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