您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
免费且易用的 Duolingo 农场工具!
// ==UserScript== // @name DuoHacker // @name:zh-CN DuoHacker — 免费 Duolingo 农场工具 // @name:ja DuoHacker — 無料の Duolingo ファーミングツール // @name:es DuoHacker — Herramienta gratuita para farmear en Duolingo // @name:ru DuoHacker — Бесплатный инструмент для фарминга Duolingo // @name:pt-BR DuoHacker — Melhor ferramenta gratuita para farmar no Duolingo // @description Best free-to-use Duolingo farming tool! // @description:zh-CN 免费且易用的 Duolingo 农场工具! // @description:ja 無料で使える Duolingo のファーミングツールです! // @description:es ¡La mejor herramienta gratuita para farmear en Duolingo! // @description:ru Лучший бесплатный инструмент для фарминга в Duolingo! // @description:pt-BR A melhor ferramenta gratuita para farmar no Duolingo! // @namespace https://irylisvps.vercel.app // @version 1.0.1 // @author DuoHacker Community // @match https://*.duolingo.com/* // @icon https://github.com/pillowslua/images/blob/main/Hacklingo.png?raw=true // @grant none // @license MIT // ==/UserScript== const VERSION = "1.0"; const DELAY = 300; const MAX_THREADS = 5; var jwt, defaultHeaders, userInfo, sub; let isRunning = false; let currentTheme = localStorage.getItem('duofarmer_theme') || 'dark'; let hasJoined = localStorage.getItem('duofarmer_joined') === 'true'; let activeThreads = []; let totalEarned = { xp: 0, gems: 0, streak: 0 }; let farmingStats = { sessions: 0, errors: 0, startTime: null }; const initInterface = () => { const containerHTML = ` <div id="_backdrop"></div> <div id="_container" class="theme-${currentTheme}"> <!-- Header with TwiskGPT inspired design --> <div id="_header"> <div class="_header_content"> <div class="_logo_section"> <div class="_logo"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.9-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg> </div> <div class="_title_section"> <h1>DuoHacker PRO</h1> <span class="_subtitle">BEST Duolingo Farming Tool</span> </div> </div> <div class="_header_actions"> <button id="_minimize_btn" class="_header_btn" title="Minimize">-</button> <button id="_settings_btn" class="_header_btn" title="Settings">⚙</button> <button id="_close_btn" class="_header_btn _close" title="Close">x</button> </div> </div> </div> <!-- Join Discord section --> <div id="_join_section" class="_section ${hasJoined ? '_collapsed' : ''}"> <div class="_section_header" onclick="toggleSection('_join_section')"> <div class="_section_title"> <span class="_section_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="12" x="2" y="6" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12c.5-1 3-3 6-3s5.5 2 6 3"/><path d="M6 12c.5 1 3 3 6 3s5.5-2 6-3"/></svg> </span> <span>Join Our Community</span> </div> <div class="_section_status"> <span id="_join_status" class="${hasJoined ? '_status_success' : '_status_error'}"> ${hasJoined ? '✓ Joined' : '⚠ Required'} </span> <span class="_chevron ${hasJoined ? '_rotated' : ''}">▼</span> </div> </div> <div class="_section_content" ${hasJoined ? 'style="display:none"' : ''}> <div class="_input_container"> <button id="_join_btn" class="_primary_btn"> <span class="_btn_content"> <span class="_btn_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg> </span> <span class="_btn_text">Join Discord</span> </span> </button> </div> <div class="_help_text"> Join our Discord server for updates and support! </div> </div> </div> <!-- Main content (hidden until joined) --> <div id="_main_content" ${hasJoined ? '' : 'style="display:none"' }> <!-- User info section --> <div id="_user_section" class="_section"> <div class="_section_header"> <div class="_section_title"> <span class="_section_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> </span> <span>Account Overview</span> </div> <button id="_refresh_btn" class="_icon_btn" title="Refresh data">↻</button> </div> <div class="_section_content"> <div class="_stats_grid"> <div class="_stat_card"> <div class="_stat_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> </div> <div class="_stat_content"> <div class="_stat_label">Username</div> <div class="_stat_value" id="_username">Loading...</div> </div> </div> <div class="_stat_card"> <div class="_stat_icon">🌎</div> <div class="_stat_content"> <div class="_stat_label">Languages</div> <div class="_stat_value"> <span id="_from_lang">--</span> -> <span id="_learning_lang">--</span> </div> </div> </div> <div class="_stat_card"> <div class="_stat_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><path d="m10 17 5-5-5-5"/></svg> </div> <div class="_stat_content"> <div class="_stat_label">Streak</div> <div class="_stat_value" id="_current_streak">0</div> </div> </div> <div class="_stat_card"> <div class="_stat_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg> </div> <div class="_stat_content"> <div class="_stat_label">Gems</div> <div class="_stat_value" id="_current_gems">0</div> </div> </div> <div class="_stat_card"> <div class="_stat_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" x2="12" y1="2" y2="22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg> </div> <div class="_stat_content"> <div class="_stat_label">Total XP</div> <div class="_stat_value" id="_current_xp">0</div> </div> </div> </div> </div> </div> <!-- Farming control section --> <div id="_farming_section" class="_section"> <div class="_section_header"> <div class="_section_title"> <span class="_section_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 22 16 17"/><path d="M14 16.5V5.5"/><path d="M15 18.5c-1.4 1-3.1 1.5-4.7 1-1.3.4-2.6-.2-3.5-1.1-.8-.8-1.3-2-1-3.3.6-2.5 4-3.8 3.8-6.1-.3-1.6-1.4-2.8-2.9-3.3"/><path d="M20.97 21.5c.2-1.1-.3-2.5-1-3.5-.7-1-2-1.6-3.5-1.1-2.7.5-3.1 2.2-3.7 4.2-.3 1.3.2 2.6 1.1 3.5.9.9 3.7 1.3 5.1.4"/><path d="M3 5.5 5 17"/><path d="M18.5 16H20c.3 0 .5-.2.5-.5V6.3c0-.3-.2-.5-.5-.5h-1.2c-.3 0-.5.2-.5.5v.3c0 .3.2.5.5.5h.5v8"/></svg> </span> <span>Farming Controls</span> </div> <div class="_thread_indicator"> <span class="_thread_count">Threads: <strong id="_active_threads">0</strong>/${MAX_THREADS}</span> </div> </div> <div class="_section_content"> <div class="_farming_controls"> <div class="_control_row"> <select id="_farming_mode" class="_modern_select"> <option value="xp_multi">⚡ Multi-threaded XP Farming</option> <option value="gem_multi">💎 Multi-threaded Gem Farming</option> <option value="streak_repair">🔧 Streak Repair (Single)</option> <option value="streak_farm">🔥 Streak Farming (Single)</option> <option value="mixed_farming">🎯 Mixed Farming (All types)</option> </select> <div class="_thread_control"> <label>Threads:</label> <input type="range" id="_thread_slider" min="1" max="${MAX_THREADS}" value="3" class="_slider"> <span id="_thread_display">3</span> </div> </div> <div class="_control_row"> <button id="_start_farming" class="_primary_btn _large"> <span class="_btn_content"> <span class="_btn_icon">▶</span> <span class="_btn_text">Start Farming</span> </span> </button> <button id="_stop_farming" class="_danger_btn _large" style="display:none"> <span class="_btn_content"> <span class="_btn_icon">⏹</span> <span class="_btn_text">Stop Farming</span> </span> </button> <button id="_pause_farming" class="_secondary_btn" style="display:none"> <span class="_btn_content"> <span class="_btn_icon">⏨</span> <span class="_btn_text">Pause</span> </span> </button> </div> </div> </div> </div> <!-- Statistics section --> <div id="_stats_section" class="_section"> <div class="_section_header"> <div class="_section_title"> <span class="_section_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M18.7 8l-5.1 5.2-2.8-2.9L7 14.1"/></svg> </span> <span>Live Statistics</span> </div> <button id="_reset_stats" class="_icon_btn" title="Reset statistics">↻</button> </div> <div class="_section_content"> <div class="_stats_grid _live_stats"> <div class="_stat_card _earned"> <div class="_stat_icon">⚡</div> <div class="_stat_content"> <div class="_stat_label">XP Earned</div> <div class="_stat_value" id="_earned_xp">0</div> </div> </div> <div class="_stat_card _earned"> <div class="_stat_icon">💎</div> <div class="_stat_content"> <div class="_stat_label">Gems Earned</div> <div class="_stat_value" id="_earned_gems">0</div> </div> </div> <div class="_stat_card _earned"> <div class="_stat_icon">🔥</div> <div class="_stat_content"> <div class="_stat_label">Streak Gained</div> <div class="_stat_value" id="_earned_streak">0</div> </div> </div> <div class="_stat_card"> <div class="_stat_icon">🎯</div> <div class="_stat_content"> <div class="_stat_label">Success Rate</div> <div class="_stat_value" id="_success_rate">100%</div> </div> </div> </div> <div class="_progress_section"> <div class="_progress_header"> <span>Farming Progress</span> <span id="_farming_time">00:00:00</span> </div> <div class="_thread_progress"> <div id="_thread_bars"></div> </div> </div> </div> </div> <!-- Console output --> <div id="_console_section" class="_section"> <div class="_section_header"> <div class="_section_title"> <span class="_section_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/></svg> </span> <span>Console Output</span> </div> <div class="_console_controls"> <button id="_clear_console" class="_icon_btn" title="Clear console">🗑</button> <button id="_auto_scroll" class="_icon_btn _active" title="Auto scroll">📜</button> </div> </div> <div class="_section_content"> <div id="_console_output" class="_console"> <div class="_console_line _info"> <span class="_timestamp">[${new Date().toLocaleTimeString()}]</span> <span class="_message">DuoNexus v${VERSION} initialized</span> </div> <div class="_console_line _info"> <span class="_timestamp">[${new Date().toLocaleTimeString()}]</span> <span class="_message">Multi-threading engine ready</span> </div> </div> </div> </div> </div> <!-- Settings Modal --> <div id="_settings_modal" class="_modal" style="display:none"> <div class="_modal_overlay"></div> <div class="_modal_container"> <div class="_modal_header"> <h2>Settings & Preferences</h2> <button id="_close_modal" class="_icon_btn">x</button> </div> <div class="_modal_content"> <div class="_setting_group"> <h3>🎨 Appearance</h3> <div class="_theme_selector"> <button class="_theme_option ${currentTheme === 'dark' ? '_active' : ''}" data-theme="dark"> <span class="_theme_preview _dark"></span> <span>Dark Mode</span> </button> <button class="_theme_option ${currentTheme === 'light' ? '_active' : ''}" data-theme="light"> <span class="_theme_preview _light"></span> <span>Light Mode</span> </button> </div> </div> <div class="_setting_group"> <h3>⚡ Performance</h3> <div class="_setting_item"> <label>Request Delay (ms)</label> <input type="range" id="_delay_slider" min="100" max="1000" value="${DELAY}" class="_slider"> <span id="_delay_value">${DELAY}ms</span> </div> <div class="_setting_item"> <label>Auto-pause on errors</label> <input type="checkbox" id="_auto_pause" class="_checkbox" checked> </div> </div> <div class="_setting_group"> <h3>🔗 Quick Actions</h3> <div class="_action_buttons"> <a href="https://www.duolingo.com/errors/404.html" target="_blank" class="_action_btn"> 📄 Blank Page </a> <button id="_export_stats" class="_action_btn"> 📊 Export Stats </button> <button id="_backup_settings" class="_action_btn"> 💾 Backup Settings </button> </div> </div> <div class="_setting_group"> <h3>ℹ About</h3> <div class="_about_section"> <p><strong>DuoHacker v${VERSION}</strong></p> <p>Join our community for FREE SUPPORT 24/7 and PRO version</p> <p>Developer: <strong>tw1sk</strong></p> <p>Free-to-use best farming tool!</p> <div class="_social_links"> <a href="https://discord.gg/Gvmd7deFtS" target="_blank" class="_social_btn">💬 Discord</a> <a href="https://irylisvps.vercel.app/" target="_blank" class="_social_btn">📦 Website</a> </div> </div> </div> </div> </div> </div> <!-- Footer --> <div id="_footer"> <div class="_footer_content"> <span>© 2025 DuoHacker by <strong>tw1sk</strong></span> <span class="_version">v${VERSION}</span> </div> </div> </div> <!-- DuoHX GPT --> <div id="_floating_container"> <div id="_floating_btn" class="theme-${currentTheme}" title="Open DuoNexus"> <div class="_btn_ripple"></div> <div class="_btn_icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.9-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg> </div> </div> </div> `; const style = document.createElement("style"); style.innerHTML = ` /* CSS Variables for theming */ :root { --animation-duration: 0.2s; --animation-ease: cubic-bezier(0.4, 0, 0.2, 1); --border-radius-sm: 6px; --border-radius-md: 12px; --border-radius-lg: 20px; --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15); --shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.2); --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-success: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); --gradient-danger: linear-gradient(135deg, #fa709a 0%, #fee140 100%); } /* Dark Theme */ .theme-dark { --bg-primary: #0d1117; --bg-secondary: #161b22; --bg-tertiary: #21262d; --bg-quaternary: #30363d; --text-primary: #f0f6fc; --text-secondary: #8b949e; --text-tertiary: #6e7681; --border-primary: #30363d; --border-secondary: #21262d; --accent-primary: #58a6ff; --accent-secondary: #1f6feb; --success-color: #3fb950; --error-color: #f85149; --warning-color: #d29922; } /* Light Theme */ .theme-light { --bg-primary: #ffffff; --bg-secondary: #f6f8fa; --bg-tertiary: #eaeef2; --bg-quaternary: #d0d7de; --text-primary: #24292f; --text-secondary: #656d76; --text-tertiary: #8c959f; --border-primary: #d0d7de; --border-secondary: #eaeef2; --accent-primary: #0969da; --accent-secondary: #0550ae; --success-color: #1a7f37; --error-color: #cf222e; --warning-color: #9a6700; } /* Reset and base styles */ * { box-sizing: border-box; } /* Container */ #_container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 95vw; max-width: 1200px; max-height: 90vh; background: var(--bg-primary); border: 1px solid var(--border-primary); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-lg); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; color: var(--text-primary); z-index: 9999; display: flex; flex-direction: column; overflow: hidden; animation: containerSlideIn var(--animation-duration) var(--animation-ease); } @keyframes containerSlideIn { from { opacity: 0; transform: translate(-50%, -60%); } to { opacity: 1; transform: translate(-50%, -50%); } } /* Backdrop */ #_backdrop { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px); z-index: 9998; animation: backdropFadeIn var(--animation-duration) var(--animation-ease); } @keyframes backdropFadeIn { from { opacity: 0; } to { opacity: 1; } } /* Header */ #_header { background: var(--bg-secondary); border-bottom: 1px solid var(--border-primary); padding: 16px 20px; } ._header_content { display: flex; justify-content: space-between; align-items: center; } ._logo_section { display: flex; align-items: center; gap: 12px; } ._logo { width: 40px; height: 40px; background: var(--gradient-primary); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; animation: logoSpin 2s ease-in-out infinite; } @keyframes logoSpin { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(180deg); } } ._title_section h1 { margin: 0; font-size: 20px; font-weight: 600; background: var(--gradient-primary); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } ._subtitle { font-size: 12px; color: var(--text-secondary); margin: 0; } ._header_actions { display: flex; gap: 8px; } ._header_btn { width: 32px; height: 32px; border: none; background: transparent; color: var(--text-secondary); border-radius: var(--border-radius-sm); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; transition: all var(--animation-duration) var(--animation-ease); } ._header_btn:hover { background: var(--bg-tertiary); color: var(--text-primary); transform: scale(1.1); } ._header_btn._close:hover { background: var(--error-color); color: white; } /* Sections */ ._section { border-bottom: 1px solid var(--border-secondary); } ._section_header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; cursor: pointer; background: var(--bg-secondary); transition: background var(--animation-duration) var(--animation-ease); } ._section_header:hover { background: var(--bg-tertiary); } ._section_title { display: flex; align-items: center; gap: 12px; font-weight: 600; font-size: 16px; } ._section_icon { font-size: 20px; display: flex; align-items: center; } ._section_icon svg { width: 20px; height: 20px; } ._section_status { display: flex; align-items: center; gap: 8px; } ._status_success { color: var(--success-color); font-weight: 600; } ._status_error { color: var(--error-color); font-weight: 600; } ._chevron { transition: transform var(--animation-duration) var(--animation-ease); color: var(--text-tertiary); } ._chevron._rotated { transform: rotate(180deg); } ._section._collapsed ._section_content { display: none; } ._section_content { padding: 20px; background: var(--bg-primary); } /* Input elements */ ._input_container { display: flex; gap: 12px; margin-bottom: 12px; } ._modern_input, ._modern_select { flex: 1; padding: 12px 16px; background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--border-radius-md); color: var(--text-primary); font-size: 14px; transition: all var(--animation-duration) var(--animation-ease); font-family: inherit; } ._modern_input:focus, ._modern_select:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.1); } ._modern_select { cursor: pointer; } /* Buttons */ ._primary_btn, ._secondary_btn, ._danger_btn { display: inline-flex; align-items: center; justify-content: center; padding: 10px 16px; border: none; border-radius: var(--border-radius-md); font-size: 14px; font-weight: 500; cursor: pointer; transition: all var(--animation-duration) var(--animation-ease); text-decoration: none; white-space: nowrap; } ._primary_btn { background: var(--accent-primary); color: white; } ._primary_btn:hover { background: var(--accent-secondary); transform: translateY(-2px); box-shadow: var(--shadow-md); } ._secondary_btn { background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border-primary); } ._secondary_btn:hover { background: var(--bg-quaternary); transform: translateY(-1px); } ._danger_btn { background: var(--error-color); color: white; } ._danger_btn:hover { background: #d73a49; transform: translateY(-2px); box-shadow: var(--shadow-md); } ._large { padding: 14px 28px; font-size: 16px; font-weight: 600; } ._btn_content { display: flex; align-items: center; gap: 8px; } ._btn_icon { font-size: 16px; } ._icon_btn { width: 36px; height: 36px; border: none; background: transparent; color: var(--text-secondary); border-radius: var(--border-radius-sm); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 16px; transition: all var(--animation-duration) var(--animation-ease); } ._icon_btn:hover { background: var(--bg-tertiary); color: var(--text-primary); transform: scale(1.1); } ._icon_btn._active { background: var(--accent-primary); color: white; } /* Stats grid */ ._stats_grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; } ._stat_card { background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--border-radius-md); padding: 16px; display: flex; align-items: center; gap: 12px; transition: all var(--animation-duration) var(--animation-ease); } ._stat_card:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--accent-primary); } ._stat_card._earned { background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%); border-color: var(--success-color); } ._stat_icon { width: 40px; height: 40px; background: var(--gradient-primary); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; } ._stat_icon svg { width: 24px; height: 24px; } ._stat_content { flex: 1; } ._stat_label { font-size: 12px; color: var(--text-secondary); margin-bottom: 4px; font-weight: 500; } ._stat_value { font-size: 18px; font-weight: 600; color: var(--text-primary); } /* Farming controls */ ._farming_controls { display: flex; flex-direction: column; gap: 16px; } ._control_row { display: flex; align-items: center; gap: 16px; } ._thread_control { display: flex; align-items: center; gap: 8px; font-size: 14px; } ._thread_indicator { background: var(--bg-tertiary); padding: 6px 12px; border-radius: var(--border-radius-sm); font-size: 12px; color: var(--text-secondary); } ._slider { width: 80px; height: 4px; border-radius: 2px; background: var(--bg-tertiary); outline: none; cursor: pointer; } ._slider::-webkit-slider-thumb { appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--accent-primary); cursor: pointer; box-shadow: var(--shadow-sm); } /* Progress bars */ ._progress_section { margin-top: 16px; } ._progress_header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; font-size: 14px; font-weight: 600; } ._thread_progress { background: var(--bg-secondary); border-radius: var(--border-radius-sm); padding: 12px; } ._thread_bar { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 12px; } ._thread_bar:last-child { margin-bottom: 0; } ._thread_id { width: 60px; color: var(--text-secondary); } ._thread_progress_bar { flex: 1; height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden; } ._thread_progress_fill { height: 100%; background: var(--gradient-success); transition: width 0.5s ease; border-radius: 3px; } ._thread_status { width: 80px; text-align: right; color: var(--text-tertiary); font-size: 11px; } /* Console */ ._console { height: 200px; background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--border-radius-md); padding: 12px; overflow-y: auto; font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-size: 13px; line-height: 1.4; } ._console_line { display: flex; gap: 8px; margin-bottom: 2px; align-items: flex-start; } ._timestamp { color: var(--text-tertiary); font-weight: 500; flex-shrink: 0; } ._message { color: var(--text-primary); } ._console_line._success ._message { color: var(--success-color); } ._console_line._error ._message { color: var(--error-color); } ._console_line._warning ._message { color: var(--warning-color); } ._console_line._info ._message { color: var(--accent-primary); } ._console_controls { display: flex; gap: 4px; } /* Settings Modal */ ._modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 10000; display: flex; align-items: center; justify-content: center; } ._modal_overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px); } ._modal_container { position: relative; width: 90%; max-width: 600px; max-height: 80vh; background: var(--bg-primary); border: 1px solid var(--border-primary); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-lg); overflow: hidden; animation: modalSlideIn var(--animation-duration) var(--animation-ease); } @keyframes modalSlideIn { from { opacity: 0; transform: scale(0.9) translateY(-20px); } to { opacity: 1; transform: scale(1) translateY(0); } } ._modal_header { display: flex; justify-content: space-between; align-items: center; padding: 20px; background: var(--bg-secondary); border-bottom: 1px solid var(--border-primary); } ._modal_header h2 { margin: 0; font-size: 18px; font-weight: 600; } ._modal_content { padding: 20px; overflow-y: auto; max-height: calc(80vh - 80px); } ._setting_group { margin-bottom: 24px; } ._setting_group:last-child { margin-bottom: 0; } ._setting_group h3 { margin: 0 0 16px 0; font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; } ._theme_selector { display: flex; gap: 12px; } ._theme_option { flex: 1; padding: 16px; background: var(--bg-secondary); border: 2px solid var(--border-primary); border-radius: var(--border-radius-md); cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 8px; transition: all var(--animation-duration) var(--animation-ease); color: var(--text-primary); } ._theme_option:hover { border-color: var(--accent-primary); transform: translateY(-2px); } ._theme_option._active { border-color: var(--accent-primary); background: var(--accent-primary); color: white; } ._theme_preview { width: 32px; height: 20px; border-radius: 4px; position: relative; } ._theme_preview._dark { background: linear-gradient(to bottom, #0d1117 0%, #161b22 50%, #21262d 100%); } ._theme_preview._light { background: linear-gradient(to bottom, #ffffff 0%, #f6f8fa 50%, #eaeef2 100%); } ._setting_item { display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--border-secondary); } ._setting_item:last-child { border-bottom: none; } ._setting_item label { font-weight: 500; color: var(--text-primary); } ._checkbox { width: 18px; height: 18px; accent-color: var(--accent-primary); } ._action_buttons { display: flex; flex-direction: column; gap: 8px; } ._action_btn { display: flex; align-items: center; gap: 8px; padding: 12px 16px; background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--border-radius-md); color: var(--text-primary); text-decoration: none; font-size: 14px; transition: all var(--animation-duration) var(--animation-ease); cursor: pointer; } ._action_btn:hover { background: var(--bg-tertiary); border-color: var(--accent-primary); } ._about_section { padding: 16px; background: var(--bg-secondary); border-radius: var(--border-radius-md); font-size: 14px; line-height: 1.6; } ._about_section p { margin: 8px 0; } ._social_links { display: flex; gap: 8px; margin-top: 12px; } ._social_btn { padding: 8px 12px; background: var(--accent-primary); color: white; text-decoration: none; border-radius: var(--border-radius-sm); font-size: 12px; font-weight: 500; transition: all var(--animation-duration) var(--animation-ease); } ._social_btn:hover { background: var(--accent-secondary); transform: translateY(-2px); } /* Footer */ #_footer { background: var(--bg-secondary); border-top: 1px solid var(--border-primary); padding: 16px 20px; } ._footer_content { display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: var(--text-secondary); } ._version { background: var(--bg-tertiary); padding: 4px 8px; border-radius: var(--border-radius-sm); font-weight: 600; } /* Floating Button - Claude.ai inspired */ #_floating_container { position: fixed; bottom: 24px; right: 24px; z-index: 10001; } #_floating_btn { position: relative; width: 56px; height: 56px; background: var(--accent-primary); border-radius: 50%; box-shadow: var(--shadow-lg); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all var(--animation-duration) var(--animation-ease); overflow: hidden; } #_floating_btn:hover { transform: scale(1.05); box-shadow: 0 8px 25px rgba(88, 166, 255, 0.4); } #_floating_btn:active { transform: scale(0.95); } ._btn_ripple { position: absolute; border-radius: 50%; transform: scale(0); animation: ripple 0.6s linear; background-color: rgba(255, 255, 255, 0.7); } @keyframes ripple { to { transform: scale(4); opacity: 0; } } ._btn_icon { font-size: 24px; z-index: 1; transition: transform var(--animation-duration) var(--animation-ease); } #_floating_btn:hover ._btn_icon { transform: rotate(180deg); } /* Responsive Design */ @media (max-width: 768px) { #_container { width: 98vw; max-height: 95vh; } ._stats_grid { grid-template-columns: 1fr 1fr; } ._control_row { flex-direction: column; align-items: stretch; } ._thread_control { justify-content: center; } ._modal_container { width: 95%; margin: 20px; } } @media (max-width: 480px) { ._stats_grid { grid-template-columns: 1fr; } #_floating_btn { width: 48px; height: 48px; bottom: 16px; right: 16px; } ._btn_icon { font-size: 20px; } } /* Utility Classes */ .hidden { display: none !important; } .loading { opacity: 0.6; pointer-events: none; } /* Scrollbar Styling */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: var(--bg-tertiary); border-radius: 4px; } ::-webkit-scrollbar-thumb { background: var(--border-primary); border-radius: 4px; transition: background var(--animation-duration) var(--animation-ease); } ::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); } /* Help text */ ._help_text { font-size: 12px; color: var(--text-tertiary); line-height: 1.4; } ._help_text strong { color: var(--accent-primary); font-weight: 600; } /* Loading animation */ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } ._loading { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } `; document.head.appendChild(style); const container = document.createElement("div"); container.innerHTML = containerHTML; document.body.appendChild(container); }; // Multi-threading worker class class FarmingWorker { constructor(id, type, delay = DELAY) { this.id = id; this.type = type; this.delay = delay; this.isActive = false; this.stats = { requests: 0, successes: 0, errors: 0 }; this.progressElement = null; } async start() { this.isActive = true; this.createProgressBar(); logToConsole(`Thread ${this.id} started (${this.type})`, 'success'); while (this.isActive && isRunning) { try { await this.doWork(); this.stats.requests++; this.stats.successes++; this.updateProgress(); await delay(this.delay + Math.random() * 200); // Add some randomization } catch (error) { this.stats.errors++; logToConsole(`Thread ${this.id} error: ${error.message}`, 'error'); await delay(this.delay * 2); // Back off on error } } logToConsole(`Thread ${this.id} stopped`, 'info'); } async doWork() { switch (this.type) { case 'xp': return await this.farmXP(); case 'gem': return await this.farmGem(); default: throw new Error(`Unknown work type: ${this.type}`); } } async farmXP() { const response = await farmXpOnce(); if (response.ok) { const data = await response.json(); const earned = data?.awardedXp || 0; totalEarned.xp += earned; updateEarnedStats(); logToConsole(`Thread ${this.id} earned ${earned} XP`, 'success'); return earned; } throw new Error(`XP farming failed: ${response.status}`); } async farmGem() { const response = await farmGemOnce(); if (response.ok) { totalEarned.gems += 30; updateEarnedStats(); logToConsole(`Thread ${this.id} earned 30 gems`, 'success'); return 30; } throw new Error(`Gem farming failed: ${response.status}`); } stop() { this.isActive = false; if (this.progressElement) { this.progressElement.remove(); } } createProgressBar() { const container = document.getElementById('_thread_bars'); if (!container) return; this.progressElement = document.createElement('div'); this.progressElement.className = '_thread_bar'; this.progressElement.innerHTML = ` <div class="_thread_id">Thread ${this.id}</div> <div class="_thread_progress_bar"> <div class="_thread_progress_fill" style="width: 0%"></div> </div> <div class="_thread_status">Starting...</div> `; container.appendChild(this.progressElement); } updateProgress() { if (!this.progressElement) return; const successRate = this.stats.requests > 0 ? (this.stats.successes / this.stats.requests * 100) : 100; const fillElement = this.progressElement.querySelector('._thread_progress_fill'); const statusElement = this.progressElement.querySelector('._thread_status'); fillElement.style.width = `${Math.min(successRate, 100)}%`; statusElement.textContent = `${this.stats.successes}/${this.stats.requests}`; // Update fill color based on success rate if (successRate >= 90) { fillElement.style.background = 'var(--gradient-success)'; } else if (successRate >= 70) { fillElement.style.background = 'var(--warning-color)'; } else { fillElement.style.background = 'var(--error-color)'; } } } // Utility functions const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const logToConsole = (message, type = 'info') => { const console = document.getElementById('_console_output'); if (!console) return; const timestamp = new Date().toLocaleTimeString(); const line = document.createElement('div'); line.className = `_console_line _${type}`; line.innerHTML = ` <span class="_timestamp">[${timestamp}]</span> <span class="_message">${message}</span> `; console.appendChild(line); // Auto scroll if enabled const autoScroll = document.getElementById('_auto_scroll'); if (autoScroll && autoScroll.classList.contains('_active')) { console.scrollTop = console.scrollHeight; } // Keep only last 100 lines while (console.children.length > 100) { console.removeChild(console.firstChild); } }; const updateEarnedStats = () => { const elements = { xp: document.getElementById('_earned_xp'), gems: document.getElementById('_earned_gems'), streak: document.getElementById('_earned_streak') }; if (elements.xp) elements.xp.textContent = totalEarned.xp.toLocaleString(); if (elements.gems) elements.gems.textContent = totalEarned.gems.toLocaleString(); if (elements.streak) elements.streak.textContent = totalEarned.streak; // Update success rate const successRate = farmingStats.sessions > 0 ? ((farmingStats.sessions - farmingStats.errors) / farmingStats.sessions * 100) : 100; const successElement = document.getElementById('_success_rate'); if (successElement) { successElement.textContent = `${successRate.toFixed(1)}%`; } // Update user info if available if (userInfo) { userInfo.totalXp += totalEarned.xp; userInfo.gems += totalEarned.gems; userInfo.streak += totalEarned.streak; updateUserInfo(); } }; const updateFarmingTime = () => { if (!farmingStats.startTime) return; const elapsed = Date.now() - farmingStats.startTime; const hours = Math.floor(elapsed / 3600000); const minutes = Math.floor((elapsed % 3600000) / 60000); const seconds = Math.floor((elapsed % 60000) / 1000); const timeElement = document.getElementById('_farming_time'); if (timeElement) { timeElement.textContent = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } }; // Interface management const setInterfaceVisible = (visible) => { const container = document.getElementById("_container"); const backdrop = document.getElementById("_backdrop"); if (container && backdrop) { container.style.display = visible ? "flex" : "none"; backdrop.style.display = visible ? "block" : "none"; } }; const isInterfaceVisible = () => { const container = document.getElementById("_container"); return container && container.style.display !== "none"; }; const toggleInterface = () => { setInterfaceVisible(!isInterfaceVisible()); }; const toggleSection = (sectionId) => { const section = document.getElementById(sectionId); if (section) { section.classList.toggle('_collapsed'); const chevron = section.querySelector('._chevron'); if (chevron) { chevron.classList.toggle('_rotated'); } } }; // Theme management const applyTheme = (theme) => { currentTheme = theme; localStorage.setItem('duofarmer_theme', theme); const elements = [ document.getElementById("_container"), document.getElementById("_floating_btn") ]; elements.forEach(el => { if (el) { el.className = el.className.replace(/theme-\w+/, `theme-${theme}`); } }); // Update theme selector document.querySelectorAll('._theme_option').forEach(btn => { btn.classList.toggle('_active', btn.dataset.theme === theme); }); }; // Event listeners const addEventListeners = () => { // Floating button document.getElementById('_floating_btn')?.addEventListener('click', (e) => { // Create ripple effect const button = e.currentTarget; const ripple = button.querySelector('._btn_ripple'); const rect = button.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = e.clientX - rect.left - size / 2; const y = e.clientY - rect.top - size / 2; ripple.style.width = ripple.style.height = size + 'px'; ripple.style.left = x + 'px'; ripple.style.top = y + 'px'; ripple.style.transform = 'scale(0)'; ripple.style.opacity = '0.7'; setTimeout(() => { ripple.style.transform = 'scale(4)'; ripple.style.opacity = '0'; }, 10); toggleInterface(); }); // Header controls document.getElementById('_minimize_btn')?.addEventListener('click', () => { setInterfaceVisible(false); }); document.getElementById('_close_btn')?.addEventListener('click', () => { if (isRunning) { if (confirm('Farming is active. Are you sure you want to close?')) { stopFarming(); setInterfaceVisible(false); } } else { setInterfaceVisible(false); } }); document.getElementById('_settings_btn')?.addEventListener('click', () => { document.getElementById('_settings_modal').style.display = 'flex'; }); // Join button document.getElementById('_join_btn')?.addEventListener('click', () => { window.open('https://discord.gg/Gvmd7deFtS', '_blank'); localStorage.setItem('duofarmer_joined', 'true'); hasJoined = true; toggleSection('_join_section'); document.getElementById('_join_status').textContent = '✓ Joined'; document.getElementById('_join_status').className = '_status_success'; document.getElementById('_main_content').style.display = 'block'; // Initialize in background initializeFarming(); }); // Farming controls document.getElementById('_start_farming')?.addEventListener('click', startFarming); document.getElementById('_stop_farming')?.addEventListener('click', stopFarming); document.getElementById('_pause_farming')?.addEventListener('click', pauseFarming); // Thread slider const slider = document.getElementById('_thread_slider'); const display = document.getElementById('_thread_display'); slider?.addEventListener('input', (e) => { if (display) display.textContent = e.target.value; updateActiveThreadsDisplay(); }); // Refresh button document.getElementById('_refresh_btn')?.addEventListener('click', async () => { const button = document.getElementById('_refresh_btn'); if (button) button.classList.add('_loading'); await refreshUserData(); if (button) button.classList.remove('_loading'); }); // Console controls document.getElementById('_clear_console')?.addEventListener('click', () => { const console = document.getElementById('_console_output'); if (console) console.innerHTML = ''; }); document.getElementById('_auto_scroll')?.addEventListener('click', (e) => { e.currentTarget.classList.toggle('_active'); }); // Settings modal document.getElementById('_close_modal')?.addEventListener('click', () => { document.getElementById('_settings_modal').style.display = 'none'; }); document.getElementById('_settings_modal')?.addEventListener('click', (e) => { if (e.target.classList.contains('_modal_overlay')) { document.getElementById('_settings_modal').style.display = 'none'; } }); // Theme selection document.querySelectorAll('._theme_option').forEach(btn => { btn.addEventListener('click', () => { applyTheme(btn.dataset.theme); }); }); // Settings controls document.getElementById('_delay_slider')?.addEventListener('input', (e) => { const value = e.target.value; document.getElementById('_delay_value').textContent = `${value}ms`; }); // Stats reset document.getElementById('_reset_stats')?.addEventListener('click', resetStats); // Export stats document.getElementById('_export_stats')?.addEventListener('click', exportStats); }; // Farming functions const startFarming = async () => { if (isRunning) return; const mode = document.getElementById('_farming_mode')?.value; const threadCount = parseInt(document.getElementById('_thread_slider')?.value || '3'); isRunning = true; farmingStats.startTime = Date.now(); farmingStats.sessions = 0; farmingStats.errors = 0; // Update UI document.getElementById('_start_farming').style.display = 'none'; document.getElementById('_stop_farming').style.display = 'inline-flex'; document.getElementById('_pause_farming').style.display = 'inline-flex'; logToConsole(`Starting ${mode} with ${threadCount} threads`, 'success'); // Start farming timer const timer = setInterval(updateFarmingTime, 1000); try { switch (mode) { case 'xp_multi': await startMultiThreadedFarming('xp', threadCount); break; case 'gem_multi': await startMultiThreadedFarming('gem', threadCount); break; case 'mixed_farming': await startMixedFarming(threadCount); break; case 'streak_repair': await repairStreak(); break; case 'streak_farm': await farmStreakLoop(); break; default: logToConsole(`Unknown farming mode: ${mode}`, 'error'); } } catch (error) { logToConsole(`Farming error: ${error.message}`, 'error'); } finally { clearInterval(timer); } }; const stopFarming = () => { if (!isRunning) return; isRunning = false; // Stop all threads activeThreads.forEach(thread => thread.stop()); activeThreads = []; // Update UI document.getElementById('_start_farming').style.display = 'inline-flex'; document.getElementById('_stop_farming').style.display = 'none'; document.getElementById('_pause_farming').style.display = 'none'; // Clear progress bars const threadBars = document.getElementById('_thread_bars'); if (threadBars) threadBars.innerHTML = ''; updateActiveThreadsDisplay(); logToConsole('Farming stopped', 'info'); }; const pauseFarming = () => { // Toggle pause state const button = document.getElementById('_pause_farming'); const isPaused = button.textContent.includes('Resume'); if (isPaused) { // Resume farming activeThreads.forEach(thread => thread.isActive = true); button.innerHTML = '<span class="_btn_content"><span class="_btn_icon">⏨</span><span class="_btn_text">Pause</span></span>'; logToConsole('Farming resumed', 'info'); } else { // Pause farming activeThreads.forEach(thread => thread.isActive = false); button.innerHTML = '<span class="_btn_content"><span class="_btn_icon">▶</span><span class="_btn_text">Resume</span></span>'; logToConsole('Farming paused', 'warning'); } }; const startMultiThreadedFarming = async (type, threadCount) => { activeThreads = []; for (let i = 1; i <= threadCount; i++) { const worker = new FarmingWorker(i, type); activeThreads.push(worker); // Start thread with slight delay to avoid rate limiting setTimeout(() => { if (isRunning) worker.start(); }, i * 100); } updateActiveThreadsDisplay(); // Wait for all threads to complete (they run until stopped) await new Promise(resolve => { const checkInterval = setInterval(() => { if (!isRunning || activeThreads.every(thread => !thread.isActive)) { clearInterval(checkInterval); resolve(); } }, 1000); }); }; const startMixedFarming = async (threadCount) => { activeThreads = []; const types = ['xp', 'gem']; for (let i = 1; i <= threadCount; i++) { const type = types[(i - 1) % types.length]; const worker = new FarmingWorker(i, type); activeThreads.push(worker); setTimeout(() => { if (isRunning) worker.start(); }, i * 150); } updateActiveThreadsDisplay(); await new Promise(resolve => { const checkInterval = setInterval(() => { if (!isRunning || activeThreads.every(thread => !thread.isActive)) { clearInterval(checkInterval); resolve(); } }, 1000); }); }; const updateActiveThreadsDisplay = () => { const display = document.getElementById('_active_threads'); if (display) { display.textContent = activeThreads.filter(thread => thread.isActive).length; } }; // Original farming functions (adapted for multi-threading) const getJwtToken = () => { const cookies = document.cookie.split(";"); for (let cookie of cookies) { cookie = cookie.trim(); if (cookie.startsWith("jwt_token=")) { return cookie.substring("jwt_token=".length); } } return null; }; const decodeJwtToken = (token) => { const base64Url = token.split(".")[1]; const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); const jsonPayload = decodeURIComponent( atob(base64) .split("") .map(c => "%" + ("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`; const response = await fetch(userInfoUrl, { method: "GET", headers: defaultHeaders, }); return await response.json(); }; const sendRequestWithDefaultHeaders = async ({ url, payload, headers = {}, method = "GET" }) => { const mergedHeaders = { ...defaultHeaders, ...headers }; return await fetch(url, { method, headers: mergedHeaders, body: payload ? JSON.stringify(payload) : undefined, }); }; const farmXpOnce = async () => { 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, method: "POST", }); }; 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 repairStreak = async () => { logToConsole('Starting streak repair...', 'info'); try { if (!userInfo.streakData?.currentStreak) { logToConsole('No streak to repair!', 'error'); return; } const startStreakDate = userInfo.streakData.currentStreak.startDate; const endStreakDate = userInfo.streakData.currentStreak.endDate; const startStreakTimestamp = Math.floor(new Date(startStreakDate).getTime() / 1000); const endStreakTimestamp = Math.floor(new Date(endStreakDate).getTime() / 1000); const expectedStreak = Math.floor((endStreakTimestamp - startStreakTimestamp) / (60 * 60 * 24)) + 1; if (expectedStreak > userInfo.streak) { logToConsole(`Found ${expectedStreak - userInfo.streak} frozen days. Repairing...`, 'warning'); let currentTimestamp = Math.floor(Date.now() / 1000); for (let i = 0; i < expectedStreak && isRunning; i++) { await farmSessionOnce(currentTimestamp, currentTimestamp + 60); currentTimestamp -= 86400; logToConsole(`Repaired day ${i + 1}/${expectedStreak}`, 'info'); await delay(DELAY); } const updatedUser = await getUserInfo(sub); if (updatedUser.streak >= expectedStreak) { logToConsole(`Streak repair completed! New streak: ${updatedUser.streak}`, 'success'); userInfo = updatedUser; totalEarned.streak += (updatedUser.streak - userInfo.streak); updateUserInfo(); updateEarnedStats(); } } else { logToConsole('No frozen streak detected', 'info'); } } catch (error) { logToConsole(`Streak repair failed: ${error.message}`, 'error'); } finally { stopFarming(); } }; const farmStreakLoop = async () => { logToConsole('Starting streak farming...', 'info'); const hasStreak = !!userInfo.streakData?.currentStreak; const startStreakDate = hasStreak ? userInfo.streakData.currentStreak.startDate : new Date(); const startFarmStreakTimestamp = Math.floor(new Date(startStreakDate).getTime() / 1000); let currentTimestamp = hasStreak ? startFarmStreakTimestamp - 86400 : startFarmStreakTimestamp; while (isRunning) { try { await farmSessionOnce(currentTimestamp, currentTimestamp + 60); currentTimestamp -= 86400; totalEarned.streak++; userInfo.streak++; updateUserInfo(); updateEarnedStats(); logToConsole(`Streak increased to ${userInfo.streak}`, 'success'); await delay(DELAY); } catch (error) { logToConsole(`Streak farming error: ${error.message}`, 'error'); await delay(DELAY * 2); } } }; 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 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, }; const updateRes = await sendRequestWithDefaultHeaders({ url: `https://www.duolingo.com/2017-06-30/sessions/${sessionData.id}`, payload: updateSessionPayload, method: "PUT", }); return await updateRes.json(); }; // UI update functions const updateUserInfo = () => { if (!userInfo) return; const elements = { username: document.getElementById('_username'), fromLang: document.getElementById('_from_lang'), learningLang: document.getElementById('_learning_lang'), currentStreak: document.getElementById('_current_streak'), currentGems: document.getElementById('_current_gems'), currentXp: document.getElementById('_current_xp') }; if (elements.username) elements.username.textContent = userInfo.username; if (elements.fromLang) elements.fromLang.textContent = userInfo.fromLanguage; if (elements.learningLang) elements.learningLang.textContent = userInfo.learningLanguage; if (elements.currentStreak) elements.currentStreak.textContent = userInfo.streak?.toLocaleString() || '0'; if (elements.currentGems) elements.currentGems.textContent = userInfo.gems?.toLocaleString() || '0'; if (elements.currentXp) elements.currentXp.textContent = userInfo.totalXp?.toLocaleString() || '0'; }; const refreshUserData = async () => { if (!sub || !defaultHeaders) return; try { logToConsole('Refreshing user data...', 'info'); userInfo = await getUserInfo(sub); updateUserInfo(); logToConsole('User data refreshed successfully', 'success'); } catch (error) { logToConsole(`Failed to refresh user data: ${error.message}`, 'error'); } }; const resetStats = () => { totalEarned = { xp: 0, gems: 0, streak: 0 }; farmingStats = { sessions: 0, errors: 0, startTime: null }; updateEarnedStats(); logToConsole('Statistics reset', 'info'); }; const exportStats = () => { const stats = { totalEarned, farmingStats, userInfo: userInfo ? { username: userInfo.username, streak: userInfo.streak, gems: userInfo.gems, totalXp: userInfo.totalXp } : null, exportDate: new Date().toISOString(), version: VERSION }; const blob = new Blob([JSON.stringify(stats, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `duofarmer-stats-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); logToConsole('Statistics exported successfully', 'success'); }; // Initialization const initializeFarming = async () => { try { jwt = getJwtToken(); if (!jwt) { logToConsole('Please login to Duolingo and reload the page', 'error'); return false; } defaultHeaders = formatHeaders(jwt); const decodedJwt = decodeJwtToken(jwt); sub = decodedJwt.sub; logToConsole('Loading user information...', 'info'); userInfo = await getUserInfo(sub); if (userInfo && userInfo.username) { updateUserInfo(); logToConsole(`Welcome ${userInfo.username}! Ready to farm.`, 'success'); return true; } else { logToConsole('Failed to load user information', 'error'); return false; } } catch (error) { logToConsole(`Initialization error: ${error.message}`, 'error'); return false; } }; // Main initialization (async () => { try { // Initialize interface initInterface(); setInterfaceVisible(false); // Apply saved theme applyTheme(currentTheme); // Add event listeners addEventListeners(); if (hasJoined) { // Initialize in background without blocking initializeFarming(); } logToConsole('DuoNexus initialized successfully', 'success'); logToConsole('Created by tw1sk - Discord: @tw1sk', 'info'); } catch (error) { console.error('Initialization failed:', error); } })();