您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fastést farming tool for Duolingo
// ==UserScript== // @name Duolingo-SM & Farming Tool // @namespace http://tampermonkey.net/ // @version 9.9.9 // @description Fastést farming tool for Duolingo // @author DUOS // @match https://www.duolingo.com/* // @grant none // @license MIT // @run-at document-end // ==/UserScript== (() => { 'use strict'; const CONFIG = { GEM_DELAY: 250, XP_DELAY: 2000, GEM_BATCH_SIZE: 1, XP_AMOUNT: 499 }; let jwt, sub, userInfo, headers; let activeTask = null; let isMinimized = false; let stats = { gems: 0, xp: 0, streak: 0 }; const utils = { getJWT: () => { const match = document.cookie.match(/jwt_token=([^;]+)/); return match ? match[1] : null; }, decodeJWT: (token) => { try { const payload = token.split('.')[1]; const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/')); return JSON.parse(decodeURIComponent(escape(decoded))); } catch (e) { return null; } }, formatHeaders: (jwt) => ({ 'Content-Type': 'application/json', 'Authorization': `Bearer ${jwt}`, 'User-Agent': navigator.userAgent }), delay: ms => new Promise(r => setTimeout(r, ms)), request: async (url, options = {}) => { const response = await fetch(url, { ...options, headers: { ...headers, ...options.headers } }); return response; } }; const api = { getUserInfo: async () => { const url = `https://www.duolingo.com/2017-06-30/users/${sub}?fields=id,username,fromLanguage,learningLanguage,streak,totalXp,gems,streakData`; const res = await utils.request(url); return res.json(); }, farmGems: async () => { const rewardId = "SKILL_COMPLETION_BALANCED-dd2495f4_d44e_3fc3_8ac8_94e2191506f0-2-GEMS"; const url = `https://www.duolingo.com/2017-06-30/users/${sub}/rewards/${rewardId}`; const data = { consumed: true, learningLanguage: userInfo.learningLanguage, fromLanguage: userInfo.fromLanguage }; return utils.request(url, { method: 'PATCH', body: JSON.stringify(data) }); }, farmXP: async () => { const url = `https://stories.duolingo.com/api2/stories/fr-en-le-passeport/complete`; const data = { awardXp: true, completedBonusChallenge: true, fromLanguage: "en", hasXpBoost: false, illustrationFormat: "svg", isFeaturedStoryInPracticeHub: true, isLegendaryMode: true, isV2Redo: false, isV2Story: false, learningLanguage: "fr", masterVersion: true, maxScore: 0, score: 0, happyHourBonusXp: 469, startTime: Math.floor(Date.now() / 1000), endTime: Math.floor(Date.now() / 1000), }; return utils.request(url, { method: 'POST', body: JSON.stringify(data) }); }, farmSessionOnce: async (startTime, endTime) => { try { const challengeTypes = ["assist", "select", "translate", "match", "listen"]; const sessionPayload = { challengeTypes: challengeTypes, fromLanguage: userInfo.fromLanguage, isFinalLevel: false, isV2: true, juicy: true, learningLanguage: userInfo.learningLanguage, smartTipsVersion: 2, type: "GLOBAL_PRACTICE", }; const sessionRes = await utils.request("https://www.duolingo.com/2017-06-30/sessions", { method: 'POST', body: JSON.stringify(sessionPayload) }); if (!sessionRes?.ok) return null; const sessionData = await sessionRes.json(); if (!sessionData?.id) return null; const updatePayload = { ...sessionData, heartsLeft: 0, startTime: startTime, enableBonusPoints: false, endTime: endTime, failed: false, maxInLessonStreak: Math.floor(Math.random() * 10 + 5), shouldLearnThings: true, }; const updateRes = await utils.request(`https://www.duolingo.com/2017-06-30/sessions/${sessionData.id}`, { method: 'PUT', body: JSON.stringify(updatePayload) }); return updateRes?.ok ? await updateRes.json() : null; } catch (error) { console.error('Session farm error:', error); return null; } } }; const farming = { async gems() { while (activeTask === 'gems') { const promises = Array(CONFIG.GEM_BATCH_SIZE).fill().map(() => api.farmGems()); const results = await Promise.allSettled(promises); const successful = results.filter(r => r.status === 'fulfilled' && r.value?.ok).length; if (successful > 0) { const earned = successful * 30; userInfo.gems += earned; stats.gems += earned; ui.updateStats(); } await utils.delay(CONFIG.GEM_DELAY); } }, async xp() { while (activeTask === 'xp') { const res = await api.farmXP(); if (res?.ok) { userInfo.totalXp += CONFIG.XP_AMOUNT; stats.xp += CONFIG.XP_AMOUNT; ui.updateStats(); } await utils.delay(CONFIG.XP_DELAY); } }, async streak() { const hasStreak = userInfo.streakData?.currentStreak; const startDate = hasStreak ? userInfo.streakData.currentStreak.startDate : new Date(); let currentTimestamp = Math.floor(new Date(startDate).getTime() / 1000) - 86400; const baseDelay = 40; while (activeTask === 'streak') { try { const sessionRes = await api.farmSessionOnce(currentTimestamp, currentTimestamp + 300); if (sessionRes) { currentTimestamp -= 86400; userInfo.streak += 1; stats.streak += 1; ui.updateStats(); await utils.delay(baseDelay); } else { await utils.delay(baseDelay * 2); } } catch (error) { console.error('Streak farming error:', error); await utils.delay(baseDelay * 3); } } }, stop() { activeTask = null; ui.updateFarmingButtons(); } }; const ui = { create() { const container = document.createElement('div'); container.id = 'duosm-tool'; container.innerHTML = ` <div class="ds-header"> <span class="ds-logo">🦉 DuoSM</span> <div class="ds-controls"> <button class="ds-btn-minimize">—</button> <button class="ds-btn-close">×</button> </div> </div> <div class="ds-content"> <div class="ds-stats"> <div class="ds-stat" data-type="streak"> <span class="ds-label">Streak</span> <span class="ds-value" id="streak-val">0</span> </div> <div class="ds-stat" data-type="gems"> <span class="ds-label">Gems</span> <span class="ds-value" id="gems-val">0</span> </div> <div class="ds-stat" data-type="xp"> <span class="ds-label">XP</span> <span class="ds-value" id="xp-val">0</span> </div> </div> <div class="ds-footer">Made by DUOS</div> </div> <div class="ds-minimized" style="display: none;"> <span>🦉</span> </div> `; const styles = ` #duosm-tool { position: fixed; top: 20px; right: 20px; width: 220px; background: linear-gradient(145deg, #1a1d29, #161926); border: 1px solid #2d3748; border-radius: 16px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; box-shadow: 0 20px 40px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.05); backdrop-filter: blur(16px); z-index: 999999; overflow: hidden; transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } #duosm-tool.minimized { width: 60px; height: 60px; bottom: 20px; top: auto; border-radius: 50%; background: linear-gradient(145deg, #4299e1, #3182ce); cursor: pointer; } .ds-header { background: linear-gradient(135deg, #2d3748, #1a202c); padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #2d3748; cursor: move; } .ds-logo { font-weight: 700; font-size: 14px; color: #e2e8f0; letter-spacing: -0.5px; } .ds-controls { display: flex; gap: 4px; } .ds-controls button { width: 20px; height: 20px; border: none; background: rgba(255,255,255,0.1); color: #cbd5e0; border-radius: 6px; cursor: pointer; font-size: 12px; transition: all 0.2s; } .ds-controls button:hover { background: rgba(239,68,68,0.8); transform: scale(1.1); } .ds-content { padding: 16px; } .ds-stats { display: grid; gap: 8px; } .ds-stat { background: linear-gradient(135deg, rgba(74,85,104,0.3), rgba(45,55,72,0.5)); border: 1px solid rgba(74,85,104,0.3); border-radius: 12px; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: all 0.3s cubic-bezier(0.4,0,0.2,1); position: relative; overflow: hidden; } .ds-stat::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent); transition: left 0.6s; } .ds-stat:hover::before { left: 100%; } .ds-stat:hover { transform: translateY(-2px) scale(1.02); border-color: rgba(66,153,225,0.6); box-shadow: 0 8px 25px rgba(0,0,0,0.3); } .ds-stat.active { background: linear-gradient(135deg, rgba(72,187,120,0.2), rgba(56,178,172,0.2)); border-color: #48bb78; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(72,187,120,0.4); } 50% { box-shadow: 0 0 0 8px rgba(72,187,120,0); } } .ds-label { font-size: 11px; color: #a0aec0; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; } .ds-value { font-size: 18px; font-weight: 700; color: #e2e8f0; text-shadow: 0 2px 4px rgba(0,0,0,0.3); } .ds-footer { text-align: center; margin-top: 12px; font-size: 10px; color: #718096; font-weight: 500; opacity: 0.7; } .ds-minimized { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 24px; cursor: pointer; } #duosm-tool:not(.minimized) .ds-minimized { display: none !important; } #duosm-tool.minimized .ds-header, #duosm-tool.minimized .ds-content { display: none; } .ds-stat[data-type="streak"]:hover { border-color: rgba(245,101,101,0.6); } .ds-stat[data-type="gems"]:hover { border-color: rgba(56,178,172,0.6); } .ds-stat[data-type="xp"]:hover { border-color: rgba(237,137,54,0.6); } .ds-stat[data-type="streak"].active { background: linear-gradient(135deg, rgba(245,101,101,0.2), rgba(229,62,62,0.2)); border-color: #f56565; } .ds-stat[data-type="gems"].active { background: linear-gradient(135deg, rgba(56,178,172,0.2), rgba(49,151,149,0.2)); border-color: #38b2ac; } .ds-stat[data-type="xp"].active { background: linear-gradient(135deg, rgba(237,137,54,0.2), rgba(221,107,32,0.2)); border-color: #ed8936; } `; document.head.insertAdjacentHTML('beforeend', `<style>${styles}</style>`); document.body.appendChild(container); this.setupEvents(container); this.makeDraggable(container); }, setupEvents(container) { container.querySelector('.ds-btn-minimize').onclick = () => this.toggleMinimize(); container.querySelector('.ds-btn-close').onclick = () => container.remove(); container.querySelector('.ds-minimized').onclick = () => this.toggleMinimize(); container.querySelectorAll('.ds-stat').forEach(stat => { stat.onclick = () => { const type = stat.dataset.type; if (activeTask === type) { farming.stop(); } else { farming.stop(); activeTask = type; farming[type](); this.updateFarmingButtons(); } }; }); }, makeDraggable(element) { let pos = { x: 0, y: 0, startX: 0, startY: 0 }; const header = element.querySelector('.ds-header'); const dragStart = (e) => { pos.startX = e.clientX; pos.startY = e.clientY; document.onmousemove = drag; document.onmouseup = dragEnd; }; const drag = (e) => { pos.x = pos.startX - e.clientX; pos.y = pos.startY - e.clientY; pos.startX = e.clientX; pos.startY = e.clientY; element.style.top = (element.offsetTop - pos.y) + 'px'; element.style.left = (element.offsetLeft - pos.x) + 'px'; element.style.right = 'auto'; element.style.bottom = 'auto'; }; const dragEnd = () => { document.onmouseup = null; document.onmousemove = null; }; header.onmousedown = dragStart; }, toggleMinimize() { isMinimized = !isMinimized; const tool = document.getElementById('duosm-tool'); tool.classList.toggle('minimized', isMinimized); }, updateStats() { if (!userInfo) return; document.getElementById('streak-val').textContent = userInfo.streak || 0; document.getElementById('gems-val').textContent = userInfo.gems || 0; document.getElementById('xp-val').textContent = (userInfo.totalXp || 0).toLocaleString(); }, updateFarmingButtons() { document.querySelectorAll('.ds-stat').forEach(stat => { stat.classList.toggle('active', activeTask === stat.dataset.type); }); } }; const init = async () => { if (!location.hostname.includes('duolingo.com')) return; jwt = utils.getJWT(); if (!jwt) return; const decoded = utils.decodeJWT(jwt); if (!decoded) return; sub = decoded.sub; headers = utils.formatHeaders(jwt); try { userInfo = await api.getUserInfo(); ui.create(); ui.updateStats(); } catch (error) { console.error('DuoSM init failed:', error); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { setTimeout(init, 1000); } })();