DuoHack

Duolingo Auto-Solver cheat [WORKING DECEMBER 2023]

  1. // ==UserScript==
  2. // @name DuoHack
  3. // @namespace Tampermonkey Scripts
  4. // @match https://*.duolingo.com/*
  5. // @grant none
  6. // @version 0,0000000000001
  7. // @author Ngô Đặng Bảo Châu
  8. // @description Duolingo Auto-Solver cheat [WORKING DECEMBER 2023]
  9. // Feel free to donate something at: https://www.patreon.com/MikailCayoglu/membership
  10. // Published on Greasyfork: https://greasyfork.org/de/scripts/463285
  11. // ==/UserScript==
  12.  
  13. let solvingIntervalId;
  14. let isAutoMode = false;
  15. const debug = false;
  16.  
  17. function addButtons() {
  18. if (window.location.pathname === '/learn') {
  19. let button = document.querySelector('a[data-test="global-practice"]');
  20. if (button) {
  21. return;
  22. }
  23. }
  24.  
  25. const solveAllButton = document.getElementById("solveAllButton");
  26. if (solveAllButton !== null) {
  27. return;
  28. }
  29.  
  30. const original = document.querySelectorAll('[data-test="player-next"]')[0];
  31. if (original === undefined) {
  32. const startButton = document.querySelector('[data-test="start-button"]');
  33. console.log(`Wrapper line: ${startButton}`);
  34. if (startButton === null) {
  35. return;
  36. }
  37. const wrapper = startButton.parentNode;
  38. const solveAllButton = document.createElement('a');
  39. solveAllButton.className = startButton.className;
  40. solveAllButton.id = "solveAllButton";
  41. solveAllButton.innerText = "COMPLETE SKILL";
  42. solveAllButton.removeAttribute('href');
  43. solveAllButton.addEventListener('click', () => {
  44. solving();
  45. setInterval(() => {
  46. const startButton = document.querySelector('[data-test="start-button"]');
  47. if (startButton && startButton.innerText.startsWith("START")) {
  48. startButton.click();
  49. }
  50. }, 3000);
  51. startButton.click();
  52. });
  53. wrapper.appendChild(solveAllButton);
  54. } else {
  55. const wrapper = document.getElementsByClassName('_10vOG')[0];
  56. wrapper.style.display = "flex";
  57.  
  58. const solveCopy = document.createElement('button');
  59. const pauseCopy = document.createElement('button');
  60.  
  61. solveCopy.id = 'solveAllButton';
  62. solveCopy.innerHTML = solvingIntervalId ? 'PAUSE SOLVE' : 'SOLVE ALL';
  63. solveCopy.disabled = false;
  64. pauseCopy.innerHTML = 'SOLVE';
  65.  
  66. const buttonStyle = `
  67. min-width: 150px;
  68. font-size: 17px;
  69. border:none;
  70. border-bottom: 4px solid #58a700;
  71. border-radius: 18px;
  72. padding: 13px 16px;
  73. transform: translateZ(0);
  74. transition: filter .2s;
  75. font-weight: 700;
  76. letter-spacing: .8px;
  77. background: #55CD2E;
  78. color:#fff;
  79. margin-left:20px;
  80. cursor:pointer;
  81. `;
  82.  
  83. solveCopy.style.cssText = buttonStyle;
  84. pauseCopy.style.cssText = buttonStyle;
  85.  
  86. [solveCopy, pauseCopy].forEach(button => {
  87. button.addEventListener("mousemove", () => {
  88. button.style.filter = "brightness(1.1)";
  89. });
  90. });
  91.  
  92. [solveCopy, pauseCopy].forEach(button => {
  93. button.addEventListener("mouseleave", () => {
  94. button.style.filter = "none";
  95. });
  96. });
  97.  
  98. original.parentElement.appendChild(pauseCopy);
  99. original.parentElement.appendChild(solveCopy);
  100.  
  101. solveCopy.addEventListener('click', solving);
  102. pauseCopy.addEventListener('click', solve);
  103. }
  104. }
  105.  
  106.  
  107. setInterval(addButtons, 3000);
  108.  
  109. function solving() {
  110. if (solvingIntervalId) {
  111. clearInterval(solvingIntervalId);
  112. solvingIntervalId = undefined;
  113. document.getElementById("solveAllButton").innerText = "SOLVE ALL";
  114. isAutoMode = false;
  115. } else {
  116. document.getElementById("solveAllButton").innerText = "PAUSE SOLVE";
  117. isAutoMode = true;
  118. solvingIntervalId = setInterval(solve, 500);
  119. }
  120. }
  121.  
  122. function solve() {
  123. const selAgain = document.querySelectorAll('[data-test="player-practice-again"]');
  124. const practiceAgain = document.querySelector('[data-test="player-practice-again"]');
  125. if (selAgain.length === 1 && isAutoMode) {
  126. // Make sure it's the `practice again` button
  127. //if (selAgain[0].innerHTML.toLowerCase() === 'practice again') {
  128. // Click the `practice again` button
  129. selAgain[0].click();
  130. // Terminate
  131. return;
  132. //}
  133. }
  134. if (practiceAgain !== null && isAutoMode) {
  135. practiceAgain.click();
  136. return;
  137. }
  138. try {
  139. window.sol = findReact(document.getElementsByClassName('_3FiYg')[0]).props.currentChallenge;
  140. } catch {
  141. let next = document.querySelector('[data-test="player-next"]');
  142. if (next) {
  143. next.click();
  144. }
  145. return;
  146. }
  147. if (!window.sol) {
  148. return;
  149. }
  150. let nextButton = document.querySelector('[data-test="player-next"]');
  151. if (!nextButton) {
  152. return;
  153. }
  154. if (document.querySelectorAll('[data-test*="challenge-speak"]').length > 0) {
  155. if (debug)
  156. document.getElementById("solveAllButton").innerText = 'Challenge Speak';
  157. const buttonSkip = document.querySelector('button[data-test="player-skip"]');
  158. if (buttonSkip) {
  159. buttonSkip.click();
  160. }
  161. } else if (window.sol.type === 'listenMatch') {
  162. if (debug)
  163. document.getElementById("solveAllButton").innerText = 'Listen Match';
  164. const nl = document.querySelectorAll('[data-test$="challenge-tap-token"]');
  165. window.sol.pairs?.forEach((pair) => {
  166. for (let i = 0; i < nl.length; i++) {
  167. let nlInnerText;
  168. if (nl[i].querySelectorAll('[data-test="challenge-tap-token-text"]').length > 1) {
  169. nlInnerText = nl[i].querySelector('[data-test="challenge-tap-token-text"]').innerText.toLowerCase().trim();
  170. } else {
  171. nlInnerText = findSubReact(nl[i]).text.toLowerCase().trim();
  172. }
  173. if (
  174. (
  175. nlInnerText === pair.learningWord.toLowerCase().trim() ||
  176. nlInnerText === pair.translation.toLowerCase().trim()
  177. ) &&
  178. !nl[i].disabled
  179. ) {
  180. nl[i].click();
  181. }
  182. }
  183. });
  184. } else if (document.querySelectorAll('[data-test="challenge-choice"]').length > 0) {
  185. // choice challenge
  186. if (debug)
  187. document.getElementById("solveAllButton").innerText = 'Challenge Choice';
  188. if (window.sol.correctTokens !== undefined) {
  189. correctTokensRun();
  190. nextButton.click()
  191. } else if (window.sol.correctIndex !== undefined) {
  192. document.querySelectorAll('[data-test="challenge-choice"]')[window.sol.correctIndex].click();
  193. nextButton.click();
  194. }
  195. } else if (document.querySelectorAll('[data-test$="challenge-tap-token"]').length > 0) {
  196. // match correct pairs challenge
  197. if (window.sol.pairs !== undefined) {
  198. if (debug)
  199. document.getElementById("solveAllButton").innerText = 'Pairs';
  200. let nl = document.querySelectorAll('[data-test$="challenge-tap-token"]');
  201. if (document.querySelectorAll('[data-test="challenge-tap-token-text"]').length
  202. === nl.length) {
  203. window.sol.pairs?.forEach((pair) => {
  204. for (let i = 0; i < nl.length; i++) {
  205. const nlInnerText = nl[i].querySelector('[data-test="challenge-tap-token-text"]').innerText.toLowerCase().trim();
  206. try {
  207. if (
  208. (
  209. nlInnerText === pair.transliteration.toLowerCase().trim() ||
  210. nlInnerText === pair.character.toLowerCase().trim()
  211. )
  212. && !nl[i].disabled
  213. ) {
  214. nl[i].click()
  215. }
  216. } catch (TypeError) {
  217. if (
  218. (
  219. nlInnerText === pair.learningToken.toLowerCase().trim() ||
  220. nlInnerText === pair.fromToken.toLowerCase().trim()
  221. )
  222. && !nl[i].disabled
  223. ) {
  224. nl[i].click()
  225. }
  226. }
  227. }
  228. })
  229. }
  230. } else if (window.sol.correctTokens !== undefined) {
  231. if (debug)
  232. document.getElementById("solveAllButton").innerText = 'Token Run';
  233. correctTokensRun();
  234. nextButton.click()
  235. } else if (window.sol.correctIndices !== undefined) {
  236. if (debug)
  237. document.getElementById("solveAllButton").innerText = 'Indices Run';
  238. correctIndicesRun();
  239. }
  240. } else if (document.querySelectorAll('[data-test="challenge-tap-token-text"]').length > 0) {
  241. if (debug)
  242. document.getElementById("solveAllButton").innerText = 'Challenge Tap Token Text';
  243. // fill the gap challenge
  244. correctIndicesRun();
  245. } else if (document.querySelectorAll('[data-test="challenge-text-input"]').length > 0) {
  246. if (debug)
  247. document.getElementById("solveAllButton").innerText = 'Challenge Text Input';
  248. let elm = document.querySelectorAll('[data-test="challenge-text-input"]')[0];
  249. let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  250. nativeInputValueSetter.call(elm, window.sol.correctSolutions ? window.sol.correctSolutions[0] : (window.sol.displayTokens ? window.sol.displayTokens.find(t => t.isBlank).text : window.sol.prompt));
  251. let inputEvent = new Event('input', {
  252. bubbles: true
  253. });
  254.  
  255. elm.dispatchEvent(inputEvent);
  256. } else if (document.querySelectorAll('[data-test*="challenge-partialReverseTranslate"]').length > 0) {
  257. if (debug)
  258. document.getElementById("solveAllButton").innerText = 'Partial Reverse';
  259. let elm = document.querySelector('[data-test*="challenge-partialReverseTranslate"]')?.querySelector("span[contenteditable]");
  260. let nativeInputNodeTextSetter = Object.getOwnPropertyDescriptor(Node.prototype, "textContent").set
  261. nativeInputNodeTextSetter.call(elm, '"' + window.sol?.displayTokens?.filter(t => t.isBlank)?.map(t => t.text)?.join()?.replaceAll(',', '') + '"');
  262. let inputEvent = new Event('input', {
  263. bubbles: true
  264. });
  265.  
  266. elm.dispatchEvent(inputEvent);
  267. } else if (document.querySelectorAll('textarea[data-test="challenge-translate-input"]').length > 0) {
  268. if (debug)
  269. document.getElementById("solveAllButton").innerText = 'Challenge Translate Input';
  270. const elm = document.querySelector('textarea[data-test="challenge-translate-input"]');
  271. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
  272. nativeInputValueSetter.call(elm, window.sol.correctSolutions ? window.sol.correctSolutions[0] : window.sol.prompt);
  273.  
  274. let inputEvent = new Event('input', {
  275. bubbles: true
  276. });
  277.  
  278. elm.dispatchEvent(inputEvent);
  279. }
  280. nextButton.click()
  281. }
  282.  
  283. function correctTokensRun() {
  284. const all_tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
  285. const correct_tokens = window.sol.correctTokens;
  286. const clicked_tokens = [];
  287. correct_tokens.forEach(correct_token => {
  288. const matching_elements = Array.from(all_tokens).filter(element => element.textContent.trim() === correct_token.trim());
  289. if (matching_elements.length > 0) {
  290. const match_index = clicked_tokens.filter(token => token.textContent.trim() === correct_token.trim()).length;
  291. if (match_index < matching_elements.length) {
  292. matching_elements[match_index].click();
  293. clicked_tokens.push(matching_elements[match_index]);
  294. } else {
  295. clicked_tokens.push(matching_elements[0]);
  296. }
  297. }
  298. });
  299. }
  300.  
  301. function correctIndicesRun() {
  302. if (window.sol.correctIndices) {
  303. window.sol.correctIndices?.forEach(index => {
  304. document.querySelectorAll('div[data-test="word-bank"] [data-test="challenge-tap-token-text"]')[index].click();
  305. });
  306. // nextButton.click();
  307. }
  308. }
  309.  
  310. function findSubReact(dom, traverseUp = 0) {
  311. const key = Object.keys(dom).find(key => key.startsWith("__reactProps$"));
  312. return dom.parentElement[key].children.props;
  313. }
  314.  
  315. function findReact(dom, traverseUp = 0) {
  316. let reactProps = Object.keys(dom.parentElement).find((key) => key.startsWith("__reactProps$"));
  317. while (traverseUp-- > 0 && dom.parentElement) {
  318. dom = dom.parentElement;
  319. reactProps = Object.keys(dom.parentElement).find((key) => key.startsWith("__reactProps$"));
  320. }
  321. return dom?.parentElement?.[reactProps]?.children[0]?._owner?.stateNode;
  322. }
  323.  
  324.  
  325. window.findReact = findReact;
  326.  
  327. window.ss = solving;