Greasy Fork 还支持 简体中文。

PWNEDuo (based on DuoHacker).

Duolingo automation auto answer script. Include skill leveling up (12.01.2023 update)

目前為 2023-01-11 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name PWNEDuo (based on DuoHacker).
  3. // @description Duolingo automation auto answer script. Include skill leveling up (12.01.2023 update)
  4. // @namespace Violentmonkey Scripts
  5. // @match https://*.duolingo.com/*
  6. // @grant none
  7. // @version 1.0.2
  8. // @author hprnv
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. var intervalId;
  13. var isAutoMode = false;
  14. function addButtons() {
  15. if(window.location.pathname == '/learn' && isAutoMode){
  16. let button = document.querySelector('a[data-test="global-practice"]');
  17. if(button){
  18. button.click();
  19. return;
  20. }
  21. }
  22. if (document.getElementById("solveAllButton") !== null) {
  23. return;
  24. }
  25.  
  26. let original = document.querySelectorAll('[data-test="player-next"]')[0];
  27. let wrapper = document.getElementsByClassName('_10vOG')[0];
  28. if (original == undefined) {
  29. let startButton = document.querySelector('[data-test="start-button"]');
  30. if (startButton == undefined) {
  31. return;
  32. }
  33. let wrapper = startButton.parentNode;
  34. let autoComplete = document.createElement('a');
  35. autoComplete.className = startButton.className;
  36. autoComplete.id = "solveAllButton";
  37. autoComplete.innerText = "COMPLETE SKILL";
  38. autoComplete.removeAttribute('href');
  39. autoComplete.onclick = function () {
  40. startSolving();
  41. setInterval(function () {
  42. let startButton = document.querySelector('[data-test="start-button"]');
  43. if (startButton && startButton.innerText.startsWith("START")) {
  44. startButton.click();
  45. }
  46. }, 3000);
  47. startButton.click();
  48. };
  49. wrapper.appendChild(autoComplete);
  50. } else {
  51.  
  52. wrapper.style.display = "flex";
  53.  
  54. let solveCopy = document.createElement('button');
  55. let pauseCopy = document.createElement('button');
  56.  
  57. solveCopy.id = 'solveAllButton';
  58. if (intervalId) {
  59. solveCopy.innerHTML = 'PAUSE SOLVE';
  60. } else {
  61. solveCopy.innerHTML = 'SOLVE ALL';
  62. }
  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. //Hover effect for buttons
  87.  
  88. function mouseOver(x) {
  89. x.style.filter = "brightness(1.1)";
  90. }
  91.  
  92. function mouseLeave(x) {
  93. x.style.filter = "none";
  94. }
  95.  
  96. let buttons = [solveCopy, pauseCopy]
  97.  
  98. buttons.forEach(button => {
  99. button.addEventListener("mousemove", () => {
  100. mouseOver(button);
  101. });
  102. });
  103.  
  104. buttons.forEach(button => {
  105. button.addEventListener("mouseleave", () => {
  106. mouseLeave(button);
  107. });
  108. });
  109.  
  110.  
  111.  
  112. original.parentElement.appendChild(pauseCopy);
  113. original.parentElement.appendChild(solveCopy);
  114.  
  115.  
  116. solveCopy.addEventListener('click', solving);
  117. pauseCopy.addEventListener('click', solve);
  118. }
  119. }
  120.  
  121. setInterval(addButtons, 3000);
  122.  
  123. function solving() {
  124. if (intervalId) {
  125. pauseSolving();
  126. } else {
  127. startSolving();
  128. }
  129. }
  130.  
  131. function startSolving() {
  132. if (intervalId) {
  133. return;
  134. }
  135. document.getElementById("solveAllButton").innerText = "PAUSE SOLVE";
  136. isAutoMode = true;
  137. intervalId = setInterval(solve, 500);
  138. }
  139.  
  140. function pauseSolving() {
  141. if (!intervalId) {
  142. return;
  143. }
  144. document.getElementById("solveAllButton").innerText = "SOLVE ALL";
  145. isAutoMode = false;
  146. clearInterval(intervalId);
  147. intervalId = undefined;
  148. }
  149.  
  150. function solve() {
  151. try {
  152. window.sol = FindReact(document.getElementsByClassName('_3FiYg')[0]).props.currentChallenge;
  153. } catch {
  154. let next = document.querySelector('[data-test="player-next"]');
  155. if (next) {
  156. next.click();
  157. }
  158. return;
  159. }
  160. if (!window.sol) {
  161. return;
  162. }
  163. let btn = null;
  164.  
  165. let selNext = document.querySelectorAll('[data-test="player-next"]');
  166. let selAgain = document.getElementsByClassName('_3_pD1 _2ESN4 _2arQ0 _2vmUZ _2Zh2S _1X3l0 eJd0I _3yrdh _2wXoR _1AM95 _1dlWz _2gnHr _2L5kw _3Ry1f')
  167.  
  168. if (selAgain.length === 1) {
  169. // Make sure it's the `practice again` button
  170. if (selAgain[0].innerHTML.toLowerCase() === 'practice again') {
  171. // Click the `practice again` button
  172. selAgain[0].click();
  173.  
  174. // Terminate
  175. return;
  176. }
  177. }
  178.  
  179. if (selNext.length === 1) {
  180. // Save the button element
  181. btn = selNext[0];
  182. if(document.querySelectorAll('[data-test*="challenge-speak"]').length > 0){
  183. let buttonSkip = document.quesrySelector('button[data-test="player-skip"]');
  184. if(buttonSkip){
  185. buttonSkip.click();
  186. }
  187. }
  188.  
  189. if (document.querySelectorAll('[data-test="challenge-choice"]').length > 0) {
  190. if (window.sol.correctIndices) {
  191. window.sol.correctIndices?.forEach(index => {
  192. document.querySelectorAll('[data-test="challenge-choice"]')[index].children[0].click();
  193. });
  194. // Click the first element
  195. } else if (window.sol.articles) {
  196. var article = '';
  197. for (var i = 0; i < window.sol.articles.length; i++) {
  198. if (window.sol.correctSolutions[0].startsWith(window.sol.articles[i])) {
  199. Array.from(document.querySelectorAll('[data-test="challenge-choice"]'))
  200. .find((elm) =>
  201. elm.querySelector('[data-test="challenge-judge-text"]').innerText == window.sol.articles[i]
  202. ).click();
  203. article = window.sol.articles[i];
  204. break;
  205. }
  206. }
  207. let elm = document.querySelectorAll('[data-test="challenge-text-input"]')[0];
  208. let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  209. nativeInputValueSetter.call(elm, window.sol.correctSolutions ? window.sol.correctSolutions[0].replace(article + ' ', '') : (window.sol.displayTokens ? window.sol.displayTokens.find(t => t.isBlank).text : window.sol.prompt));
  210. let inputEvent = new Event('input', {
  211. bubbles: true
  212. });
  213.  
  214. elm.dispatchEvent(inputEvent);
  215. } else {
  216. document.querySelectorAll('[data-test="challenge-choice"]')[window.sol.correctIndex].click();
  217. }
  218. // Click the solve button
  219. btn.click();
  220. }
  221.  
  222. if (document.querySelectorAll('[data-test="challenge-choice-card"]').length > 0) {
  223. // Click the first element
  224. if (window.sol.correctIndices) {
  225. window.sol.correctIndices?.forEach(index => {
  226. document.querySelectorAll('[data-test="challenge-choice-card"]')[index].children[0].click();
  227. });
  228. } else {
  229. document.querySelectorAll('[data-test="challenge-choice-card"]')[window.sol.correctIndex].click();
  230. }
  231. // Click the solve button
  232. btn.click();
  233. }
  234.  
  235. if (window.sol.type == 'listenMatch') {
  236. let nl = document.querySelectorAll('[data-test="challenge-tap-token"]');
  237. window.sol.pairs?.forEach((pair) => {
  238. for (let i = 0; i < nl.length; i++) {
  239. let nlInnerText;
  240. if (nl[i].querySelectorAll('[data-test="challenge-tap-token-text"]').length > 1) {
  241. nlInnerText = nl[i].querySelector('[data-test="challenge-tap-token-text"]').innerText.toLowerCase().trim();
  242. } else {
  243. nlInnerText = FindSubReact(nl[i]).text.toLowerCase().trim();
  244. }
  245. if (
  246. (
  247. nlInnerText == pair.learningWord.toLowerCase().trim() ||
  248. nlInnerText == pair.translation.toLowerCase().trim()
  249. ) &&
  250. !nl[i].disabled
  251. ) {
  252. nl[i].click();
  253. }
  254. }
  255. });
  256. }
  257.  
  258. if (window.sol.type == 'listenSpell') {
  259. let tokens = window.sol.displayTokens.filter(x => x.damageStart !== undefined);
  260. let elms = document.querySelectorAll('._2cjP3._2IKiF');
  261. let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  262.  
  263. var solutionCharacters = [];
  264. for (let tok of tokens) {
  265. for (let i = tok.damageStart; i < tok.damageEnd; i++) {
  266. solutionCharacters.push(tok.text[i]);
  267. }
  268. }
  269.  
  270. for (var elmIndex = 0; elmIndex < elms.length; elmIndex++) {
  271. nativeInputValueSetter.call(elms[elmIndex], solutionCharacters[elmIndex]);
  272.  
  273. let inputEvent = new Event('input', {
  274. bubbles: true
  275. });
  276.  
  277. elms[elmIndex].dispatchEvent(inputEvent);
  278. }
  279. }
  280.  
  281. if (document.querySelectorAll('[data-test="challenge-tap-token"]').length > 0) {
  282. // Click the first element
  283. if (window.sol.pairs) {
  284. let nl = document.querySelectorAll('[data-test="challenge-tap-token"]');
  285. if (document.querySelectorAll('[data-test="challenge-tap-token-text"]').length == document.querySelectorAll('[data-test="challenge-tap-token"]').length) {
  286. window.sol.pairs?.forEach((pair) => {
  287. for (let i = 0; i < nl.length; i++) {
  288. const nlInnerText = nl[i].querySelector('[data-test="challenge-tap-token-text"]').innerText.toLowerCase().trim();
  289. if (
  290. (
  291. nlInnerText == pair.learningToken.toLowerCase().trim() ||
  292. nlInnerText == pair.fromToken.toLowerCase().trim()
  293. ) &&
  294. !nl[i].disabled
  295. ) {
  296. nl[i].click();
  297. }
  298. }
  299. });
  300. }
  301. } else if(!window.sol.correctTokens){
  302. let clicked = {}
  303. let nl = document.querySelectorAll('[data-test="challenge-tap-token"]');
  304. window.sol.correctIndices?.forEach(index => {
  305. let correctAnswer = window.sol.choices[index];
  306. for (let i = 0; i < nl.length; i++) {
  307. if ((nl[i].innerText).toLowerCase().trim() == correctAnswer.text.toLowerCase().trim() && !nl[i].disabled && !clicked[i]) {
  308. clicked[i] = 1;
  309. nl[i].click();
  310. break;
  311. }
  312. }
  313. });
  314. } else {
  315. let clicked = {}
  316. let nl = document.querySelectorAll('[data-test="challenge-tap-token"]');
  317. window.sol.correctIndices?.forEach(index => {
  318. let correctAnswer = window.sol.correctTokens[index];
  319. for (let i = 0; i < nl.length; i++) {
  320. if ((nl[i].innerText).toLowerCase().trim() == correctAnswer.toLowerCase().trim() && !nl[i].disabled && !clicked[i]) {
  321. clicked[i] = 1;
  322. nl[i].click();
  323. break;
  324. }
  325. }
  326. });
  327. }
  328. // Click the solve button
  329. btn.click();
  330. }
  331.  
  332. if (document.querySelectorAll('[data-test="challenge-text-input"]').length > 0) {
  333.  
  334. let elm = document.querySelectorAll('[data-test="challenge-text-input"]')[0];
  335. let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  336. nativeInputValueSetter.call(elm, window.sol.correctSolutions ? window.sol.correctSolutions[0] : (window.sol.displayTokens ? window.sol.displayTokens.find(t => t.isBlank).text : window.sol.prompt));
  337. let inputEvent = new Event('input', {
  338. bubbles: true
  339. });
  340.  
  341. elm.dispatchEvent(inputEvent);
  342. }
  343.  
  344.  
  345. if (document.querySelectorAll('[data-test*="challenge-partialReverseTranslate"]').length > 0) {
  346. let elm = document.querySelector('[data-test*="challenge-partialReverseTranslate"]')?.querySelector("span[contenteditable]");
  347. let nativeInputNodeTextSetter = Object.getOwnPropertyDescriptor(Node.prototype, "textContent").set
  348. nativeInputNodeTextSetter.call(elm, '"' + window.sol.displayTokens.filter(t => t.isBlank).map(t=>t.text).join().replaceAll(',', '') + '"');
  349. let inputEvent = new Event('input', {
  350. bubbles: true
  351. });
  352.  
  353. elm.dispatchEvent(inputEvent);
  354. }
  355.  
  356. if (document.getElementsByTagName('textarea').length > 0) {
  357. let elm = document.getElementsByTagName('textarea')[0]
  358.  
  359. let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
  360. nativeInputValueSetter.call(elm, window.sol.correctSolutions ? window.sol.correctSolutions[0] : window.sol.prompt);
  361.  
  362. let inputEvent = new Event('input', {
  363. bubbles: true
  364. });
  365.  
  366. elm.dispatchEvent(inputEvent);
  367. }
  368.  
  369. // Continue
  370. btn.click();
  371. }
  372. }
  373.  
  374. function FindSubReact(dom, traverseUp = 0) {
  375. const key = Object.keys(dom).find(key => key.startsWith("__reactProps$"));
  376. return dom.parentElement[key].children.props;
  377. }
  378.  
  379. function FindReact(dom, traverseUp = 0) {
  380. const key = Object.keys(dom.parentElement).find(key => key.startsWith("__reactProps$"));
  381. return dom.parentElement[key].children[0]._owner.stateNode;
  382. }
  383.  
  384. window.findReact = FindReact;
  385.  
  386. window.ss = startSolving;