// ==UserScript== // @name DuoHacker // @description Best free-to-use Duolingo farming tool! // @namespace https://irylisvps.vercel.app // @version 1.0.0 // @author DuoHacker Community // @match https://*.duolingo.com/* // @icon https://github.com/pillowslua/images/blob/main/Hacklingo.png?raw=true // @grant none // @license MIT // @downloadURL none // ==/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 = `

DuoHacker PRO

BEST Duolingo Farming Tool
Join Our Community
${hasJoined ? '✓ Joined' : '⚠ Required'}
Join our Discord server for updates and support!
Account Overview
Username
Loading...
🌎
Languages
-- -> --
Streak
0
Gems
0
Total XP
0
Farming Controls
Threads: 0/${MAX_THREADS}
3
Live Statistics
XP Earned
0
💎
Gems Earned
0
🔥
Streak Gained
0
🎯
Success Rate
100%
Farming Progress 00:00:00
Console Output
[${new Date().toLocaleTimeString()}] DuoNexus v${VERSION} initialized
[${new Date().toLocaleTimeString()}] Multi-threading engine ready
`; 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 = `
Thread ${this.id}
Starting...
`; 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 = ` [${timestamp}] ${message} `; 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 = 'Pause'; logToConsole('Farming resumed', 'info'); } else { // Pause farming activeThreads.forEach(thread => thread.isActive = false); button.innerHTML = 'Resume'; 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); } })();