DuoFarmer

[ LITE ] DuoFarmer 是一款可帮助您以惊人的速度在 Duolingo 中赚取 XP 的工具。

安装此脚本
作者推荐脚本

您可能也喜欢Get Duolingo JWT

安装此脚本
  1. // ==UserScript==
  2. // @name DuoFarmer
  3. // @namespace https://duo-farmer.vercel.app
  4. // @version 1.2
  5. // @description [ LITE ] DuoFarmer is a tool that helps you earn XP in Duolingo at blazing speed.
  6. // @description:vi [ LITE ] DuoFarmer là một công cụ giúp bạn hack XP trong Duolingo với tốc độ cực nhanh.
  7. // @description:fr [ LITE ] DuoFarmer est un outil qui vous aide à gagner de l'XP sur Duolingo à une vitesse fulgurante.
  8. // @description:es [ LITE ] DuoFarmer es una herramienta que te ayuda a ganar XP en Duolingo a una velocidad asombrosa.
  9. // @description:de [ LITE ] DuoFarmer ist ein Tool, das Ihnen hilft, in Duolingo blitzschnell XP zu verdienen.
  10. // @description:it [ LITE ] DuoFarmer è uno strumento che ti aiuta a guadagnare XP su Duolingo a una velocità incredibile.
  11. // @description:ja [ LITE ] DuoFarmer は、Duolingo で驚異的なスピードで XP を獲得するのに役立つツールです。
  12. // @description:ko [ LITE ] DuoFarmer는 Duolingo에서 엄청난 속도로 XP를 얻을 수 있도록 도와주는 도구입니다.
  13. // @description:ru [ LITE ] DuoFarmer - это инструмент, который помогает вам зарабатывать XP в Duolingo с невероятной скоростью.
  14. // @description:zh-CN [ LITE ] DuoFarmer 是一款可帮助您以惊人的速度在 Duolingo 中赚取 XP 的工具。
  15. // @author Lamduck
  16. // @match https://*.duolingo.com/*
  17. // @icon https://www.google.com/s2/favicons?sz=64&domain=duolingo.com
  18. // @grant none
  19. // @license none
  20. // ==/UserScript==
  21.  
  22. const VERSION = "1.2";
  23. const DELAY = 500;
  24. var jwt, defaultHeaders, userInfo, sub;
  25. let isRunning = false;
  26.  
  27. const initInterface = () => {
  28. const containerHTML = `
  29. <link rel="stylesheet" href="xp.css" />
  30. <div id="_overlay"></div>
  31. <div id="_container">
  32. <div id="_header">
  33. <span class="_label">Duofarmer minimal UI</span>
  34. </div>
  35. <div id="_body">
  36. <table id="_table_main" class="_table">
  37. <thead>
  38. <tr>
  39. <th>Username</th>
  40. <th>From</th>
  41. <th>Learning</th>
  42. </tr>
  43. </thead>
  44. <tbody>
  45. <tr>
  46. <td id="_username">duofarmer</td>
  47. <td id="_from">any</td>
  48. <td id="_learn">any</td>
  49. </tr>
  50. </tbody>
  51. </table>
  52. <table id="_table_progress" class="_table">
  53. <thead>
  54. <tr>
  55. <th>Streak</th>
  56. <th>Gem</th>
  57. <th>XP</th>
  58. </tr>
  59. </thead>
  60. <tbody>
  61. <tr>
  62. <td id="_streak">0</td>
  63. <td id="_gem">0</td>
  64. <td id="_xp">0</td>
  65. </tr>
  66. </tbody>
  67. </table>
  68. <div id="_action_row">
  69. <select id="_select_option">
  70. <!-- <option value="option1">Option 1</option> -->
  71. <!-- <option value="option2">Option 2</option> -->
  72. </select>
  73. <button id="_start_btn">Start</button>
  74. <button id="_stop_btn" hidden>Stop</button>
  75. </div>
  76. <div id="_notify">
  77. This is a minimal version that reduces device resources. <br />
  78. Only support learning language is English. <br />
  79. (will not work if from language is also English) <br>
  80. <br>
  81. Recommended to go to blank page for max performance + reduce resource usage.
  82. </div>
  83. <a id="_blank_page_link" href="https://www.duolingo.com/errors/404.html">Blank page (click here)</a>
  84. </div>
  85. <div id="_footer">
  86. <a href="https://gf.qytechs.cn/vi/scripts/528621-duofarmer" target="_blank"
  87. >Greasyfork</a
  88. >
  89. <a href="https://t.me/duofarmer" target="_blank">Telegram</a>
  90. <a>Version <span id="_version">1.0</span></a>
  91. </div>
  92. </div>
  93. <div id="_floating_btn">😼</div>
  94. `;
  95.  
  96. const style = document.createElement("style");
  97. style.innerHTML = `#_container {
  98. width: 90vw;
  99. max-width: 800px;
  100. min-height: 40vh;
  101. max-height: 90vh;
  102. background: #222;
  103. color: #fff;
  104. border-radius: 10px;
  105. box-shadow: 0 2px 12px #0008;
  106. font-family: sans-serif;
  107. display: flex;
  108. flex-direction: column;
  109. align-items: center;
  110. justify-content: center;
  111. position: fixed;
  112. top: 50%;
  113. left: 50%;
  114. transform: translate(-50%, -50%);
  115. z-index: 9999;
  116. }
  117.  
  118. #_header {
  119. height: 60px;
  120. background: #333;
  121. display: flex;
  122. align-items: center;
  123. justify-content: center;
  124. border-top-left-radius: 10px;
  125. border-top-right-radius: 10px;
  126. width: 100%;
  127. }
  128.  
  129. #_body {
  130. min-height: 40vh;
  131. max-height: 100%;
  132. min-width: 0;
  133. background: #282828;
  134. display: flex;
  135. align-items: center;
  136. justify-content: center;
  137. width: 100%;
  138. overflow-y: auto;
  139. flex: 1;
  140. flex-direction: column;
  141. }
  142.  
  143. #_footer {
  144. height: 50px;
  145. background: #222;
  146. display: flex;
  147. align-items: center;
  148. justify-content: space-evenly;
  149. border-bottom-left-radius: 10px;
  150. border-bottom-right-radius: 10px;
  151. width: 100%;
  152. }
  153.  
  154. ._label {
  155. font-size: 1em;
  156. }
  157.  
  158. #_header ._label {
  159. font-size: 1.5em;
  160. font-style: italic;
  161. font-weight: bold;
  162. color: #fac8ff;
  163. }
  164.  
  165. #_body ._label {
  166. font-size: 1.2em;
  167. }
  168.  
  169. ._table {
  170. width: 100%;
  171. background: #232323;
  172. color: #fff;
  173. border-radius: 8px;
  174. padding: 8px 12px;
  175. text-align: center;
  176. table-layout: fixed;
  177. }
  178.  
  179. ._table th,
  180. ._table td {
  181. padding: 9px 12px;
  182. text-align: center;
  183. border-bottom: 1px solid #444;
  184. width: 1%;
  185. }
  186.  
  187. ._table tbody tr:last-child td {
  188. border-bottom: none;
  189. }
  190.  
  191. #_body h3 {
  192. margin: 0;
  193. color: #fff;
  194. font-size: 1.1em;
  195. font-weight: bold;
  196. letter-spacing: 1px;
  197. }
  198.  
  199. #_action_row {
  200. width: 90%;
  201. display: flex;
  202. justify-content: space-between;
  203. align-items: center;
  204. margin: 8px 0;
  205. gap: 8px;
  206. }
  207. #_select_option {
  208. width: 90%;
  209. max-width: 90%;
  210. margin-right: 8px;
  211. padding: 8px 12px;
  212. border-radius: 6px;
  213. border: 1px solid #444;
  214. background: #232323;
  215. color: #fff;
  216. font-size: 1em;
  217. outline: none;
  218. }
  219.  
  220. #_start_btn, #_stop_btn {
  221. width: auto;
  222. margin-left: 0;
  223. padding: 8px 18px;
  224. border-radius: 6px;
  225. border: none;
  226. background: #229100;
  227. color: #fff;
  228. font-size: 1em;
  229. font-weight: bold;
  230. cursor: pointer;
  231. box-shadow: 0 2px 8px #0003;
  232. }
  233. #_stop_btn {
  234. background: #af0303;
  235. }
  236. ._disable_btn {
  237. background: #52454560 !important;
  238. cursor: not-allowed !important;
  239. }
  240.  
  241. #_notify {
  242. width: 90%;
  243. max-width: 90%;
  244. min-height: 10vh;
  245. margin: 8px 0;
  246. padding: 8px 12px;
  247. border-radius: 6px;
  248. background: #333;
  249. color: #c8ff00;
  250. font-size: 1em;
  251. word-wrap: break-word;
  252. /* text-align: center; */
  253. }
  254.  
  255. #_blank_page_link {
  256. margin-bottom: 8px;
  257. color: #fce6ff;
  258. font-weight: bold;
  259. font-style: italic;
  260. }
  261.  
  262. #_footer a,
  263. #_footer span {
  264. text-decoration: none;
  265. color: #00aeff;
  266. font-size: 1em;
  267. font-weight: bold;
  268. font-style: italic;
  269. }
  270.  
  271. #_overlay {
  272. position: fixed;
  273. top: 0;
  274. left: 0;
  275. width: 100vw;
  276. height: 100vh;
  277. background: rgba(0,0,0,0.8);
  278. z-index: 9998;
  279. pointer-events: all;
  280. }
  281.  
  282. #_floating_btn {
  283. position: fixed;
  284. bottom: 10%;
  285. right: 2%;
  286. width: 40px;
  287. height: 40px;
  288. background: #35bd00;
  289. border-radius: 50%;
  290. box-shadow: 0 2px 8px #0005;
  291. z-index: 10000;
  292. cursor: pointer;
  293. display: flex;
  294. align-items: center;
  295. justify-content: center;
  296. }
  297.  
  298. .hidden{
  299. display: none;
  300. }`;
  301. document.head.appendChild(style);
  302.  
  303. const container = document.createElement("div");
  304. container.innerHTML = containerHTML;
  305. document.body.appendChild(container);
  306.  
  307. // doi phien ban
  308. const version = document.getElementById("_version");
  309. version.innerText = VERSION;
  310. };
  311.  
  312. const setInterfaceVisible = (visible) => {
  313. const container = document.getElementById("_container");
  314. const overlay = document.getElementById("_overlay");
  315. container.style.display = visible ? "flex" : "none";
  316. overlay.style.display = visible ? "block" : "none";
  317. };
  318.  
  319. const isInterfaceVisible = () => {
  320. const container = document.getElementById("_container");
  321. return container.style.display !== "none" && container.style.display !== "";
  322. };
  323.  
  324. const toggleInterface = () => {
  325. setInterfaceVisible(!isInterfaceVisible());
  326. };
  327.  
  328. const addEventFloatingBtn = () => {
  329. const floatingBtn = document.getElementById("_floating_btn");
  330. const startBtn = document.getElementById("_start_btn");
  331. const stopBtn = document.getElementById("_stop_btn");
  332. const select = document.getElementById("_select_option");
  333. floatingBtn.addEventListener("click", () => {
  334. if (isRunning) {
  335. if (confirm("Duofarmer is farming. Do you want to stop and hide UI?")) {
  336. isRunning = false;
  337. stopBtn.hidden = true;
  338. startBtn.hidden = false;
  339. startBtn.disabled = true;
  340. startBtn.className = "_disable_btn";
  341. select.disabled = false;
  342. setTimeout(() => {
  343. startBtn.className = "";
  344. startBtn.disabled = false;
  345. }, 2000);
  346. setInterfaceVisible(false);
  347. return;
  348. } else {
  349. // Không làm gì nếu không muốn dừng
  350. return;
  351. }
  352. }
  353. setInterfaceVisible(!isInterfaceVisible());
  354. });
  355. };
  356.  
  357. const addEventStartBtn = () => {
  358. const startBtn = document.getElementById("_start_btn");
  359. const stopBtn = document.getElementById("_stop_btn");
  360. const select = document.getElementById("_select_option");
  361. startBtn.addEventListener("click", async () => {
  362. isRunning = true;
  363. startBtn.hidden = true;
  364. stopBtn.hidden = false;
  365. stopBtn.disabled = true;
  366. stopBtn.className = "_disable_btn";
  367. select.disabled = true;
  368.  
  369. // Lấy option đang chọn
  370. const selected = select.options[select.selectedIndex];
  371. const optionData = {
  372. type: selected.getAttribute("data-type"),
  373. amount: Number(selected.getAttribute("data-amount")),
  374. from: selected.getAttribute("data-from"),
  375. learn: selected.getAttribute("data-learn"),
  376. value: selected.value,
  377. label: selected.textContent,
  378. };
  379. await farmSelectedOption(optionData);
  380.  
  381. setTimeout(() => {
  382. stopBtn.className = "";
  383. stopBtn.disabled = false;
  384. }, 2000);
  385. });
  386. };
  387.  
  388. const addEventStopBtn = () => {
  389. const startBtn = document.getElementById("_start_btn");
  390. const stopBtn = document.getElementById("_stop_btn");
  391. const select = document.getElementById("_select_option");
  392. stopBtn.addEventListener("click", () => {
  393. isRunning = false;
  394. stopBtn.hidden = true;
  395. startBtn.hidden = false;
  396. startBtn.disabled = true;
  397. startBtn.className = "_disable_btn";
  398. select.disabled = false;
  399. setTimeout(() => {
  400. startBtn.className = "";
  401. startBtn.disabled = false;
  402. }, 2000);
  403. });
  404. };
  405.  
  406. const addEventVersionLink = () => {
  407. const versionLink = document.getElementById("_version");
  408. versionLink.addEventListener("click", () => {
  409. prompt("Your JWT token: ", jwt);
  410. });
  411. };
  412.  
  413. const addEventListeners = () => {
  414. addEventFloatingBtn();
  415. addEventStartBtn();
  416. addEventStopBtn();
  417. addEventVersionLink();
  418. };
  419.  
  420. // Thêm hàm tạo option cho select
  421. const populateOptions = () => {
  422. const select = document.getElementById("_select_option");
  423. select.innerHTML = "";
  424. // Lấy fromLanguage và learningLanguage từ userInfo nếu có
  425. const fromLang = userInfo?.fromLanguage || "ru";
  426. const learnLang = userInfo?.learningLanguage || "en";
  427. // Danh sách option mẫu
  428. const options = [
  429. { type: "gem", label: `Gem 30`, value: `gem-30`, amount: 30 },
  430. {
  431. type: "xp",
  432. label: `XP 499 (any -> en)`,
  433. value: `xp-499`,
  434. amount: 499,
  435. from: fromLang,
  436. learn: "en",
  437. },
  438. {
  439. type: "streak",
  440. label: `Streak repair (restore frozen streak)`,
  441. value: `repair`,
  442. },
  443. {
  444. type: "streak",
  445. label: `Streak farm (beta test)`,
  446. value: `farm`,
  447. },
  448. ];
  449. options.forEach((opt) => {
  450. const option = document.createElement("option");
  451. option.value = opt.value;
  452. option.textContent = opt.label;
  453. option.setAttribute("data-type", opt.type);
  454. option.setAttribute("data-amount", opt.amount);
  455. option.setAttribute("data-from", opt.from);
  456. option.setAttribute("data-learn", opt.learn);
  457. select.appendChild(option);
  458. });
  459. };
  460.  
  461. const updateNotify = (message) => {
  462. const notify = document.getElementById("_notify");
  463. const now = new Date().toLocaleTimeString();
  464. notify.innerText = `[${now}] ` + message;
  465. };
  466.  
  467. const disableInterface = (notify = {}) => {
  468. const startBtn = document.getElementById("_start_btn");
  469. const stopBtn = document.getElementById("_stop_btn");
  470. const select = document.getElementById("_select_option");
  471. startBtn.disabled = true;
  472. startBtn.className = "_disable_btn";
  473. stopBtn.disabled = true;
  474. select.disabled = true;
  475.  
  476. if (notify) {
  477. const notifyElement = document.getElementById("_notify");
  478. notifyElement.innerText = notify;
  479. }
  480. };
  481.  
  482. const resetStartStopBtn = () => {
  483. isRunning = false;
  484. const startBtn = document.getElementById("_start_btn");
  485. const stopBtn = document.getElementById("_stop_btn");
  486. const select = document.getElementById("_select_option");
  487. stopBtn.hidden = true;
  488. startBtn.hidden = false;
  489. startBtn.disabled = true;
  490. startBtn.className = "_disable_btn";
  491. select.disabled = false;
  492. setTimeout(() => {
  493. startBtn.className = "";
  494. startBtn.disabled = false;
  495. }, 2000);
  496. };
  497.  
  498. const blockStopBtn = () => {
  499. const stopBtn = document.getElementById("_stop_btn");
  500. stopBtn.disabled = true;
  501. stopBtn.classList.add("_disable_btn");
  502. };
  503.  
  504. const unblockStopBtn = () => {
  505. const stopBtn = document.getElementById("_stop_btn");
  506. stopBtn.disabled = false;
  507. stopBtn.classList.remove("_disable_btn");
  508. };
  509.  
  510. //--------------------Logic--------------------//
  511.  
  512. const getJwtToken = () => {
  513. var cookies = document.cookie.split(";");
  514. for (var i = 0; i < cookies.length; i++) {
  515. var cookie = cookies[i].trim();
  516. if (cookie.startsWith("jwt_token=")) {
  517. return cookie.substring("jwt_token=".length);
  518. }
  519. }
  520. return null;
  521. };
  522.  
  523. const decodeJwtToken = (token) => {
  524. var base64Url = token.split(".")[1];
  525. var base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
  526. var jsonPayload = decodeURIComponent(
  527. atob(base64)
  528. .split("")
  529. .map(function (c) {
  530. return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
  531. })
  532. .join("")
  533. );
  534.  
  535. return JSON.parse(jsonPayload);
  536. };
  537.  
  538. const formatHeaders = (jwt) => ({
  539. "Content-Type": "application/json",
  540. Authorization: "Bearer " + jwt,
  541. "User-Agent": navigator.userAgent,
  542. });
  543.  
  544. const getUserInfo = async (sub) => {
  545. const userInfoUrl = `https://www.duolingo.com/2017-06-30/users/${sub}?fields=id,username,fromLanguage,learningLanguage,streak,totalXp,level,numFollowers,numFollowing,gems,creationDate,streakData`;
  546. let response = await fetch(userInfoUrl, {
  547. method: "GET",
  548. headers: defaultHeaders,
  549. });
  550. return await response.json();
  551. };
  552.  
  553. const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  554.  
  555. const updateUserInfo = () => {
  556. const username = document.getElementById("_username");
  557. const from = document.getElementById("_from");
  558. const learn = document.getElementById("_learn");
  559. const streak = document.getElementById("_streak");
  560. const gem = document.getElementById("_gem");
  561. const xp = document.getElementById("_xp");
  562. username.innerText = userInfo.username;
  563. from.innerText = userInfo.fromLanguage;
  564. learn.innerText = userInfo.learningLanguage;
  565. streak.innerText = userInfo.streak;
  566. gem.innerText = userInfo.gems;
  567. xp.innerText = userInfo.totalXp;
  568. };
  569.  
  570. const toTimestamp = (dateStr) => {
  571. return Math.floor(new Date(dateStr).getTime() / 1000);
  572. };
  573.  
  574. const daysBetween = (startTimestamp, endTimestamp) => {
  575. return Math.floor((endTimestamp - startTimestamp) / (60 * 60 * 24));
  576. };
  577.  
  578. const sendRequest = async ({ url, payload, headers, method = "PUT" }) => {
  579. try {
  580. const res = await fetch(url, {
  581. method,
  582. headers,
  583. body: payload ? JSON.stringify(payload) : undefined,
  584. });
  585. return res;
  586. } catch (error) {
  587. return error;
  588. }
  589. };
  590.  
  591. const sendRequestWithDefaultHeaders = async ({
  592. url,
  593. payload,
  594. headers = {},
  595. method = "GET",
  596. }) => {
  597. const mergedHeaders = { ...defaultHeaders, ...headers };
  598. return sendRequest({ url, payload, headers: mergedHeaders, method });
  599. };
  600.  
  601. const farmGemOnce = async () => {
  602. const idReward =
  603. "SKILL_COMPLETION_BALANCED-dd2495f4_d44e_3fc3_8ac8_94e2191506f0-2-GEMS";
  604. const patchUrl = `https://www.duolingo.com/2017-06-30/users/${sub}/rewards/${idReward}`;
  605. const patchData = {
  606. consumed: true,
  607. learningLanguage: userInfo.learningLanguage,
  608. fromLanguage: userInfo.fromLanguage,
  609. };
  610. return await sendRequestWithDefaultHeaders({
  611. url: patchUrl,
  612. payload: patchData,
  613. method: "PATCH",
  614. });
  615. };
  616.  
  617. const farmGemLoop = async () => {
  618. const gemFarmed = 30;
  619. while (isRunning) {
  620. try {
  621. await farmGemOnce();
  622. userInfo = { ...userInfo, gems: userInfo.gems + gemFarmed };
  623. updateNotify(`You got ${gemFarmed} gem!!!`);
  624. updateUserInfo();
  625. await delay(DELAY);
  626. } catch (error) {
  627. updateNotify(
  628. `Error ${error.status}! Please record screen and report in telegram group!`
  629. );
  630. await delay(DELAY + 1000);
  631. }
  632. }
  633. };
  634.  
  635. const farmXpOnce = async (amount) => {
  636. const startTime = Math.floor(Date.now() / 1000);
  637. const fromLanguage = userInfo.fromLanguage;
  638. const completeUrl = `https://stories.duolingo.com/api2/stories/en-${fromLanguage}-the-passport/complete`;
  639. const payload = {
  640. awardXp: true,
  641. isFeaturedStoryInPracticeHub: false,
  642. completedBonusChallenge: true,
  643. mode: "READ",
  644. isV2Redo: false,
  645. isV2Story: false,
  646. isLegendaryMode: true,
  647. masterVersion: false,
  648. maxScore: 0,
  649. numHintsUsed: 0,
  650. score: 0,
  651. startTime: startTime,
  652. fromLanguage: fromLanguage,
  653. learningLanguage: "en",
  654. hasXpBoost: false,
  655. happyHourBonusXp: 449,
  656. };
  657. return await sendRequestWithDefaultHeaders({
  658. url: completeUrl,
  659. payload: payload,
  660. headers: defaultHeaders,
  661. method: "POST",
  662. });
  663. };
  664.  
  665. const farmXpLoop = async (amount) => {
  666. while (isRunning) {
  667. try {
  668. const response = await farmXpOnce(amount);
  669. if (response.status == 500) {
  670. updateNotify(
  671. "Make sure you are on English course (learning lang must be EN)!"
  672. );
  673. await delay(DELAY + 1000);
  674. continue;
  675. }
  676. const responseData = await response.json();
  677. const xpFarmed = responseData?.awardedXp || 0;
  678. userInfo = { ...userInfo, totalXp: userInfo.totalXp + xpFarmed };
  679. updateNotify(`You got ${xpFarmed} XP!!!`);
  680. updateUserInfo();
  681. await delay(DELAY);
  682. } catch (error) {
  683. updateNotify(
  684. `Error ${error.status}! Please record screen and report in telegram group!`
  685. );
  686. await delay(DELAY + 1000);
  687. }
  688. }
  689. };
  690.  
  691. const farmSessionOnce = async (startTime, endTime) => {
  692. //tạo và lấy session
  693. const sessionPayload = {
  694. challengeTypes: [
  695. "assist",
  696. "characterIntro",
  697. "characterMatch",
  698. "characterPuzzle",
  699. "characterSelect",
  700. "characterTrace",
  701. "characterWrite",
  702. "completeReverseTranslation",
  703. "definition",
  704. "dialogue",
  705. "extendedMatch",
  706. "extendedListenMatch",
  707. "form",
  708. "freeResponse",
  709. "gapFill",
  710. "judge",
  711. "listen",
  712. "listenComplete",
  713. "listenMatch",
  714. "match",
  715. "name",
  716. "listenComprehension",
  717. "listenIsolation",
  718. "listenSpeak",
  719. "listenTap",
  720. "orderTapComplete",
  721. "partialListen",
  722. "partialReverseTranslate",
  723. "patternTapComplete",
  724. "radioBinary",
  725. "radioImageSelect",
  726. "radioListenMatch",
  727. "radioListenRecognize",
  728. "radioSelect",
  729. "readComprehension",
  730. "reverseAssist",
  731. "sameDifferent",
  732. "select",
  733. "selectPronunciation",
  734. "selectTranscription",
  735. "svgPuzzle",
  736. "syllableTap",
  737. "syllableListenTap",
  738. "speak",
  739. "tapCloze",
  740. "tapClozeTable",
  741. "tapComplete",
  742. "tapCompleteTable",
  743. "tapDescribe",
  744. "translate",
  745. "transliterate",
  746. "transliterationAssist",
  747. "typeCloze",
  748. "typeClozeTable",
  749. "typeComplete",
  750. "typeCompleteTable",
  751. "writeComprehension",
  752. ],
  753. fromLanguage: userInfo.fromLanguage,
  754. isFinalLevel: false,
  755. isV2: true,
  756. juicy: true,
  757. learningLanguage: userInfo.learningLanguage,
  758. smartTipsVersion: 2,
  759. type: "GLOBAL_PRACTICE",
  760. };
  761. const sessionRes = await sendRequestWithDefaultHeaders({
  762. url: "https://www.duolingo.com/2017-06-30/sessions",
  763. payload: sessionPayload,
  764. method: "POST",
  765. });
  766. const sessionData = await sessionRes.json();
  767.  
  768. // lấy session và gán vào update
  769. const updateSessionPayload = {
  770. ...sessionData,
  771. heartsLeft: 0,
  772. startTime: startTime,
  773. enableBonusPoints: false,
  774. endTime: endTime,
  775. failed: false,
  776. maxInLessonStreak: 9,
  777. shouldLearnThings: true,
  778. };
  779. const updateRes = await sendRequestWithDefaultHeaders({
  780. url: `https://www.duolingo.com/2017-06-30/sessions/${sessionData.id}`,
  781. payload: updateSessionPayload,
  782. method: "PUT",
  783. });
  784. return await updateRes.json();
  785. };
  786.  
  787. const repairStreak = async () => {
  788. blockStopBtn();
  789. try {
  790. if(!userInfo.streakData.currentStreak) {
  791. updateNotify("You have no streak! Abort!");
  792. resetStartStopBtn();
  793. }
  794.  
  795. const startStreakDate = userInfo.streakData.currentStreak.startDate;
  796. const endStreakDate = userInfo.streakData.currentStreak.endDate;
  797.  
  798. const startStreakTimestamp = toTimestamp(startStreakDate);
  799. const endStreakTimestamp = toTimestamp(endStreakDate);
  800. const expectedStreak =
  801. daysBetween(startStreakTimestamp, endStreakTimestamp) + 1; //+1 vì nó bị lệch gì đó
  802.  
  803. //check xem có streak freeze không
  804. if (expectedStreak > userInfo.streak) {
  805. updateNotify("Your streak is frozen somewhere! Repairing...");
  806. await delay(2000);
  807.  
  808. let currentTimestamp = Math.floor(Date.now() / 1000);
  809. for (let i = 0; i < expectedStreak; i++) {
  810. const createdSession = await farmSessionOnce(
  811. currentTimestamp,
  812. currentTimestamp + 60
  813. );
  814. currentTimestamp -= 86400;
  815. updateNotify(
  816. `Trying to repair streak ( ${i + 1}/${expectedStreak})...`
  817. );
  818. await delay(DELAY);
  819. }
  820.  
  821. const userAfterRepair = await getUserInfo(sub);
  822. if (userAfterRepair.streakData.currentStreak.length > expectedStreak) {
  823. updateNotify(`Your streak has been repaired! No more frozen streak!`);
  824. userInfo = userAfterRepair;
  825. updateUserInfo();
  826. } else {
  827. updateNotify(
  828. `Streak repair failed or no frozen streak! Please check your account!`
  829. );
  830. }
  831. } else {
  832. updateNotify("You have no frozen streak! No need to repair!");
  833. resetStartStopBtn();
  834. return;
  835. }
  836. } finally {
  837. unblockStopBtn();
  838. }
  839. };
  840.  
  841. const farmStreakLoop = async () => {
  842. const hasStreak = !!userInfo.streakData.currentStreak;
  843. const startStreakDate = hasStreak
  844. ? userInfo.streakData.currentStreak.startDate
  845. : new Date();
  846.  
  847. const startFarmStreakTimestamp = toTimestamp(startStreakDate);
  848. let currentTimestamp = hasStreak
  849. ? startFarmStreakTimestamp - 86400 // Nếu đã có streak, farm từ ngày trước đó
  850. : startFarmStreakTimestamp; // Nếu chưa có streak, farm luôn ngày hôm nay
  851.  
  852. while (isRunning) {
  853. try {
  854. const sessionRes = await farmSessionOnce(currentTimestamp, currentTimestamp + 60);
  855. if (sessionRes) {
  856. currentTimestamp -= 86400;
  857. userInfo = { ...userInfo, streak: userInfo.streak + 1 };
  858. updateNotify(`You got +1 streak!`);
  859. updateUserInfo();
  860. await delay(DELAY);
  861. } else {
  862. updateNotify("Failed to farm streak session, I'm trying again...");
  863. await delay(2000);
  864. continue;
  865. }
  866. } catch (error) {
  867. updateNotify(`Error in farmStreak: ${error?.message || error}`);
  868. await delay(2000);
  869. continue;
  870. }
  871. }
  872. }
  873.  
  874. const farmSelectedOption = async (option) => {
  875. const { type, value, amount, from, learn } = option;
  876.  
  877. switch (type) {
  878. case "gem":
  879. farmGemLoop();
  880. break;
  881. case "xp":
  882. farmXpLoop(amount);
  883. break;
  884. case "streak":
  885. if (value == "repair") {
  886. repairStreak();
  887. } else if (value == "farm") {
  888. farmStreakLoop();
  889. }
  890.  
  891. break;
  892. }
  893. };
  894.  
  895. const initVariables = async () => {
  896. jwt = getJwtToken();
  897. if (!jwt) {
  898. disableInterface("Please login to Duolingo and reload!");
  899. return;
  900. }
  901. defaultHeaders = formatHeaders(jwt);
  902. const decodedJwt = decodeJwtToken(jwt);
  903. sub = decodedJwt.sub;
  904. userInfo = await getUserInfo(sub);
  905. populateOptions(); // cập nhật lại option khi đã có userInfo
  906. };
  907.  
  908. //--------------------Main--------------------//
  909.  
  910. (async () => {
  911. initInterface();
  912. setInterfaceVisible(false);
  913. addEventListeners();
  914. await initVariables();
  915. updateUserInfo();
  916. })();

QingJ © 2025

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