Greasy Fork is available in English.
具有新安全模式的最佳免费 Duolingo 农场工具!
当前为
// ==UserScript==
// @name DuoHacker
// @name:zh-CN DuoHacker — 新安全模式 Duolingo 农场工具
// @name:ja DuoHacker — 新しい安全モード Duolingo ファーミングツール
// @name:es DuoHacker — Nueva Modo Seguro Herramienta para farmear en Duolingo
// @name:ru DuoHacker — Новый безопасный режим для фарминга Duolingo
// @name:pt-BR DuoHacker — Novo Modo Seguro Ferramenta para farmar no Duolingo
// @name:de DuoHacker — Neuer Sicherer Modus Duolingo Farming-Tool
// @name:it DuoHacker — Nuova Modalità Sicura Strumento di farming Duolingo
// @name:ko DuoHacker — 새로운 안전 모드 Duolingo 팜 도구
// @name:hi DuoHacker — नया सुरक्षित मोड Duolingo फार्मिंग टूल
// @name:ar DuoHacker — الوضع الآمن الجديد أداة زراعة Duolingo
// @name:tr DuoHacker — Yeni Güvenli Mod Duolingo Farming Aracı
// @name:pl DuoHacker — Nowy Tryb Bezpieczny Narzędzie do farmienia Duolingo
// @description Best free-to-use Duolingo farming tool with new safe mode!
// @description:zh-CN 具有新安全模式的最佳免费 Duolingo 农场工具!
// @description:ja 新しい安全モードを搭載した最高の無料 Duolingo ファーミングツール!
// @description:es ¡La mejor herramienta gratuita para farmear en Duolingo con nuevo modo seguro!
// @description:ru Лучший бесплатный инструмент для фарминга Duolingo с новым безопасным режимом!
// @description:pt-BR A melhor ferramenta gratuita para farmar no Duolingo com novo modo seguro!
// @description:de Bestes kostenloses Duolingo Farming-Tool mit neuem Sicherem Modus!
// @description:it Migliore strumento di farming Duolingo gratuito con nuova modalità sicura!
// @description:ko 새로운 안전 모드가 탑재된 최고의 무료 Duolingo 팜 도구!
// @description:hi नए सुरक्षित मोड के साथ सर्वश्रेष्ठ मुफ्त Duolingo फार्मिंग टूल!
// @description:ar أفضل أداة زراعة Duolingo مجانية مع الوضع الآمن الجديد!
// @description:tr Yeni güvenli modlu en iyi ücretsiz Duolingo farming aracı!
// @description:pl Najlepsze darmowe narzędzie do farmienia Duolingo z nowym trybem bezpiecznym!
// @namespace https://irylisvps.vercel.app
// @version 2.0.0
// @author DuoHacker Community
// @match https://*.duolingo.com/*
// @icon https://github.com/pillowslua/images/blob/main/logoo.png?raw=true
// @grant none
// @license MIT
// ==/UserScript==
const VERSION = "2.0.0";
const SAFE_DELAY = 2000;
const FAST_DELAY = 300;
var jwt, defaultHeaders, userInfo, sub;
let isRunning = false;
let currentMode = 'safe';
let currentTheme = localStorage.getItem('duofarmer_theme') || 'dark';
let hasJoined = localStorage.getItem('duofarmer_joined') === 'true';
let totalEarned = { xp: 0, gems: 0, streak: 0 };
let farmingStats = { sessions: 0, errors: 0, startTime: null };
let farmingInterval = null;
const initInterface = () => {
const containerHTML = `
<div id="_backdrop"></div>
<div id="_container" class="theme-${currentTheme}">
<div id="_header">
<div class="_header_top">
<div class="_brand">
<div class="_logo_container">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" class="_logo">
<defs>
<linearGradient id="logoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#00D4FF;stop-opacity:1" />
<stop offset="50%" style="stop-color:#7B2FF7;stop-opacity:1" />
<stop offset="100%" style="stop-color:#FF107F;stop-opacity:1" />
</linearGradient>
<filter id="logoShadow">
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-opacity="0.3"/>
</filter>
</defs>
<circle cx="60" cy="60" r="50" fill="url(#logoGrad)" filter="url(#logoShadow)"/>
<path d="M60 30 Q75 45 60 70 Q45 45 60 30" fill="white" opacity="0.9"/>
<circle cx="45" cy="50" r="4" fill="white"/>
<circle cx="75" cy="50" r="4" fill="white"/>
<path d="M45 75 Q60 85 75 75" stroke="white" stroke-width="3" fill="none" stroke-linecap="round"/>
</svg>
</div>
<div class="_brand_text">
<h1>DuoHacker</h1>
<span class="_version_badge">v2.0</span>
</div>
</div>
<div class="_header_controls">
<button id="_settings_btn" class="_control_btn _settings">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v6m0 6v6m4.22-13.22l4.24 4.24M1.54 1.54l4.24 4.24M20.46 20.46l-4.24-4.24M1.54 20.46l4.24-4.24"/>
</svg>
</button>
<button id="_theme_toggle" class="_control_btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
</button>
<button id="_minimize_btn" class="_control_btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"/>
</svg>
</button>
<button id="_close_btn" class="_control_btn _close">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
</div>
</div>
<div id="_main_content" style="display:none">
<div class="_profile_card">
<div class="_profile_header">
<div class="_avatar">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
</div>
<div class="_profile_info">
<h2 id="_username">Loading...</h2>
<p id="_user_details">Fetching data...</p>
</div>
<button id="_refresh_profile" class="_refresh_btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"/>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
</svg>
</button>
</div>
<div class="_stats_row">
<div class="_stat_item">
<div class="_stat_icon">⚡</div>
<div class="_stat_info">
<span class="_stat_value" id="_current_xp">0</span>
<span class="_stat_label">Total XP</span>
</div>
</div>
<div class="_stat_item">
<div class="_stat_icon">🔥</div>
<div class="_stat_info">
<span class="_stat_value" id="_current_streak">0</span>
<span class="_stat_label">Streak</span>
</div>
</div>
<div class="_stat_item">
<div class="_stat_icon">💎</div>
<div class="_stat_info">
<span class="_stat_value" id="_current_gems">0</span>
<span class="_stat_label">Gems</span>
</div>
</div>
</div>
</div>
<div class="_mode_section">
<h3>Select Farming Mode</h3>
<div class="_mode_cards">
<div class="_mode_card ${currentMode === 'safe' ? '_active' : ''}" data-mode="safe">
<div class="_mode_icon">🛡️</div>
<h4>Safe Mode</h4>
<p>Slow but undetectable farming</p>
<div class="_mode_specs">
<span class="_spec">2s delay</span>
<span class="_spec">100% safe</span>
</div>
</div>
<div class="_mode_card ${currentMode === 'fast' ? '_active' : ''}" data-mode="fast">
<div class="_mode_icon">⚡</div>
<h4>Fast Mode</h4>
<p>Quick farming with moderate risk</p>
<div class="_mode_specs">
<span class="_spec">0.3s delay</span>
<span class="_spec">Use carefully</span>
</div>
</div>
</div>
</div>
<div class="_options_section">
<h3>Farming Options</h3>
<div class="_option_grid">
<button class="_option_btn" data-type="xp">
<div class="_option_icon">⚡</div>
<span>Farm XP</span>
</button>
<button class="_option_btn" data-type="gems">
<div class="_option_icon">💎</div>
<span>Farm Gems</span>
</button>
<button class="_option_btn" data-type="streak_repair">
<div class="_option_icon">🔧</div>
<span>Repair Streak</span>
</button>
<button class="_option_btn" data-type="streak_farm">
<div class="_option_icon">🔥</div>
<span>Farm Streak</span>
</button>
</div>
</div>
<div class="_control_panel">
<button id="_start_farming" class="_start_btn">
<span class="_btn_text">Start Farming</span>
<div class="_btn_glow"></div>
</button>
<button id="_stop_farming" class="_stop_btn" style="display:none">
<span class="_btn_text">Stop Farming</span>
</button>
</div>
<div class="_live_stats">
<h3>Live Statistics</h3>
<div class="_stats_grid">
<div class="_live_stat">
<div class="_live_icon">⚡</div>
<div class="_live_data">
<span id="_earned_xp">0</span>
<small>XP Earned</small>
</div>
</div>
<div class="_live_stat">
<div class="_live_icon">💎</div>
<div class="_live_data">
<span id="_earned_gems">0</span>
<small>Gems Earned</small>
</div>
</div>
<div class="_live_stat">
<div class="_live_icon">🔥</div>
<div class="_live_data">
<span id="_earned_streak">0</span>
<small>Streak Gained</small>
</div>
</div>
<div class="_live_stat">
<div class="_live_icon">⏱️</div>
<div class="_live_data">
<span id="_farming_time">00:00</span>
<small>Time Elapsed</small>
</div>
</div>
</div>
</div>
<div class="_console_section">
<div class="_console_header">
<h3>Activity Log</h3>
<button id="_clear_console" class="_clear_btn">Clear</button>
</div>
<div id="_console_output" class="_console">
<div class="_log_entry _info">
<span class="_log_time">${new Date().toLocaleTimeString()}</span>
<span class="_log_msg">DuoHacker v2.0 initialized</span>
</div>
</div>
</div>
</div>
<div id="_join_section" class="_join_section">
<div class="_join_content">
<div class="_join_icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2.5-1-3.5a2.5 2.5 0 0 0-1.5 3.5z"/>
<path d="M15.5 14.5A2.5 2.5 0 0 1 13 12c0-1.38.5-2.5 1-3.5a2.5 2.5 0 0 1 1.5 3.5z"/>
<path d="M9 16c-1.5 1.5-2 3.5-2 5.5h10c0-2-.5-4-2-5.5"/>
<path d="M9 16c0-1 1-2 1-3h4c0 1 1 2 1 3"/>
</svg>
</div>
<h2>Join Our Community</h2>
<p>Get access to updates, support, and exclusive features</p>
<button id="_join_btn" class="_join_btn">
<span>Join Discord Server</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8L22 12L18 16"/>
<path d="M2 12H22"/>
</svg>
</button>
</div>
</div>
<div class="_footer">
<span>© 2025 DuoHacker by tw1sk</span>
<div class="_footer_links">
<button id="_website_btn" class="_footer_link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<path d="M2 12h20"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>
Website
</button>
<button id="_discord_btn" class="_footer_link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2.5-1-3.5a2.5 2.5 0 0 0-1.5 3.5z"/>
<path d="M15.5 14.5A2.5 2.5 0 0 1 13 12c0-1.38.5-2.5 1-3.5a2.5 2.5 0 0 1 1.5 3.5z"/>
<path d="M9 16c-1.5 1.5-2 3.5-2 5.5h10c0-2-.5-4-2-5.5"/>
<path d="M9 16c0-1 1-2 1-3h4c0 1 1 2 1 3"/>
</svg>
Discord
</button>
</div>
<span class="_footer_version">v2.0.0</span>
</div>
</div>
<div id="_settings_modal" class="_modal" style="display:none">
<div class="_modal_overlay"></div>
<div class="_modal_container">
<div class="_modal_header">
<h2>Settings</h2>
<button id="_close_settings" class="_close_modal_btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="_modal_content">
<div class="_settings_section">
<h3>Account Management</h3>
<div class="_setting_item">
<button id="_get_jwt_btn" class="_setting_btn _primary">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
Get JWT Token
</button>
</div>
<div class="_setting_item">
<button id="_logout_btn" class="_setting_btn _danger">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
Log Out
</button>
</div>
<div class="_setting_item">
<div class="_jwt_input_group">
<input type="text" id="_jwt_input" placeholder="Enter JWT Token">
<button id="_login_jwt_btn" class="_setting_btn _success">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
<polyline points="10 17 15 12 10 7"/>
<line x1="15" y1="12" x2="3" y2="12"/>
</svg>
Login
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="_fab">
<div class="_fab_ring"></div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<path d="M12 6v6l4 2"/>
</svg>
</div>
`;
const style = document.createElement("style");
style.innerHTML = `
:root {
--primary-gradient: linear-gradient(135deg, #00D4FF 0%, #7B2FF7 50%, #FF107F 100%);
--success-gradient: linear-gradient(135deg, #00F260 0%, #0575E6 100%);
--danger-gradient: linear-gradient(135deg, #FF512F 0%, #DD2476 100%);
--glass-bg: rgba(255, 255, 255, 0.1);
--glass-border: rgba(255, 255, 255, 0.2);
--shadow-glow: 0 0 20px rgba(123, 47, 247, 0.3);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.theme-dark {
--bg-primary: #0a0a0f;
--bg-secondary: #151520;
--bg-card: rgba(30, 30, 45, 0.8);
--text-primary: #ffffff;
--text-secondary: #a0a0b0;
--text-muted: #707080;
--border-color: rgba(255, 255, 255, 0.1);
--accent-color: #7B2FF7;
--success-color: #00F260;
--error-color: #FF512F;
}
.theme-light {
--bg-primary: #f8f9fa;
--bg-secondary: #ffffff;
--bg-card: rgba(255, 255, 255, 0.9);
--text-primary: #212529;
--text-secondary: #6c757d;
--text-muted: #adb5bd;
--border-color: rgba(0, 0, 0, 0.1);
--accent-color: #7B2FF7;
--success-color: #00F260;
--error-color: #FF512F;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#_container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: min(90vw, 900px);
max-height: 90vh;
background: var(--bg-primary);
border-radius: 24px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
border: 1px solid var(--border-color);
overflow: hidden;
z-index: 10000;
display: flex;
flex-direction: column;
animation: containerAppear 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@keyframes containerAppear {
0% {
opacity: 0;
transform: translate(-50%, -50%) scale(0.8) rotateX(10deg);
}
100% {
opacity: 1;
transform: translate(-50%, -50%) scale(1) rotateX(0);
}
}
#_backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
z-index: 9999;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
#_header {
background: var(--bg-secondary);
padding: 20px 24px;
border-bottom: 1px solid var(--border-color);
}
._header_top {
display: flex;
justify-content: space-between;
align-items: center;
}
._brand {
display: flex;
align-items: center;
gap: 16px;
}
._logo_container {
width: 48px;
height: 48px;
animation: logoFloat 3s ease-in-out infinite;
}
@keyframes logoFloat {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
._logo {
width: 100%;
height: 100%;
}
._brand_text {
display: flex;
align-items: center;
gap: 12px;
}
._brand_text h1 {
font-size: 24px;
font-weight: 700;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
._version_badge {
background: var(--success-gradient);
color: white;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
._header_controls {
display: flex;
gap: 8px;
}
._control_btn {
width: 40px;
height: 40px;
border: none;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 12px;
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
backdrop-filter: blur(10px);
}
._control_btn:hover {
background: var(--accent-color);
color: white;
transform: translateY(-2px);
box-shadow: var(--shadow-glow);
}
._control_btn._close:hover {
background: var(--error-color);
}
._control_btn._settings {
background: var(--accent-color);
color: white;
box-shadow: var(--shadow-glow);
}
._control_btn._settings:hover {
background: var(--success-color);
transform: translateY(-2px) scale(1.1);
}
#_main_content {
flex: 1;
overflow-y: auto;
padding: 24px;
display: flex;
flex-direction: column;
gap: 24px;
}
._profile_card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 24px;
backdrop-filter: blur(10px);
transition: var(--transition);
}
._profile_card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
._profile_header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}
._avatar {
width: 60px;
height: 60px;
background: var(--primary-gradient);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
._avatar svg {
width: 32px;
height: 32px;
}
._profile_info h2 {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
}
._profile_info p {
color: var(--text-secondary);
font-size: 14px;
}
._refresh_btn {
margin-left: auto;
width: 36px;
height: 36px;
border: none;
background: var(--glass-bg);
border: 1px solid var(--border-color);
border-radius: 10px;
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
._refresh_btn:hover {
background: var(--accent-color);
color: white;
transform: rotate(180deg);
}
._stats_row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
._stat_item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--glass-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
backdrop-filter: blur(10px);
}
._stat_icon {
font-size: 24px;
}
._stat_info {
display: flex;
flex-direction: column;
}
._stat_value {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
}
._stat_label {
font-size: 12px;
color: var(--text-secondary);
}
._mode_section h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 16px;
}
._mode_cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
._mode_card {
background: var(--bg-card);
border: 2px solid var(--border-color);
border-radius: 16px;
padding: 20px;
cursor: pointer;
transition: var(--transition);
text-align: center;
backdrop-filter: blur(10px);
}
._mode_card:hover {
transform: translateY(-4px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
}
._mode_card._active {
border-color: var(--accent-color);
background: linear-gradient(135deg, rgba(123, 47, 247, 0.1), rgba(255, 16, 127, 0.1));
box-shadow: var(--shadow-glow);
}
._mode_icon {
font-size: 48px;
margin-bottom: 12px;
}
._mode_card h4 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
}
._mode_card p {
color: var(--text-secondary);
font-size: 14px;
margin-bottom: 12px;
}
._mode_specs {
display: flex;
justify-content: center;
gap: 8px;
}
._spec {
background: var(--glass-bg);
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
color: var(--text-muted);
}
._options_section h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 16px;
}
._option_grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
._option_btn {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 16px;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 12px;
backdrop-filter: blur(10px);
}
._option_btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
border-color: var(--accent-color);
}
._option_btn._selected {
background: var(--accent-color);
color: white;
}
._option_icon {
font-size: 24px;
}
._option_btn span {
font-weight: 500;
}
._control_panel {
display: flex;
justify-content: center;
gap: 16px;
}
._start_btn, ._stop_btn {
position: relative;
padding: 16px 48px;
border: none;
border-radius: 16px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
overflow: hidden;
}
._start_btn {
background: var(--success-gradient);
color: white;
}
._stop_btn {
background: var(--danger-gradient);
color: white;
}
._start_btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 35px rgba(0, 242, 96, 0.3);
}
._stop_btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 35px rgba(255, 81, 47, 0.3);
}
._btn_glow {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s;
}
._start_btn:hover ._btn_glow {
left: 100%;
}
._live_stats h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 16px;
}
._stats_grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
._live_stat {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 16px;
display: flex;
align-items: center;
gap: 12px;
backdrop-filter: blur(10px);
}
._live_icon {
font-size: 24px;
}
._live_data {
display: flex;
flex-direction: column;
}
._live_data span {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
}
._live_data small {
font-size: 12px;
color: var(--text-secondary);
}
._console_section {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 16px;
backdrop-filter: blur(10px);
}
._console_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
}
._console_header h3 {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
._clear_btn {
background: var(--glass-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 6px 12px;
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
transition: var(--transition);
}
._clear_btn:hover {
background: var(--error-color);
color: white;
}
._console {
height: 150px;
overflow-y: auto;
padding: 16px 20px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 13px;
}
._log_entry {
display: flex;
gap: 12px;
margin-bottom: 8px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
._log_time {
color: var(--text-muted);
flex-shrink: 0;
}
._log_msg {
color: var(--text-secondary);
}
._log_entry._success ._log_msg {
color: var(--success-color);
}
._log_entry._error ._log_msg {
color: var(--error-color);
}
._log_entry._info ._log_msg {
color: var(--accent-color);
}
._join_section {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
}
._join_content {
text-align: center;
max-width: 400px;
}
._join_icon {
width: 80px;
height: 80px;
background: var(--primary-gradient);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24px;
color: white;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
._join_icon svg {
width: 40px;
height: 40px;
}
._join_content h2 {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 12px;
}
._join_content p {
color: var(--text-secondary);
margin-bottom: 24px;
}
._join_btn {
background: var(--primary-gradient);
color: white;
border: none;
padding: 14px 28px;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: var(--transition);
}
._join_btn:hover {
transform: translateY(-2px);
box-shadow: 0 15px 35px rgba(123, 47, 247, 0.3);
}
._footer {
padding: 16px 24px;
background: var(--bg-secondary);
border-top: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: var(--text-muted);
}
._footer_links {
display: flex;
gap: 12px;
}
._footer_link {
display: flex;
align-items: center;
gap: 6px;
background: var(--glass-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 6px 12px;
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
transition: var(--transition);
}
._footer_link:hover {
background: var(--accent-color);
color: white;
}
._footer_link svg {
width: 14px;
height: 14px;
}
._footer_version {
background: var(--glass-bg);
padding: 4px 8px;
border-radius: 6px;
}
#_fab {
position: fixed;
bottom: 24px;
right: 24px;
width: 56px;
height: 56px;
background: var(--primary-gradient);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
box-shadow: 0 10px 25px rgba(123, 47, 247, 0.3);
transition: var(--transition);
z-index: 9998;
}
#_fab:hover {
transform: scale(1.1);
box-shadow: 0 15px 35px rgba(123, 47, 247, 0.5);
}
._fab_ring {
position: absolute;
width: 100%;
height: 100%;
border: 2px solid var(--accent-color);
border-radius: 50%;
animation: ringPulse 2s infinite;
}
@keyframes ringPulse {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
#_fab svg {
width: 24px;
height: 24px;
z-index: 1;
}
._modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 10001;
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.8);
backdrop-filter: blur(10px);
}
._modal_container {
position: relative;
width: 90%;
max-width: 500px;
background: var(--bg-primary);
border: 2px solid var(--accent-color);
border-radius: 20px;
box-shadow: 0 25px 50px rgba(123, 47, 247, 0.4);
overflow: hidden;
animation: modalSlideIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.8) translateY(30px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
._modal_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px;
background: var(--bg-secondary);
border-bottom: 2px solid var(--accent-color);
}
._modal_header h2 {
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
._close_modal_btn {
width: 36px;
height: 36px;
border: none;
background: var(--error-color);
color: white;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
._close_modal_btn:hover {
background: var(--accent-color);
transform: scale(1.1);
}
._modal_content {
padding: 24px;
}
._settings_section {
margin-bottom: 24px;
}
._settings_section:last-child {
margin-bottom: 0;
}
._settings_section h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 8px;
}
._setting_item {
margin-bottom: 16px;
}
._setting_item:last-child {
margin-bottom: 0;
}
._setting_btn {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
background: var(--bg-card);
border: 2px solid var(--border-color);
border-radius: 16px;
color: var(--text-primary);
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
backdrop-filter: blur(10px);
}
._setting_btn:hover {
transform: translateY(-3px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
._setting_btn._primary {
background: var(--primary-gradient);
color: white;
border-color: var(--accent-color);
}
._setting_btn._primary:hover {
box-shadow: 0 15px 35px rgba(123, 47, 247, 0.4);
}
._setting_btn._success {
background: var(--success-gradient);
color: white;
border-color: var(--success-color);
}
._setting_btn._success:hover {
box-shadow: 0 15px 35px rgba(0, 242, 96, 0.4);
}
._setting_btn._danger {
background: var(--danger-gradient);
color: white;
border-color: var(--error-color);
}
._setting_btn._danger:hover {
box-shadow: 0 15px 35px rgba(255, 81, 47, 0.4);
}
._setting_btn svg {
width: 20px;
height: 20px;
}
._jwt_input_group {
display: flex;
gap: 12px;
}
#_jwt_input {
flex: 1;
padding: 16px 20px;
background: var(--bg-card);
border: 2px solid var(--border-color);
border-radius: 16px;
color: var(--text-primary);
font-size: 14px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
transition: var(--transition);
}
#_jwt_input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 4px rgba(123, 47, 247, 0.2);
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: var(--accent-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
@media (max-width: 768px) {
#_container {
width: 95vw;
max-height: 95vh;
}
._stats_row, ._mode_cards, ._option_grid, ._stats_grid {
grid-template-columns: 1fr;
}
._control_panel {
flex-direction: column;
}
._start_btn, ._stop_btn {
width: 100%;
}
._footer {
flex-direction: column;
gap: 12px;
}
._footer_links {
width: 100%;
justify-content: center;
}
._jwt_input_group {
flex-direction: column;
}
}
`;
document.head.appendChild(style);
const container = document.createElement("div");
container.innerHTML = containerHTML;
document.body.appendChild(container);
};
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 entry = document.createElement('div');
entry.className = `_log_entry _${type}`;
entry.innerHTML = `
<span class="_log_time">${timestamp}</span>
<span class="_log_msg">${message}</span>
`;
console.appendChild(entry);
console.scrollTop = console.scrollHeight;
while (console.children.length > 50) {
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;
};
const updateFarmingTime = () => {
if (!farmingStats.startTime) return;
const elapsed = Date.now() - farmingStats.startTime;
const minutes = Math.floor(elapsed / 60000);
const seconds = Math.floor((elapsed % 60000) / 1000);
const timeElement = document.getElementById('_farming_time');
if (timeElement) {
timeElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
};
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 applyTheme = (theme) => {
currentTheme = theme;
localStorage.setItem('duofarmer_theme', theme);
const container = document.getElementById("_container");
if (container) {
container.className = container.className.replace(/theme-\w+/, `theme-${theme}`);
}
};
const addEventListeners = () => {
document.getElementById('_fab')?.addEventListener('click', toggleInterface);
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('_theme_toggle')?.addEventListener('click', () => {
applyTheme(currentTheme === 'dark' ? 'light' : 'dark');
});
document.getElementById('_settings_btn')?.addEventListener('click', () => {
document.getElementById('_settings_modal').style.display = 'flex';
});
document.getElementById('_close_settings')?.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';
}
});
document.getElementById('_get_jwt_btn')?.addEventListener('click', () => {
const token = getJwtToken();
if (token) {
navigator.clipboard.writeText(token);
logToConsole('JWT Token copied to clipboard', 'success');
alert('JWT Token copied to clipboard!\n\nToken: ' + token.substring(0, 50) + '...');
} else {
logToConsole('JWT Token not found', 'error');
alert('JWT Token not found! Please make sure you are logged in to Duolingo.');
}
});
document.getElementById('_logout_btn')?.addEventListener('click', () => {
if (confirm('Are you sure you want to log out?')) {
window.location.href = 'https://www.duolingo.com/logout';
}
});
document.getElementById('_login_jwt_btn')?.addEventListener('click', () => {
const jwtInput = document.getElementById('_jwt_input');
const token = jwtInput.value.trim();
if (token) {
document.cookie = `jwt_token=${token}; path=/; domain=.duolingo.com`;
logToConsole('JWT Token updated, refreshing page...', 'success');
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
logToConsole('Please enter a valid JWT Token', 'error');
alert('Please enter a valid JWT Token');
}
});
document.getElementById('_website_btn')?.addEventListener('click', () => {
window.open('https://irylisvps.vercel.app/', '_blank');
});
document.getElementById('_discord_btn')?.addEventListener('click', () => {
window.open('https://discord.gg/Gvmd7deFtS', '_blank');
});
document.getElementById('_join_btn')?.addEventListener('click', () => {
window.open('https://discord.gg/Gvmd7deFtS', '_blank');
localStorage.setItem('duofarmer_joined', 'true');
hasJoined = true;
document.getElementById('_join_section').style.display = 'none';
document.getElementById('_main_content').style.display = 'flex';
initializeFarming();
});
document.querySelectorAll('._mode_card').forEach(card => {
card.addEventListener('click', () => {
document.querySelectorAll('._mode_card').forEach(c => c.classList.remove('_active'));
card.classList.add('_active');
currentMode = card.dataset.mode;
logToConsole(`Switched to ${currentMode} mode`, 'info');
});
});
document.querySelectorAll('._option_btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('._option_btn').forEach(b => b.classList.remove('_selected'));
btn.classList.add('_selected');
});
});
document.getElementById('_start_farming')?.addEventListener('click', startFarming);
document.getElementById('_stop_farming')?.addEventListener('click', stopFarming);
document.getElementById('_refresh_profile')?.addEventListener('click', async () => {
const btn = document.getElementById('_refresh_profile');
btn.style.animation = 'spin 1s linear';
await refreshUserData();
btn.style.animation = '';
});
document.getElementById('_clear_console')?.addEventListener('click', () => {
const console = document.getElementById('_console_output');
if (console) {
console.innerHTML = '';
logToConsole('Console cleared', 'info');
}
});
};
const startFarming = async () => {
if (isRunning) return;
const selectedOption = document.querySelector('._option_btn._selected');
if (!selectedOption) {
logToConsole('Please select a farming option', 'error');
return;
}
const type = selectedOption.dataset.type;
const delay = currentMode === 'safe' ? SAFE_DELAY : FAST_DELAY;
isRunning = true;
farmingStats.startTime = Date.now();
document.getElementById('_start_farming').style.display = 'none';
document.getElementById('_stop_farming').style.display = 'block';
logToConsole(`Started ${type} farming in ${currentMode} mode`, 'success');
const timer = setInterval(updateFarmingTime, 1000);
try {
switch (type) {
case 'xp':
await farmXP(delay);
break;
case 'gems':
await farmGems(delay);
break;
case 'streak_repair':
await repairStreak();
break;
case 'streak_farm':
await farmStreak();
break;
}
} catch (error) {
logToConsole(`Farming error: ${error.message}`, 'error');
} finally {
clearInterval(timer);
}
};
const stopFarming = () => {
if (!isRunning) return;
isRunning = false;
if (farmingInterval) {
clearInterval(farmingInterval);
farmingInterval = null;
}
document.getElementById('_start_farming').style.display = 'block';
document.getElementById('_stop_farming').style.display = 'none';
logToConsole('Farming stopped', 'info');
};
const farmXP = async (delayMs) => {
while (isRunning) {
try {
const response = await farmXpOnce();
if (response.ok) {
const data = await response.json();
const earned = data?.awardedXp || 0;
totalEarned.xp += earned;
updateEarnedStats();
logToConsole(`Earned ${earned} XP`, 'success');
}
await delay(delayMs);
} catch (error) {
logToConsole(`XP farming error: ${error.message}`, 'error');
await delay(delayMs * 2);
}
}
};
const farmGems = async (delayMs) => {
while (isRunning) {
try {
const response = await farmGemOnce();
if (response.ok) {
totalEarned.gems += 30;
updateEarnedStats();
logToConsole('Earned 30 gems', 'success');
}
await delay(delayMs);
} catch (error) {
logToConsole(`Gem farming error: ${error.message}`, 'error');
await delay(delayMs * 2);
}
}
};
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(currentMode === 'safe' ? SAFE_DELAY : FAST_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 farmStreak = 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(currentMode === 'safe' ? SAFE_DELAY : FAST_DELAY);
} catch (error) {
logToConsole(`Streak farming error: ${error.message}`, 'error');
await delay((currentMode === 'safe' ? SAFE_DELAY : FAST_DELAY) * 2);
}
}
};
const getJwtToken = () => {
let match = document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'));
if (match) {
return match[2];
}
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 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();
};
const updateUserInfo = () => {
if (!userInfo) return;
const elements = {
username: document.getElementById('_username'),
user_details: document.getElementById('_user_details'),
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.user_details) {
elements.user_details.textContent = `${userInfo.fromLanguage} → ${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', 'success');
} catch (error) {
logToConsole(`Failed to refresh: ${error.message}`, 'error');
}
};
const initializeFarming = async () => {
try {
jwt = getJwtToken();
if (!jwt) {
logToConsole('Please login to Duolingo and reload', 'error');
return false;
}
defaultHeaders = formatHeaders(jwt);
const decodedJwt = decodeJwtToken(jwt);
sub = decodedJwt.sub;
logToConsole('Loading user data...', 'info');
userInfo = await getUserInfo(sub);
if (userInfo && userInfo.username) {
updateUserInfo();
logToConsole(`Welcome ${userInfo.username}!`, 'success');
return true;
} else {
logToConsole('Failed to load user data', 'error');
return false;
}
} catch (error) {
logToConsole(`Init error: ${error.message}`, 'error');
return false;
}
};
(async () => {
try {
initInterface();
setInterfaceVisible(false);
applyTheme(currentTheme);
addEventListeners();
if (hasJoined) {
document.getElementById('_join_section').style.display = 'none';
document.getElementById('_main_content').style.display = 'flex';
initializeFarming();
}
logToConsole('DuoHacker v2.0 ready', 'success');
} catch (error) {
console.error('Init failed:', error);
}
})();