您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
[ LITE ] DuoFarmer 是一款可帮助您以惊人的速度在 Duolingo 中赚取 XP 的工具。
- // ==UserScript==
- // @name DuoFarmer
- // @namespace https://duo-farmer.vercel.app
- // @version 1.2
- // @description [ LITE ] DuoFarmer is a tool that helps you earn XP in Duolingo at blazing speed.
- // @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.
- // @description:fr [ LITE ] DuoFarmer est un outil qui vous aide à gagner de l'XP sur Duolingo à une vitesse fulgurante.
- // @description:es [ LITE ] DuoFarmer es una herramienta que te ayuda a ganar XP en Duolingo a una velocidad asombrosa.
- // @description:de [ LITE ] DuoFarmer ist ein Tool, das Ihnen hilft, in Duolingo blitzschnell XP zu verdienen.
- // @description:it [ LITE ] DuoFarmer è uno strumento che ti aiuta a guadagnare XP su Duolingo a una velocità incredibile.
- // @description:ja [ LITE ] DuoFarmer は、Duolingo で驚異的なスピードで XP を獲得するのに役立つツールです。
- // @description:ko [ LITE ] DuoFarmer는 Duolingo에서 엄청난 속도로 XP를 얻을 수 있도록 도와주는 도구입니다.
- // @description:ru [ LITE ] DuoFarmer - это инструмент, который помогает вам зарабатывать XP в Duolingo с невероятной скоростью.
- // @description:zh-CN [ LITE ] DuoFarmer 是一款可帮助您以惊人的速度在 Duolingo 中赚取 XP 的工具。
- // @author Lamduck
- // @match https://*.duolingo.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=duolingo.com
- // @grant none
- // @license none
- // ==/UserScript==
- const VERSION = "1.2";
- const DELAY = 500;
- var jwt, defaultHeaders, userInfo, sub;
- let isRunning = false;
- const initInterface = () => {
- const containerHTML = `
- <link rel="stylesheet" href="xp.css" />
- <div id="_overlay"></div>
- <div id="_container">
- <div id="_header">
- <span class="_label">Duofarmer minimal UI</span>
- </div>
- <div id="_body">
- <table id="_table_main" class="_table">
- <thead>
- <tr>
- <th>Username</th>
- <th>From</th>
- <th>Learning</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td id="_username">duofarmer</td>
- <td id="_from">any</td>
- <td id="_learn">any</td>
- </tr>
- </tbody>
- </table>
- <table id="_table_progress" class="_table">
- <thead>
- <tr>
- <th>Streak</th>
- <th>Gem</th>
- <th>XP</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td id="_streak">0</td>
- <td id="_gem">0</td>
- <td id="_xp">0</td>
- </tr>
- </tbody>
- </table>
- <div id="_action_row">
- <select id="_select_option">
- <!-- <option value="option1">Option 1</option> -->
- <!-- <option value="option2">Option 2</option> -->
- </select>
- <button id="_start_btn">Start</button>
- <button id="_stop_btn" hidden>Stop</button>
- </div>
- <div id="_notify">
- This is a minimal version that reduces device resources. <br />
- Only support learning language is English. <br />
- (will not work if from language is also English) <br>
- <br>
- Recommended to go to blank page for max performance + reduce resource usage.
- </div>
- <a id="_blank_page_link" href="https://www.duolingo.com/errors/404.html">Blank page (click here)</a>
- </div>
- <div id="_footer">
- <a href="https://gf.qytechs.cn/vi/scripts/528621-duofarmer" target="_blank"
- >Greasyfork</a
- >
- <a href="https://t.me/duofarmer" target="_blank">Telegram</a>
- <a>Version <span id="_version">1.0</span></a>
- </div>
- </div>
- <div id="_floating_btn">😼</div>
- `;
- const style = document.createElement("style");
- style.innerHTML = `#_container {
- width: 90vw;
- max-width: 800px;
- min-height: 40vh;
- max-height: 90vh;
- background: #222;
- color: #fff;
- border-radius: 10px;
- box-shadow: 0 2px 12px #0008;
- font-family: sans-serif;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: 9999;
- }
- #_header {
- height: 60px;
- background: #333;
- display: flex;
- align-items: center;
- justify-content: center;
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
- width: 100%;
- }
- #_body {
- min-height: 40vh;
- max-height: 100%;
- min-width: 0;
- background: #282828;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- overflow-y: auto;
- flex: 1;
- flex-direction: column;
- }
- #_footer {
- height: 50px;
- background: #222;
- display: flex;
- align-items: center;
- justify-content: space-evenly;
- border-bottom-left-radius: 10px;
- border-bottom-right-radius: 10px;
- width: 100%;
- }
- ._label {
- font-size: 1em;
- }
- #_header ._label {
- font-size: 1.5em;
- font-style: italic;
- font-weight: bold;
- color: #fac8ff;
- }
- #_body ._label {
- font-size: 1.2em;
- }
- ._table {
- width: 100%;
- background: #232323;
- color: #fff;
- border-radius: 8px;
- padding: 8px 12px;
- text-align: center;
- table-layout: fixed;
- }
- ._table th,
- ._table td {
- padding: 9px 12px;
- text-align: center;
- border-bottom: 1px solid #444;
- width: 1%;
- }
- ._table tbody tr:last-child td {
- border-bottom: none;
- }
- #_body h3 {
- margin: 0;
- color: #fff;
- font-size: 1.1em;
- font-weight: bold;
- letter-spacing: 1px;
- }
- #_action_row {
- width: 90%;
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin: 8px 0;
- gap: 8px;
- }
- #_select_option {
- width: 90%;
- max-width: 90%;
- margin-right: 8px;
- padding: 8px 12px;
- border-radius: 6px;
- border: 1px solid #444;
- background: #232323;
- color: #fff;
- font-size: 1em;
- outline: none;
- }
- #_start_btn, #_stop_btn {
- width: auto;
- margin-left: 0;
- padding: 8px 18px;
- border-radius: 6px;
- border: none;
- background: #229100;
- color: #fff;
- font-size: 1em;
- font-weight: bold;
- cursor: pointer;
- box-shadow: 0 2px 8px #0003;
- }
- #_stop_btn {
- background: #af0303;
- }
- ._disable_btn {
- background: #52454560 !important;
- cursor: not-allowed !important;
- }
- #_notify {
- width: 90%;
- max-width: 90%;
- min-height: 10vh;
- margin: 8px 0;
- padding: 8px 12px;
- border-radius: 6px;
- background: #333;
- color: #c8ff00;
- font-size: 1em;
- word-wrap: break-word;
- /* text-align: center; */
- }
- #_blank_page_link {
- margin-bottom: 8px;
- color: #fce6ff;
- font-weight: bold;
- font-style: italic;
- }
- #_footer a,
- #_footer span {
- text-decoration: none;
- color: #00aeff;
- font-size: 1em;
- font-weight: bold;
- font-style: italic;
- }
- #_overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: rgba(0,0,0,0.8);
- z-index: 9998;
- pointer-events: all;
- }
- #_floating_btn {
- position: fixed;
- bottom: 10%;
- right: 2%;
- width: 40px;
- height: 40px;
- background: #35bd00;
- border-radius: 50%;
- box-shadow: 0 2px 8px #0005;
- z-index: 10000;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .hidden{
- display: none;
- }`;
- document.head.appendChild(style);
- const container = document.createElement("div");
- container.innerHTML = containerHTML;
- document.body.appendChild(container);
- // doi phien ban
- const version = document.getElementById("_version");
- version.innerText = VERSION;
- };
- const setInterfaceVisible = (visible) => {
- const container = document.getElementById("_container");
- const overlay = document.getElementById("_overlay");
- container.style.display = visible ? "flex" : "none";
- overlay.style.display = visible ? "block" : "none";
- };
- const isInterfaceVisible = () => {
- const container = document.getElementById("_container");
- return container.style.display !== "none" && container.style.display !== "";
- };
- const toggleInterface = () => {
- setInterfaceVisible(!isInterfaceVisible());
- };
- const addEventFloatingBtn = () => {
- const floatingBtn = document.getElementById("_floating_btn");
- const startBtn = document.getElementById("_start_btn");
- const stopBtn = document.getElementById("_stop_btn");
- const select = document.getElementById("_select_option");
- floatingBtn.addEventListener("click", () => {
- if (isRunning) {
- if (confirm("Duofarmer is farming. Do you want to stop and hide UI?")) {
- isRunning = false;
- stopBtn.hidden = true;
- startBtn.hidden = false;
- startBtn.disabled = true;
- startBtn.className = "_disable_btn";
- select.disabled = false;
- setTimeout(() => {
- startBtn.className = "";
- startBtn.disabled = false;
- }, 2000);
- setInterfaceVisible(false);
- return;
- } else {
- // Không làm gì nếu không muốn dừng
- return;
- }
- }
- setInterfaceVisible(!isInterfaceVisible());
- });
- };
- const addEventStartBtn = () => {
- const startBtn = document.getElementById("_start_btn");
- const stopBtn = document.getElementById("_stop_btn");
- const select = document.getElementById("_select_option");
- startBtn.addEventListener("click", async () => {
- isRunning = true;
- startBtn.hidden = true;
- stopBtn.hidden = false;
- stopBtn.disabled = true;
- stopBtn.className = "_disable_btn";
- select.disabled = true;
- // Lấy option đang chọn
- const selected = select.options[select.selectedIndex];
- const optionData = {
- type: selected.getAttribute("data-type"),
- amount: Number(selected.getAttribute("data-amount")),
- from: selected.getAttribute("data-from"),
- learn: selected.getAttribute("data-learn"),
- value: selected.value,
- label: selected.textContent,
- };
- await farmSelectedOption(optionData);
- setTimeout(() => {
- stopBtn.className = "";
- stopBtn.disabled = false;
- }, 2000);
- });
- };
- const addEventStopBtn = () => {
- const startBtn = document.getElementById("_start_btn");
- const stopBtn = document.getElementById("_stop_btn");
- const select = document.getElementById("_select_option");
- stopBtn.addEventListener("click", () => {
- isRunning = false;
- stopBtn.hidden = true;
- startBtn.hidden = false;
- startBtn.disabled = true;
- startBtn.className = "_disable_btn";
- select.disabled = false;
- setTimeout(() => {
- startBtn.className = "";
- startBtn.disabled = false;
- }, 2000);
- });
- };
- const addEventVersionLink = () => {
- const versionLink = document.getElementById("_version");
- versionLink.addEventListener("click", () => {
- prompt("Your JWT token: ", jwt);
- });
- };
- const addEventListeners = () => {
- addEventFloatingBtn();
- addEventStartBtn();
- addEventStopBtn();
- addEventVersionLink();
- };
- // Thêm hàm tạo option cho select
- const populateOptions = () => {
- const select = document.getElementById("_select_option");
- select.innerHTML = "";
- // Lấy fromLanguage và learningLanguage từ userInfo nếu có
- const fromLang = userInfo?.fromLanguage || "ru";
- const learnLang = userInfo?.learningLanguage || "en";
- // Danh sách option mẫu
- const options = [
- { type: "gem", label: `Gem 30`, value: `gem-30`, amount: 30 },
- {
- type: "xp",
- label: `XP 499 (any -> en)`,
- value: `xp-499`,
- amount: 499,
- from: fromLang,
- learn: "en",
- },
- {
- type: "streak",
- label: `Streak repair (restore frozen streak)`,
- value: `repair`,
- },
- {
- type: "streak",
- label: `Streak farm (beta test)`,
- value: `farm`,
- },
- ];
- options.forEach((opt) => {
- const option = document.createElement("option");
- option.value = opt.value;
- option.textContent = opt.label;
- option.setAttribute("data-type", opt.type);
- option.setAttribute("data-amount", opt.amount);
- option.setAttribute("data-from", opt.from);
- option.setAttribute("data-learn", opt.learn);
- select.appendChild(option);
- });
- };
- const updateNotify = (message) => {
- const notify = document.getElementById("_notify");
- const now = new Date().toLocaleTimeString();
- notify.innerText = `[${now}] ` + message;
- };
- const disableInterface = (notify = {}) => {
- const startBtn = document.getElementById("_start_btn");
- const stopBtn = document.getElementById("_stop_btn");
- const select = document.getElementById("_select_option");
- startBtn.disabled = true;
- startBtn.className = "_disable_btn";
- stopBtn.disabled = true;
- select.disabled = true;
- if (notify) {
- const notifyElement = document.getElementById("_notify");
- notifyElement.innerText = notify;
- }
- };
- const resetStartStopBtn = () => {
- isRunning = false;
- const startBtn = document.getElementById("_start_btn");
- const stopBtn = document.getElementById("_stop_btn");
- const select = document.getElementById("_select_option");
- stopBtn.hidden = true;
- startBtn.hidden = false;
- startBtn.disabled = true;
- startBtn.className = "_disable_btn";
- select.disabled = false;
- setTimeout(() => {
- startBtn.className = "";
- startBtn.disabled = false;
- }, 2000);
- };
- const blockStopBtn = () => {
- const stopBtn = document.getElementById("_stop_btn");
- stopBtn.disabled = true;
- stopBtn.classList.add("_disable_btn");
- };
- const unblockStopBtn = () => {
- const stopBtn = document.getElementById("_stop_btn");
- stopBtn.disabled = false;
- stopBtn.classList.remove("_disable_btn");
- };
- //--------------------Logic--------------------//
- const getJwtToken = () => {
- var cookies = document.cookie.split(";");
- for (var i = 0; i < cookies.length; i++) {
- var cookie = cookies[i].trim();
- if (cookie.startsWith("jwt_token=")) {
- return cookie.substring("jwt_token=".length);
- }
- }
- return null;
- };
- const decodeJwtToken = (token) => {
- var base64Url = token.split(".")[1];
- var base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
- var jsonPayload = decodeURIComponent(
- atob(base64)
- .split("")
- .map(function (c) {
- return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
- })
- .join("")
- );
- return JSON.parse(jsonPayload);
- };
- const formatHeaders = (jwt) => ({
- "Content-Type": "application/json",
- Authorization: "Bearer " + jwt,
- "User-Agent": navigator.userAgent,
- });
- const getUserInfo = async (sub) => {
- const userInfoUrl = `https://www.duolingo.com/2017-06-30/users/${sub}?fields=id,username,fromLanguage,learningLanguage,streak,totalXp,level,numFollowers,numFollowing,gems,creationDate,streakData`;
- let response = await fetch(userInfoUrl, {
- method: "GET",
- headers: defaultHeaders,
- });
- return await response.json();
- };
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
- const updateUserInfo = () => {
- const username = document.getElementById("_username");
- const from = document.getElementById("_from");
- const learn = document.getElementById("_learn");
- const streak = document.getElementById("_streak");
- const gem = document.getElementById("_gem");
- const xp = document.getElementById("_xp");
- username.innerText = userInfo.username;
- from.innerText = userInfo.fromLanguage;
- learn.innerText = userInfo.learningLanguage;
- streak.innerText = userInfo.streak;
- gem.innerText = userInfo.gems;
- xp.innerText = userInfo.totalXp;
- };
- const toTimestamp = (dateStr) => {
- return Math.floor(new Date(dateStr).getTime() / 1000);
- };
- const daysBetween = (startTimestamp, endTimestamp) => {
- return Math.floor((endTimestamp - startTimestamp) / (60 * 60 * 24));
- };
- const sendRequest = async ({ url, payload, headers, method = "PUT" }) => {
- try {
- const res = await fetch(url, {
- method,
- headers,
- body: payload ? JSON.stringify(payload) : undefined,
- });
- return res;
- } catch (error) {
- return error;
- }
- };
- const sendRequestWithDefaultHeaders = async ({
- url,
- payload,
- headers = {},
- method = "GET",
- }) => {
- const mergedHeaders = { ...defaultHeaders, ...headers };
- return sendRequest({ url, payload, headers: mergedHeaders, method });
- };
- const farmGemOnce = async () => {
- const idReward =
- "SKILL_COMPLETION_BALANCED-dd2495f4_d44e_3fc3_8ac8_94e2191506f0-2-GEMS";
- const patchUrl = `https://www.duolingo.com/2017-06-30/users/${sub}/rewards/${idReward}`;
- const patchData = {
- consumed: true,
- learningLanguage: userInfo.learningLanguage,
- fromLanguage: userInfo.fromLanguage,
- };
- return await sendRequestWithDefaultHeaders({
- url: patchUrl,
- payload: patchData,
- method: "PATCH",
- });
- };
- const farmGemLoop = async () => {
- const gemFarmed = 30;
- while (isRunning) {
- try {
- await farmGemOnce();
- userInfo = { ...userInfo, gems: userInfo.gems + gemFarmed };
- updateNotify(`You got ${gemFarmed} gem!!!`);
- updateUserInfo();
- await delay(DELAY);
- } catch (error) {
- updateNotify(
- `Error ${error.status}! Please record screen and report in telegram group!`
- );
- await delay(DELAY + 1000);
- }
- }
- };
- const farmXpOnce = async (amount) => {
- const startTime = Math.floor(Date.now() / 1000);
- const fromLanguage = userInfo.fromLanguage;
- const completeUrl = `https://stories.duolingo.com/api2/stories/en-${fromLanguage}-the-passport/complete`;
- const payload = {
- awardXp: true,
- isFeaturedStoryInPracticeHub: false,
- completedBonusChallenge: true,
- mode: "READ",
- isV2Redo: false,
- isV2Story: false,
- isLegendaryMode: true,
- masterVersion: false,
- maxScore: 0,
- numHintsUsed: 0,
- score: 0,
- startTime: startTime,
- fromLanguage: fromLanguage,
- learningLanguage: "en",
- hasXpBoost: false,
- happyHourBonusXp: 449,
- };
- return await sendRequestWithDefaultHeaders({
- url: completeUrl,
- payload: payload,
- headers: defaultHeaders,
- method: "POST",
- });
- };
- const farmXpLoop = async (amount) => {
- while (isRunning) {
- try {
- const response = await farmXpOnce(amount);
- if (response.status == 500) {
- updateNotify(
- "Make sure you are on English course (learning lang must be EN)!"
- );
- await delay(DELAY + 1000);
- continue;
- }
- const responseData = await response.json();
- const xpFarmed = responseData?.awardedXp || 0;
- userInfo = { ...userInfo, totalXp: userInfo.totalXp + xpFarmed };
- updateNotify(`You got ${xpFarmed} XP!!!`);
- updateUserInfo();
- await delay(DELAY);
- } catch (error) {
- updateNotify(
- `Error ${error.status}! Please record screen and report in telegram group!`
- );
- await delay(DELAY + 1000);
- }
- }
- };
- const farmSessionOnce = async (startTime, endTime) => {
- //tạo và lấy session
- const sessionPayload = {
- challengeTypes: [
- "assist",
- "characterIntro",
- "characterMatch",
- "characterPuzzle",
- "characterSelect",
- "characterTrace",
- "characterWrite",
- "completeReverseTranslation",
- "definition",
- "dialogue",
- "extendedMatch",
- "extendedListenMatch",
- "form",
- "freeResponse",
- "gapFill",
- "judge",
- "listen",
- "listenComplete",
- "listenMatch",
- "match",
- "name",
- "listenComprehension",
- "listenIsolation",
- "listenSpeak",
- "listenTap",
- "orderTapComplete",
- "partialListen",
- "partialReverseTranslate",
- "patternTapComplete",
- "radioBinary",
- "radioImageSelect",
- "radioListenMatch",
- "radioListenRecognize",
- "radioSelect",
- "readComprehension",
- "reverseAssist",
- "sameDifferent",
- "select",
- "selectPronunciation",
- "selectTranscription",
- "svgPuzzle",
- "syllableTap",
- "syllableListenTap",
- "speak",
- "tapCloze",
- "tapClozeTable",
- "tapComplete",
- "tapCompleteTable",
- "tapDescribe",
- "translate",
- "transliterate",
- "transliterationAssist",
- "typeCloze",
- "typeClozeTable",
- "typeComplete",
- "typeCompleteTable",
- "writeComprehension",
- ],
- fromLanguage: userInfo.fromLanguage,
- isFinalLevel: false,
- isV2: true,
- juicy: true,
- learningLanguage: userInfo.learningLanguage,
- smartTipsVersion: 2,
- type: "GLOBAL_PRACTICE",
- };
- const sessionRes = await sendRequestWithDefaultHeaders({
- url: "https://www.duolingo.com/2017-06-30/sessions",
- payload: sessionPayload,
- method: "POST",
- });
- const sessionData = await sessionRes.json();
- // lấy session và gán vào update
- const updateSessionPayload = {
- ...sessionData,
- heartsLeft: 0,
- startTime: startTime,
- enableBonusPoints: false,
- endTime: endTime,
- failed: false,
- maxInLessonStreak: 9,
- shouldLearnThings: true,
- };
- const updateRes = await sendRequestWithDefaultHeaders({
- url: `https://www.duolingo.com/2017-06-30/sessions/${sessionData.id}`,
- payload: updateSessionPayload,
- method: "PUT",
- });
- return await updateRes.json();
- };
- const repairStreak = async () => {
- blockStopBtn();
- try {
- if(!userInfo.streakData.currentStreak) {
- updateNotify("You have no streak! Abort!");
- resetStartStopBtn();
- }
- const startStreakDate = userInfo.streakData.currentStreak.startDate;
- const endStreakDate = userInfo.streakData.currentStreak.endDate;
- const startStreakTimestamp = toTimestamp(startStreakDate);
- const endStreakTimestamp = toTimestamp(endStreakDate);
- const expectedStreak =
- daysBetween(startStreakTimestamp, endStreakTimestamp) + 1; //+1 vì nó bị lệch gì đó
- //check xem có streak freeze không
- if (expectedStreak > userInfo.streak) {
- updateNotify("Your streak is frozen somewhere! Repairing...");
- await delay(2000);
- let currentTimestamp = Math.floor(Date.now() / 1000);
- for (let i = 0; i < expectedStreak; i++) {
- const createdSession = await farmSessionOnce(
- currentTimestamp,
- currentTimestamp + 60
- );
- currentTimestamp -= 86400;
- updateNotify(
- `Trying to repair streak ( ${i + 1}/${expectedStreak})...`
- );
- await delay(DELAY);
- }
- const userAfterRepair = await getUserInfo(sub);
- if (userAfterRepair.streakData.currentStreak.length > expectedStreak) {
- updateNotify(`Your streak has been repaired! No more frozen streak!`);
- userInfo = userAfterRepair;
- updateUserInfo();
- } else {
- updateNotify(
- `Streak repair failed or no frozen streak! Please check your account!`
- );
- }
- } else {
- updateNotify("You have no frozen streak! No need to repair!");
- resetStartStopBtn();
- return;
- }
- } finally {
- unblockStopBtn();
- }
- };
- const farmStreakLoop = async () => {
- const hasStreak = !!userInfo.streakData.currentStreak;
- const startStreakDate = hasStreak
- ? userInfo.streakData.currentStreak.startDate
- : new Date();
- const startFarmStreakTimestamp = toTimestamp(startStreakDate);
- let currentTimestamp = hasStreak
- ? startFarmStreakTimestamp - 86400 // Nếu đã có streak, farm từ ngày trước đó
- : startFarmStreakTimestamp; // Nếu chưa có streak, farm luôn ngày hôm nay
- while (isRunning) {
- try {
- const sessionRes = await farmSessionOnce(currentTimestamp, currentTimestamp + 60);
- if (sessionRes) {
- currentTimestamp -= 86400;
- userInfo = { ...userInfo, streak: userInfo.streak + 1 };
- updateNotify(`You got +1 streak!`);
- updateUserInfo();
- await delay(DELAY);
- } else {
- updateNotify("Failed to farm streak session, I'm trying again...");
- await delay(2000);
- continue;
- }
- } catch (error) {
- updateNotify(`Error in farmStreak: ${error?.message || error}`);
- await delay(2000);
- continue;
- }
- }
- }
- const farmSelectedOption = async (option) => {
- const { type, value, amount, from, learn } = option;
- switch (type) {
- case "gem":
- farmGemLoop();
- break;
- case "xp":
- farmXpLoop(amount);
- break;
- case "streak":
- if (value == "repair") {
- repairStreak();
- } else if (value == "farm") {
- farmStreakLoop();
- }
- break;
- }
- };
- const initVariables = async () => {
- jwt = getJwtToken();
- if (!jwt) {
- disableInterface("Please login to Duolingo and reload!");
- return;
- }
- defaultHeaders = formatHeaders(jwt);
- const decodedJwt = decodeJwtToken(jwt);
- sub = decodedJwt.sub;
- userInfo = await getUserInfo(sub);
- populateOptions(); // cập nhật lại option khi đã có userInfo
- };
- //--------------------Main--------------------//
- (async () => {
- initInterface();
- setInterfaceVisible(false);
- addEventListeners();
- await initVariables();
- updateUserInfo();
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址