您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hacklingo — 支持高速多线程的XP、连胜和宝石刷分工具。
// ==UserScript== // @name Hacklingo // @name:vi Hacklingo - Công cụ farm XP // @name:en Hacklingo - XP Farming Tool // @name:es Hacklingo - Herramienta de farmeo de XP // @name:fr Hacklingo - Outil de farm XP // @name:de Hacklingo - XP Farming Tool // @name:pt-BR Hacklingo - Ferramenta de farm XP // @name:ru Hacklingo - Инструмент для фарма XP // @name:zh-CN Hacklingo - XP刷分工具 // @name:ja Hacklingo - XPファーミングツール // @name:ko Hacklingo - XP 파밍 도구 // @description Hacklingo — tool for farming XP, streaks, and gems with ultra-fast multi-thread support. // @description:vi Hacklingo — công cụ farm XP, streaks và gems với tốc độ cao nhờ multi-thread. // @description:en Hacklingo — tool for farming XP, streaks, and gems with ultra-fast multi-thread support. // @description:es Hacklingo — herramienta para farmear XP, rachas y gemas con soporte multi-hilo de alta velocidad. // @description:fr Hacklingo — outil pour farmer XP, séries et gemmes avec un support multi-thread ultra-rapide. // @description:de Hacklingo — Tool zum Farmen von XP, Serien und Edelsteinen mit ultraschneller Multi-Thread-Unterstützung. // @description:pt-BR Hacklingo — ferramenta para farmar XP, streaks e gemas com suporte multi-thread de alta velocidade. // @description:ru Hacklingo — инструмент для фарма XP, серий и самоцветов с ультра-быстрой многопоточностью. // @description:zh-CN Hacklingo — 支持高速多线程的XP、连胜和宝石刷分工具。 // @description:ja Hacklingo — XP・連続日数・ジェムを超高速マルチスレッドで稼ぐツール。 // @description:ko Hacklingo — 초고속 멀티스레드로 XP, 연속 기록, 보석을 파밍하는 도구. // @namespace https://twisk.fun // @version 1.0.1 // @author airpl4ne // @author Airplane Mode // @author S // @match https://*.duolingo.com/* // @icon https://github.com/pillowslua/crackduo/blob/main/hacklingo.png?raw=true // @grant none // @license MIT // ==/UserScript== const VERSION = "1.0.0"; // version const BASE_DELAY = 500; // Anti-rate limit . Please dont change this const MAX_CONCURRENT_REQUESTS = 50; // Max concurrent requests const DISCORD_LINK = "https://discord.gg/m3EV55SpYw"; // Discord server link const REAPPEAR_DELAY = 10000; // delay !! var jwt, defaultHeaders, userInfo, sub; let isRunning = false; let threadCount = 1; // Default thread count let requestQueue = []; // quueu let activeRequests = 0; // active rqs let rateLimitDelay = BASE_DELAY; // delay set // user agent! const USER_AGENTS = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.10 Safari/605.1.15", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582", "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:136.0) Gecko/20100101 Firefox/136.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Trailer/93.3.8652.5" ]; const initInterface = () => { const containerHTML = ` <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet"> <div id="_overlay"></div> <div id="_container"> <div id="_header"> <span class="_label">Hacklingo v1.0.1</span> <button id="_close_btn">✕</button> </div> <div id="_body"> <div id="_user_card" class="_card"> <h3>User Info</h3> <div class="_info_row"><span>Username:</span><span id="_username">duofarmer</span></div> <div class="_info_row"><span>From:</span><span id="_from">any</span></div> <div class="_info_row"><span>Learning:</span><span id="_learn">any</span></div> </div> <div id="_progress_card" class="_card"> <h3>Progress</h3> <div class="_info_row"><span>Streak:</span><span id="_streak">0</span></div> <div class="_info_row"><span>Gem:</span><span id="_gem">0</span></div> <div class="_info_row"><span>XP:</span><span id="_xp">0</span></div> </div> <div id="_controls_card" class="_card"> <h3>Controls</h3> <select id="_select_option"></select> <div class="_thread_row"> <label for="_thread_count">Threads (1+, high values may still hit limits):</label> <input type="number" id="_thread_count" min="1" value="1"> </div> <div id="_action_row"> <button id="_start_btn">Start Farming</button> <button id="_stop_btn" hidden>Stop Farming</button> </div> </div> <div id="_notify_card" class="_card"> <p id="_notify">Welcome! Select an option and start farming. Supports English learning only. For best performance, use a blank page. User-agent rotation and adaptive delays enabled to handle rate limits better. Join our Discord for support!</p> <a id="_blank_page_link" href="https://www.duolingo.com/errors/404.html">Go to Blank Page</a> </div> </div> <div id="_footer"> <a href="https://twisk.fun/SuperLinkNexus" target="_blank">Superlinks Bot</a> <a href="https://twisk.fun/" target="_blank">Our Website</a> <a href="https://discord.gg/m3EV55SpYw" target="_blank">Discord</a> <span>Version <span id="_version">1.0</span></span> </div> </div> <div id="_floating_btn">🚀</div> <div id="_popup_overlay"></div> <div id="_popup_container"> <div id="_popup_header"> <span class="_popup_label">Join TWISK DEVELOPMENT!</span> <button id="_popup_close_btn">✕</button> </div> <div id="_popup_body"> <div class="_popup_content"> <img src="https://discord.com/assets/2c21aeda9de2b3c44e2f.jpg" alt="Discord Logo" style="width: 80px; height: auto; margin-bottom: 16px;"> <p>Connect with TWISK DEVELOPMENT 🌐✅ on Discord! Join our server for updates, support, and a vibrant community.</p> <a href="${DISCORD_LINK}" target="_blank" id="_join_discord_btn">Join Now!</a> </div> </div> </div> `; const style = document.createElement("style"); style.innerHTML = ` body { font-family: 'Roboto', sans-serif; } #_container { width: 90vw; max-width: 600px; min-height: 50vh; background: #ffffff; color: #333333; border-radius: 16px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1), 0 0 20px rgba(33, 150, 243, 0.3); display: flex; flex-direction: column; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; overflow: hidden; } #_header { height: 60px; background: #2196f3; color: #ffffff; display: flex; align-items: center; justify-content: center; font-size: 1.5em; font-weight: 700; position: relative; border-top-left-radius: 16px; border-top-right-radius: 16px; } #_close_btn { position: absolute; right: 16px; background: none; border: none; color: #ffffff; font-size: 1.2em; cursor: pointer; } #_body { padding: 16px; flex: 1; display: flex; flex-direction: column; gap: 16px; overflow-y: auto; } #_footer { height: 50px; background: #f5f5f5; display: flex; align-items: center; justify-content: space-around; border-bottom-left-radius: 16px; border-bottom-right-radius: 16px; font-size: 0.9em; } #_footer a, #_footer span { color: #2196f3; text-decoration: none; font-weight: 500; } ._card { background: #f9f9f9; border-radius: 12px; padding: 16px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } ._card h3 { margin: 0 0 12px 0; font-size: 1.1em; color: #2196f3; font-weight: 500; } ._info_row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 1em; } ._info_row span:first-child { color: #666666; } ._info_row span:last-child { font-weight: 500; } #_select_option { width: 100%; padding: 12px; border: 1px solid #dddddd; border-radius: 8px; background: #ffffff; color: #333333; font-size: 1em; margin-bottom: 12px; } ._thread_row { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; } ._thread_row label { font-size: 0.9em; color: #666666; } #_thread_count { width: 60px; padding: 8px; border: 1px solid #dddddd; border-radius: 8px; text-align: center; } #_action_row { display: flex; gap: 12px; } #_start_btn, #_stop_btn { flex: 1; padding: 12px; border: none; border-radius: 8px; font-size: 1em; font-weight: 500; cursor: pointer; transition: all 0.3s; } #_start_btn { background: #2196f3; color: #ffffff; box-shadow: 0 2px 8px rgba(33, 150, 243, 0.3); } #_start_btn:hover { box-shadow: 0 4px 12px rgba(33, 150, 243, 0.5); } #_stop_btn { background: #f44336; color: #ffffff; box-shadow: 0 2px 8px rgba(244, 67, 54, 0.3); } #_stop_btn:hover { box-shadow: 0 4px 12px rgba(244, 67, 54, 0.5); } ._disable_btn { background: #dddddd !important; cursor: not-allowed !important; box-shadow: none !important; } #_notify_card { background: #e3f2fd; color: #1976d2; font-size: 0.9em; } #_blank_page_link { color: #2196f3; text-decoration: none; font-weight: 500; display: block; margin-top: 8px; } #_overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.5); z-index: 9998; } #_floating_btn { position: fixed; bottom: 5%; right: 5%; width: 56px; height: 56px; background: #2196f3; color: #ffffff; border-radius: 50%; box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4), 0 0 20px rgba(33, 150, 243, 0.6); z-index: 10000; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 1.5em; transition: all 0.3s; } #_floating_btn:hover { box-shadow: 0 6px 16px rgba(33, 150, 243, 0.6), 0 0 30px rgba(33, 150, 243, 0.8); } #_popup_container { width: 90vw; max-width: 400px; background: #ffffff; color: #333333; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2), 0 0 20px rgba(33, 150, 243, 0.3); display: flex; flex-direction: column; position: fixed; top: 30%; left: 70%; transform: translate(-50%, -50%); z-index: 10001; overflow: hidden; } #_popup_header { height: 50px; background: #2196f3; color: #ffffff; display: flex; align-items: center; justify-content: center; font-size: 1.2em; font-weight: 700; position: relative; border-top-left-radius: 12px; border-top-right-radius: 12px; } #_popup_close_btn { position: absolute; right: 12px; background: none; border: none; color: #ffffff; font-size: 1em; cursor: pointer; } #_popup_body { padding: 16px; text-align: center; } ._popup_content p { margin: 0 0 16px 0; font-size: 1em; color: #333333; } #_join_discord_btn { display: inline-block; padding: 10px 20px; background: #5865f2; color: #ffffff; border-radius: 8px; text-decoration: none; font-weight: 500; transition: all 0.3s; } #_join_discord_btn:hover { box-shadow: 0 4px 12px rgba(88, 101, 242, 0.5); } #_popup_overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.3); z-index: 10000; display: none; } .hidden { display: none; } `; document.head.appendChild(style); const container = document.createElement("div"); container.innerHTML = containerHTML; document.body.appendChild(container); document.getElementById("_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 setPopupVisible = (visible) => { const popupContainer = document.getElementById("_popup_container"); const popupOverlay = document.getElementById("_popup_overlay"); popupContainer.style.display = visible ? "flex" : "none"; popupOverlay.style.display = visible ? "block" : "none"; }; const addEventFloatingBtn = () => { const floatingBtn = document.getElementById("_floating_btn"); floatingBtn.addEventListener("click", toggleInterface); }; const addEventCloseBtn = () => { const closeBtn = document.getElementById("_close_btn"); closeBtn.addEventListener("click", () => setInterfaceVisible(false)); }; const addEventPopupCloseBtn = () => { const popupCloseBtn = document.getElementById("_popup_close_btn"); popupCloseBtn.addEventListener("click", () => { setPopupVisible(false); setTimeout(() => setPopupVisible(true), REAPPEAR_DELAY); }); }; const addEventStartBtn = () => { const startBtn = document.getElementById("_start_btn"); const stopBtn = document.getElementById("_stop_btn"); const select = document.getElementById("_select_option"); const threadInput = document.getElementById("_thread_count"); startBtn.addEventListener("click", async () => { isRunning = true; startBtn.hidden = true; stopBtn.hidden = false; select.disabled = true; threadInput.disabled = true; threadCount = Math.max(parseInt(threadInput.value) || 1, 1); rateLimitDelay = BASE_DELAY; updateNotify(`Starting with ${threadCount} threads. User-agent rotation and adaptive delays activated.`); 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); }); }; const addEventStopBtn = () => { const startBtn = document.getElementById("_start_btn"); const stopBtn = document.getElementById("_stop_btn"); const select = document.getElementById("_select_option"); const threadInput = document.getElementById("_thread_count"); stopBtn.addEventListener("click", () => { isRunning = false; requestQueue = []; activeRequests = 0; rateLimitDelay = BASE_DELAY; stopBtn.hidden = true; startBtn.hidden = false; select.disabled = false; threadInput.disabled = false; updateNotify("Farming stopped. Ready to start again."); }); }; const addEventVersionLink = () => { const versionLink = document.getElementById("_version"); versionLink.addEventListener("click", () => { prompt("Your JWT token: ", jwt); }); }; const addEventListeners = () => { addEventFloatingBtn(); addEventCloseBtn(); addEventPopupCloseBtn(); addEventStartBtn(); addEventStopBtn(); addEventVersionLink(); }; const populateOptions = () => { const select = document.getElementById("_select_option"); select.innerHTML = ""; const fromLang = userInfo?.fromLanguage || "ru"; const learnLang = userInfo?.learningLanguage || "en"; 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 || 0); 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 = (message = "") => { const startBtn = document.getElementById("_start_btn"); const stopBtn = document.getElementById("_stop_btn"); const select = document.getElementById("_select_option"); const threadInput = document.getElementById("_thread_count"); startBtn.disabled = true; stopBtn.disabled = true; select.disabled = true; threadInput.disabled = true; if (message) updateNotify(message); }; const resetStartStopBtn = () => { isRunning = false; requestQueue = []; activeRequests = 0; rateLimitDelay = BASE_DELAY; const startBtn = document.getElementById("_start_btn"); const stopBtn = document.getElementById("_stop_btn"); const select = document.getElementById("_select_option"); const threadInput = document.getElementById("_thread_count"); stopBtn.hidden = true; startBtn.hidden = false; select.disabled = false; threadInput.disabled = false; }; 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 getRandomUserAgent = () => { return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)]; }; const formatHeaders = (jwt) => ({ "Content-Type": "application/json", Authorization: "Bearer " + jwt, "User-Agent": getRandomUserAgent(), }); 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 adjustRateLimitDelay = (status) => { if (status === 429) { rateLimitDelay = Math.min(rateLimitDelay * 2, 15000); updateNotify(`Rate limit hit! Increasing delay to ${rateLimitDelay}ms.`); } else { rateLimitDelay = Math.max(BASE_DELAY, rateLimitDelay * 0.75); updateNotify(`Successful request. Current delay: ${rateLimitDelay}ms.`); } }; const processQueue = async () => { while (requestQueue.length > 0 && activeRequests < MAX_CONCURRENT_REQUESTS && isRunning) { const { task, resolve, reject } = requestQueue.shift(); activeRequests++; try { const result = await task(); resolve(result); await delay(rateLimitDelay + Math.random() * 200); } catch (error) { reject(error); } finally { activeRequests--; processQueue(); } } }; const queueRequest = (task) => { return new Promise((resolve, reject) => { requestQueue.push({ task, resolve, reject }); processQueue(); }); }; 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, }); adjustRateLimitDelay(res.status); if (res.status === 429) { await delay(rateLimitDelay); return sendRequest({ url, payload, headers, method }); } if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } return res; } catch (error) { adjustRateLimitDelay(429); throw error; } }; const sendRequestWithDefaultHeaders = async ({ url, payload, headers = {}, method = "GET", }) => { defaultHeaders = formatHeaders(jwt); 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 queueRequest(() => sendRequestWithDefaultHeaders({ url: patchUrl, payload: patchData, method: "PATCH", }) ); }; const farmGemLoop = async () => { const gemFarmed = 30; while (isRunning) { try { const promises = Array.from({ length: threadCount }, () => farmGemOnce()); await Promise.all(promises); userInfo = { ...userInfo, gems: userInfo.gems + gemFarmed * threadCount }; updateNotify(`You got ${gemFarmed * threadCount} gems with ${threadCount} threads!!!`); updateUserInfo(); await delay(rateLimitDelay); } catch (error) { updateNotify( `Error ${error.message}! Please record screen and report in telegram group!` ); await delay(rateLimitDelay + 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 queueRequest(() => sendRequestWithDefaultHeaders({ url: completeUrl, payload: payload, headers: defaultHeaders, method: "POST", }) ); }; const farmXpLoop = async (amount) => { while (isRunning) { try { const promises = Array.from({ length: threadCount }, () => farmXpOnce(amount)); const responses = await Promise.all(promises); let totalXpFarmed = 0; for (const response of responses) { if (response.status === 500) { updateNotify( "Make sure you are on English course (learning lang must be EN)!" ); await delay(rateLimitDelay + 1000); continue; } const responseData = await response.json(); totalXpFarmed += responseData?.awardedXp || 0; } userInfo = { ...userInfo, totalXp: userInfo.totalXp + totalXpFarmed }; updateNotify(`You got ${totalXpFarmed} XP with ${threadCount} threads!!!`); updateUserInfo(); await delay(rateLimitDelay); } catch (error) { updateNotify( `Error ${error.message}! Please record screen and report in telegram group!` ); await delay(rateLimitDelay + 1000); } } }; const farmSessionOnce = async (startTime, endTime) => { 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 queueRequest(() => sendRequestWithDefaultHeaders({ url: "https://www.duolingo.com/2017-06-30/sessions", payload: sessionPayload, method: "POST", }) ); const sessionData = await sessionRes.json(); const updateSessionPayload = { ...sessionData, heartsLeft: 0, startTime: startTime, enableBonusPoints: false, endTime: endTime, failed: false, maxInLessonStreak: 9, shouldLearnThings: true, }; return await queueRequest(() => sendRequestWithDefaultHeaders({ url: `https://www.duolingo.com/2017-06-30/sessions/${sessionData.id}`, payload: updateSessionPayload, method: "PUT", }) ); }; const repairStreak = async () => { blockStopBtn(); try { if (!userInfo.streakData.currentStreak) { updateNotify("You have no streak! Abort!"); resetStartStopBtn(); return; } 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; if (expectedStreak > userInfo.streak) { updateNotify("Your streak is frozen somewhere! Repairing..."); await delay(rateLimitDelay); let currentTimestamp = Math.floor(Date.now() / 1000); const repairPromises = []; for (let i = 0; i < expectedStreak; i++) { repairPromises.push(farmSessionOnce(currentTimestamp, currentTimestamp + 60)); currentTimestamp -= 86400; } await Promise.all(repairPromises.map((p, i) => p.then(() => updateNotify(`Repaired streak (${i + 1}/${expectedStreak})...`)))); 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 : startFarmStreakTimestamp; 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(rateLimitDelay); } else { updateNotify("Failed to farm streak session, trying again..."); await delay(rateLimitDelay + 2000); continue; } } catch (error) { updateNotify(`Error in farmStreak: ${error?.message || error}`); await delay(rateLimitDelay + 2000); continue; } } }; const farmSelectedOption = async (option) => { const { type, value, amount } = option; switch (type) { case "gem": await farmGemLoop(); break; case "xp": await farmXpLoop(amount); break; case "streak": if (value === "repair") { await repairStreak(); } else if (value === "farm") { await farmStreakLoop(); } break; } resetStartStopBtn(); }; 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(); }; (async () => { initInterface(); setInterfaceVisible(false); setPopupVisible(true); addEventListeners(); await initVariables(); updateUserInfo(); })();