duohacker

there's cheats for Duolingo? what

  1. // ==UserScript==
  2. // @name duohacker
  3. // @namespace https://www.duolingo.com
  4. // @homepageURL https://github.com/elvisoliveira/duohacker
  5. // @supportURL https://github.com/elvisoliveira/duohacker/issues
  6. // @version 1.0.11
  7. // @description there's cheats for Duolingo? what
  8. // @author elvisoliveira
  9. // @match https://www.duolingo.com/practice*
  10. // @match https://www.duolingo.com/learn*
  11. // @license MIT
  12. // @grant none
  13. // @run-at document-end
  14. // ==/UserScript==
  15.  
  16. const keys = () => {
  17. const d = (t) => `[data-test="${t}"]`;
  18. return {
  19. AUDIO_BUTTON: d('audio-button'),
  20. BLAME_INCORRECT: d('blame blame-incorrect'),
  21. CHALLENGE: '[data-test~="challenge"]',
  22. CHALLENGE_CHOICE: d('challenge-choice'),
  23. CHALLENGE_CHOICE_CARD: d('challenge-choice-card'),
  24. CHALLENGE_JUDGE_TEXT: d('challenge-judge-text'),
  25. CHALLENGE_LISTEN_SPELL: d('challenge challenge-listenSpell'),
  26. CHALLENGE_LISTEN_TAP: d('challenge-listenTap'),
  27. CHALLENGE_TAP_TOKEN: '[data-test*="challenge-tap-token"]',
  28. CHALLENGE_TAP_TOKEN_TEXT: d('challenge-tap-token-text'),
  29. CHALLENGE_TEXT_INPUT: d('challenge-text-input'),
  30. CHALLENGE_TRANSLATE_INPUT: d('challenge-translate-input'),
  31. CHALLENGE_TYPE_CLOZE: d('challenge challenge-typeCloze'),
  32. CHALLENGE_TYPE_CLOZE_TABLE: d('challenge challenge-typeClozeTable'),
  33. CHARACTER_MATCH: d('challenge challenge-characterMatch'),
  34. PLAYER_NEXT: [d('player-next'), d('story-start')].join(','),
  35. PLAYER_SKIP: d('player-skip'),
  36. STORIES_CHOICE: d('stories-choice'),
  37. STORIES_ELEMENT: d('stories-element'),
  38. STORIES_PLAYER_DONE: d('stories-player-done'),
  39. STORIES_PLAYER_NEXT: d('stories-player-continue'),
  40. STORIES_PLAYER_START: d('story-start'),
  41. TYPE_COMPLETE_TABLE: d('challenge challenge-typeCompleteTable'),
  42. WORD_BANK: d('word-bank'),
  43. PLUS_NO_THANKS: d('plus-no-thanks'),
  44. PRACTICE_HUB_AD_NO_THANKS_BUTTON: d('practice-hub-ad-no-thanks-button')
  45. };
  46. };
  47.  
  48. const TIME_OUT = 1500;
  49.  
  50. window.dynamicInput = (element, text) => {
  51. const tag = element.tagName === 'SPAN' ? 'textContent' : 'value';
  52. const input = element;
  53. const lastValue = input[tag];
  54. input[tag] = text;
  55. const event = new Event('input', { bubbles: true });
  56. event.simulated = true;
  57. const tracker = input._valueTracker;
  58. if (tracker) {
  59. tracker.setValue(lastValue);
  60. }
  61. input.dispatchEvent(event);
  62. };
  63.  
  64. window.clickEvent = new MouseEvent('click', {
  65. view: window,
  66. bubbles: true,
  67. cancelable: true,
  68. });
  69.  
  70. // https://stackoverflow.com/a/39165137
  71. // https://github.com/Venryx/mobx-devtools-advanced/blob/master/Docs/TreeTraversal.md
  72. window.getReactFiber = (dom) => {
  73. const key = Object.keys(dom).find((key) => {
  74. return (
  75. key.startsWith('__reactFiber$') || // react 17+
  76. key.startsWith('__reactInternalInstance$') // react <17
  77. );
  78. });
  79. return dom[key];
  80. };
  81.  
  82. // Gets Challenge Object
  83. function getElementIndex(element) {
  84. let result = null;
  85. if (element instanceof Array) {
  86. for (let i = 0; i < element.length; i++) {
  87. result = getElementIndex(element[i]);
  88. if (result) break;
  89. }
  90. } else {
  91. for (let prop in element) {
  92. if (prop == 'challenge') {
  93. if (typeof element[prop] == 'object')
  94. return element;
  95. return element[prop];
  96. }
  97. if (element[prop] instanceof Object || element[prop] instanceof Array) {
  98. result = getElementIndex(element[prop]);
  99. if (result) break;
  100. }
  101. }
  102. }
  103. return result;
  104. }
  105.  
  106. function getProps(element) {
  107. let propsClass = Object.keys(element).filter((att) => /^__reactProps/g.test(att))[0];
  108. return element[propsClass];
  109. }
  110.  
  111. // Gets the Challenge
  112. function getChallenge() {
  113. const dataTestDOM = document.querySelectorAll(keys().CHALLENGE);
  114. if (dataTestDOM.length > 0) {
  115. let current = 0;
  116. for (let i = 0; i < dataTestDOM.length; i++) {
  117. if (dataTestDOM[i].childNodes.length > 0)
  118. current = i;
  119. }
  120. const currentDOM = dataTestDOM[current];
  121. const propsValues = getProps(currentDOM);
  122. const { challenge } = getElementIndex(propsValues);
  123. return challenge;
  124. }
  125. }
  126.  
  127. // Solves the Challenge
  128. function classify() {
  129. const challenge = getChallenge();
  130. if (!challenge) return;
  131. window.actions[challenge.type](challenge);
  132. }
  133.  
  134. function pressEnter() {
  135. const clickEvent = new MouseEvent('click', {
  136. view: window,
  137. bubbles: true,
  138. cancelable: false,
  139. });
  140.  
  141. // Stops when an answer is incorrect
  142. const isIncorrect = document.querySelectorAll(keys().BLAME_INCORRECT).length > 0;
  143. if (isIncorrect) {
  144. terminal.log('Incorrect, stopped');
  145. clearInterval(mainInterval);
  146. }
  147.  
  148. const isPlayerNext = document.querySelector(keys().PLAYER_NEXT);
  149. if (isPlayerNext !== null)
  150. isPlayerNext.dispatchEvent(clickEvent);
  151.  
  152. if (/learn/gi.test(window.location.href) == true)
  153. window.location.replace('https://www.duolingo.com/practice');
  154. }
  155.  
  156. // Main Function
  157. function main() {
  158. try {
  159. const isPlayerNext = document.querySelectorAll(keys().PLAYER_NEXT);
  160. const isAdScreen = document.querySelector([keys().PLUS_NO_THANKS, keys().PRACTICE_HUB_AD_NO_THANKS_BUTTON].join(','));
  161. if (isPlayerNext !== null && isPlayerNext.length > 0) {
  162. if (isPlayerNext[0].getAttribute('aria-disabled') === 'true')
  163. classify();
  164. } else if (isAdScreen !== null && isAdScreen.length > 0) {
  165. isAdScreen.click();
  166. }
  167. setTimeout(pressEnter, 150); // pressEnter();
  168. } catch (e) {
  169. // terminal.log(e);
  170. }
  171. }
  172.  
  173. // To not mess duolingo's own log
  174. function setConsole() {
  175. const iframe = document.createElement('iframe');
  176. iframe.id = 'logger';
  177. iframe.style.display = 'none';
  178. document.body.appendChild(iframe);
  179. window.terminal = iframe.contentWindow.console;
  180. }
  181.  
  182. // Calls main()
  183. let mainInterval;
  184. function solveChallenge() {
  185. if (document.getElementById('logger') == null)
  186. setConsole();
  187.  
  188. // Check if its a Skill / Alphabet / Checkpoint URL
  189. if (/lesson|practice/gi.test(window.location.href) == true) {
  190. clearInterval(mainInterval);
  191. mainInterval = setInterval(main, TIME_OUT);
  192. }
  193.  
  194. if (/learn/gi.test(window.location.href) == true)
  195. window.location.replace('https://www.duolingo.com/practice');
  196. }
  197.  
  198. (solveChallenge)();
  199.  
  200. window.keys = keys();
  201. window.actions = {};
  202.  
  203. window.actions.assist =
  204. window.actions.definition = (challenge) => {
  205. const { choices, correctIndex } = challenge;
  206. const tokens = document.querySelectorAll(window.keys.CHALLENGE_CHOICE);
  207. tokens.forEach((e, i) => {
  208. if (i == correctIndex)
  209. e.dispatchEvent(clickEvent);
  210. });
  211. return { choices, correctIndex };
  212. };
  213.  
  214. window.actions.characterMatch = (challenge) => {
  215. const { pairs } = challenge;
  216. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN);
  217. pairs.forEach((pair) => {
  218. for (let i = 0; i < tokens.length; i++) {
  219. if (
  220. tokens[i].innerText === pair.fromToken ||
  221. tokens[i].innerText === pair.learningToken
  222. ) {
  223. tokens[i].dispatchEvent(clickEvent);
  224. }
  225. }
  226. });
  227. return { pairs };
  228. };
  229.  
  230. window.actions.select =
  231. window.actions.gapFill =
  232. window.actions.readComprehension =
  233. window.actions.selectPronunciation =
  234. window.actions.listenComprehension =
  235. window.actions.characterSelect = (challenge) => {
  236. const { choices, correctIndex } = challenge;
  237. const { CHALLENGE_CHOICE } = window.keys;
  238. document.querySelectorAll(CHALLENGE_CHOICE)[correctIndex].dispatchEvent(clickEvent);
  239. return { choices, correctIndex };
  240. };
  241.  
  242. window.actions.completeReverseTranslation = (challenge) => {
  243. const { displayTokens } = challenge;
  244. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
  245. let i = 0;
  246. displayTokens.forEach((token) => {
  247. if (token.isBlank) {
  248. dynamicInput(tokens[i], token.text);
  249. i++;
  250. }
  251. });
  252. return { displayTokens };
  253. };
  254.  
  255. window.actions.characterIntro =
  256. window.actions.dialogue = (challenge) => {
  257. const { choices, correctIndex } = challenge;
  258. document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent);
  259. return { choices, correctIndex };
  260. };
  261.  
  262. window.actions.judge = (challenge) => {
  263. const { correctIndices } = challenge;
  264. document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndices[0]].dispatchEvent(clickEvent);
  265. return { correctIndices };
  266. };
  267.  
  268. window.actions.listen = (challenge) => {
  269. const { prompt } = challenge;
  270. let textInputElement = document.querySelectorAll(window.keys.CHALLENGE_TRANSLATE_INPUT)[0];
  271. dynamicInput(textInputElement, prompt);
  272. return { prompt };
  273. };
  274.  
  275. window.actions.listenComplete = (challenge) => {
  276. const { displayTokens } = challenge;
  277. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
  278. let i = 0;
  279. displayTokens.forEach((token) => {
  280. if (token.isBlank)
  281. dynamicInput(tokens[i], token.text);
  282. });
  283. return { displayTokens };
  284. };
  285.  
  286. window.actions.listenIsolation = (challenge) => {
  287. const { correctIndex } = challenge;
  288. const tokens = document.querySelectorAll(window.keys.CHALLENGE_CHOICE);
  289. tokens.forEach((e, i) => {
  290. if (i == correctIndex) {
  291. e.dispatchEvent(clickEvent);
  292. }
  293. });
  294. return { correctIndex };
  295. };
  296.  
  297. window.actions.listenMatch = (challenge) => {
  298. const { pairs } = challenge;
  299. const tokens = document.querySelectorAll('button'.concat(window.keys.CHALLENGE_TAP_TOKEN));
  300. for (let i = 0; i <= 3; i++) {
  301. const dataset = getReactFiber(tokens[i]).return.child.stateNode.dataset.test;
  302. const word = dataset.split('-')[0];
  303. tokens[i].dispatchEvent(clickEvent);
  304. for (let j = 4; j <= 7; j++) {
  305. const text = tokens[j].querySelector(window.keys.CHALLENGE_TAP_TOKEN_TEXT).innerText;
  306. if (/\s/g.test(dataset) && text.endsWith(` ${word}`)) {
  307. tokens[j].dispatchEvent(clickEvent);
  308. } else if (text == word) {
  309. tokens[j].dispatchEvent(clickEvent);
  310. }
  311. }
  312. }
  313. return { pairs }
  314. };
  315.  
  316. window.actions.listenSpell = (challenge) => {
  317. const { displayTokens } = challenge;
  318. const tokens = document.querySelectorAll(window.keys.CHALLENGE_LISTEN_SPELL.concat(' input[type="text"]:not([readonly])'));
  319. let i = 0;
  320. displayTokens.forEach((word) => {
  321. if (!isNaN(word.damageStart)) {
  322. for (let c of word.text.substring(word.damageStart, word.damageEnd ?? word.text.length)) {
  323. dynamicInput(tokens[i], c);
  324. i++;
  325. }
  326. }
  327. });
  328. return { displayTokens };
  329. };
  330.  
  331. window.actions.listenTap = (challenge) => {
  332. const { correctTokens } = challenge;
  333. const tokens = Array.from(document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN)).filter(e => e.tagName === 'BUTTON');
  334. for (let word of correctTokens) {
  335. for (let i of Object.keys(tokens)) {
  336. if (tokens[i].innerText === word) {
  337. tokens[i].dispatchEvent(clickEvent);
  338. tokens.splice(i, 1);
  339. break;
  340. }
  341. }
  342. }
  343. return { correctTokens };
  344. };
  345.  
  346. window.actions.match = (challenge) => {
  347. const { pairs } = challenge;
  348. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
  349. pairs.forEach((pair) => {
  350. for (let i = 0; i < tokens.length; i++) {
  351. if (
  352. tokens[i].innerText === pair.fromToken ||
  353. tokens[i].innerText === pair.learningToken
  354. ) {
  355. tokens[i].dispatchEvent(clickEvent);
  356. }
  357. }
  358. });
  359. return { pairs };
  360. };
  361.  
  362. window.actions.name = (challenge) => {
  363. const { correctSolutions, articles, grader } = challenge;
  364. let tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
  365. if (articles) {
  366. correctSolutions.forEach((solution) => {
  367. solution = solution.split(' ');
  368. solution.forEach((word) => {
  369. let i = articles.indexOf(word);
  370. if (i > -1) {
  371. document.querySelectorAll(window.keys.CHALLENGE_CHOICE)[i].dispatchEvent(clickEvent);
  372. solution.splice(solution.indexOf(word), 1);
  373. dynamicInput(tokens[0], solution.join(' '));
  374. }
  375. });
  376. });
  377. } else {
  378. correctSolutions.forEach((solution) => {
  379. dynamicInput(tokens[0], solution);
  380. });
  381. }
  382. return { correctSolutions, articles, grader };
  383. };
  384.  
  385. window.actions.partialReverseTranslate = (challenge) => {
  386. const { displayTokens, grader } = challenge;
  387. let tokens = document.querySelectorAll('[contenteditable=true]');
  388. let value = '';
  389. displayTokens.forEach((token) => {
  390. if (token.isBlank)
  391. value = value + token.text;
  392. });
  393. dynamicInput(tokens[0], value);
  394. return { displayTokens, grader };
  395. };
  396.  
  397. window.actions.speak = (challenge) => {
  398. const { prompt } = challenge;
  399. document.querySelectorAll(window.keys.PLAYER_SKIP)[0].dispatchEvent(clickEvent);
  400. return { prompt };
  401. };
  402.  
  403. window.actions.selectTranscription = (challenge) => {
  404. const { choices, correctIndex } = challenge;
  405. document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent);
  406. return { choices, correctIndex };
  407. };
  408.  
  409. window.actions.tapCloze = (challenge) => {
  410. const { choices, correctIndices } = challenge;
  411. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN);
  412. for (let i = 0; i < correctIndices.length; i++) {
  413. choices.forEach((value, j) => {
  414. if (correctIndices[i] == j) {
  415. for (let k = 0; k < tokens.length; k++) {
  416. if (tokens[k].innerText == value) {
  417. tokens[k].dispatchEvent(clickEvent);
  418. }
  419. }
  420. }
  421. });
  422. }
  423. return { choices, correctIndices };
  424. };
  425.  
  426. window.actions.tapClozeTable = (challenge) => {
  427. const { displayTokens } = challenge;
  428. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
  429. displayTokens.forEach((line) => {
  430. line.forEach((column) => {
  431. column.forEach((word) => {
  432. if (word.damageStart) {
  433. tokens.forEach((token) => {
  434. if (token.innerText == word.text.substring(word.damageStart, word.text.length)) {
  435. token.dispatchEvent(clickEvent);
  436. }
  437. });
  438. }
  439. });
  440. });
  441. });
  442. return { displayTokens };
  443. };
  444.  
  445. window.actions.tapComplete = (challenge) => {
  446. const { choices, correctIndices } = challenge;
  447. const tokens = document.querySelectorAll(window.keys.WORD_BANK.concat(' ', window.keys.CHALLENGE_TAP_TOKEN_TEXT));
  448. correctIndices.forEach((i) => {
  449. tokens[i].dispatchEvent(clickEvent);
  450. });
  451. return { choices, correctIndices };
  452. };
  453.  
  454. window.actions.tapCompleteTable = (challenge) => {
  455. const { choices, displayTokens } = challenge;
  456. const tokens = document.querySelectorAll(window.keys.WORD_BANK.concat(' ', window.keys.CHALLENGE_TAP_TOKEN));
  457. displayTokens.forEach((line) => {
  458. line.forEach((column) => {
  459. if (column[0].isBlank == true) {
  460. tokens.forEach((e) => {
  461. if (e.innerText == column[0].text) {
  462. e.dispatchEvent(clickEvent);
  463. }
  464. });
  465. }
  466. });
  467. });
  468. return { choices, displayTokens };
  469. };
  470.  
  471. window.actions.translate = (challenge) => {
  472. const { correctTokens, correctSolutions } = challenge;
  473. if (correctTokens) {
  474. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
  475. let ignoreTokeIndexes = [];
  476. for (let correctTokenIndex in correctTokens) {
  477. for (let tokenIndex in tokens) {
  478. const token = tokens[tokenIndex];
  479. if (ignoreTokeIndexes.includes(tokenIndex)) continue;
  480. if (token.innerText === correctTokens[correctTokenIndex]) {
  481. token.dispatchEvent(clickEvent);
  482. ignoreTokeIndexes.push(tokenIndex);
  483. break;
  484. }
  485. }
  486. }
  487. } else if (correctSolutions) {
  488. let textInputElement = document.querySelectorAll(
  489. window.keys.CHALLENGE_TRANSLATE_INPUT
  490. )[0];
  491. dynamicInput(textInputElement, correctSolutions[0]);
  492. }
  493. return { correctTokens };
  494. };
  495.  
  496. window.actions.typeCloze = (challenge) => {
  497. const { displayTokens } = challenge;
  498. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TYPE_CLOZE.concat(' input'));
  499. let i = 0;
  500. displayTokens.forEach((word) => {
  501. if (word.damageStart) {
  502. dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
  503. i++;
  504. }
  505. });
  506. return { displayTokens };
  507. };
  508.  
  509. window.actions.typeClozeTable = (challenge) => {
  510. const { displayTokens } = challenge;
  511. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TYPE_CLOZE_TABLE.concat(' input'));
  512. let i = 0;
  513. displayTokens.forEach((line) => {
  514. line.forEach((column) => {
  515. column.forEach((word) => {
  516. if (word.damageStart) {
  517. dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
  518. i++;
  519. }
  520. });
  521. });
  522. });
  523. return { displayTokens };
  524. };
  525.  
  526. window.actions.typeCompleteTable = (challenge) => {
  527. const { displayTokens } = challenge;
  528. const tokens = document.querySelectorAll(window.keys.TYPE_COMPLETE_TABLE.concat(' input'));
  529. let index = 0;
  530. displayTokens.forEach((line) => {
  531. line.forEach((column) => {
  532. if (column[0].isBlank == true) {
  533. dynamicInput(tokens[index], column[0].text);
  534. index++;
  535. }
  536. });
  537. });
  538. return { displayTokens };
  539. };

QingJ © 2025

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