Advanced Duolingo automation - Auto-solve lessons, farm XP, Gems, Streaks with beautiful UI
// ==UserScript==
// @name DuoGang
// @license mit
// @namespace https://github.com/duochristian
// @version 1.0.0
// @description Advanced Duolingo automation - Auto-solve lessons, farm XP, Gems, Streaks with beautiful UI
// @author DuoGangTeam
// @match *://*.duolingo.com/*
// @match *://*.duolingo.cn/*
// @icon https://raw.githubusercontent.com/JULIANGAMER587/Duogang_icon/main/Screenshot%202026-05-07%20151236-Photoroom.png
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect duolingo.com
// @connect stories.duolingo.com
// @connect goals-api.duolingo.com
// @connect duolingo-leaderboards-prod.duolingo.com
// @connect fonts.googleapis.com
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// ==================== STYLES ====================
const fontLink = document.createElement('link');
fontLink.rel = 'stylesheet';
fontLink.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap';
document.head.appendChild(fontLink);
GM_addStyle(`
:root {
--DC-gold: 255, 179, 0;
--DC-gold-dark: 204, 143, 0;
--DC-blue: 28, 176, 246;
--DC-green: 88, 204, 2;
--DC-red: 255, 75, 75;
--DC-purple: 206, 130, 255;
/* Light mode defaults */
--DC-bg: rgba(255,255,255,0.95);
--DC-surface: rgba(248,248,248,0.9);
--DC-text: #1a1a1a;
--DC-text-sub: #888;
--DC-divider: rgba(0,0,0,0.08);
--DC-outline: rgba(0,0,0,0.08);
--DC-input-bg: rgba(255,179,0,0.05);
--DC-input-outline: rgba(255,179,0,0.2);
--DC-notif-bg: rgba(255,255,255,0.97);
--DC-shadow: rgba(0,0,0,0.10);
}
#DC_Root.DC_Dark {
--DC-bg: rgba(22,22,28,0.97);
--DC-surface: rgba(30,30,38,0.95);
--DC-text: #f0f0f0;
--DC-text-sub: #aaa;
--DC-divider: rgba(255,255,255,0.08);
--DC-outline: rgba(255,255,255,0.08);
--DC-input-bg: rgba(255,179,0,0.07);
--DC-input-outline: rgba(255,179,0,0.25);
--DC-notif-bg: rgba(28,28,36,0.98);
--DC-shadow: rgba(0,0,0,0.40);
}
#DC_Root * { box-sizing: border-box; }
#DC_Root p, #DC_Root span, #DC_Root button, #DC_Root input, #DC_Root label, #DC_Root div {
font-family: 'Inter', 'din-round', -apple-system, sans-serif !important;
}
#DC_Root p, #DC_Root span { margin: 0; padding: 0; }
.DC_Main {
display: inline-flex; flex-direction: column;
justify-content: flex-end; align-items: flex-end;
gap: 10px; position: fixed; right: 16px; bottom: 16px;
z-index: 2147483647; transition: bottom 0.6s cubic-bezier(0.16,1,0.32,1);
user-select: none;
}
.DC_Main.DC_Dragging { transition: none !important; }
@media (max-width: 699px) { .DC_Main { margin-bottom: 80px; } }
.DC_Main_Box {
display: flex; width: 320px; padding: 18px;
flex-direction: column; gap: 12px;
border-radius: 20px;
outline: 2px solid var(--DC-outline); outline-offset: -2px;
background: var(--DC-bg);
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px var(--DC-shadow);
transition: opacity 0.6s cubic-bezier(0.16,1,0.32,1), filter 0.6s cubic-bezier(0.16,1,0.32,1);
}
.DC_HStack { display:flex; align-items:center; gap:8px; align-self:stretch; }
.DC_HStack_Between { display:flex; align-items:center; justify-content:space-between; align-self:stretch; }
.DC_VStack { display:flex; flex-direction:column; gap:8px; align-self:stretch; }
.DC_Divider { height:1px; background:var(--DC-divider); align-self:stretch; flex-shrink:0; }
.DC_Title { font-size:15px; font-weight:700; color:var(--DC-text); }
.DC_Subtitle { font-size:12px; font-weight:500; color:var(--DC-text-sub); }
.DC_Btn {
display:flex; height:44px; padding:10px 16px;
align-items:center; gap:8px;
border-radius:12px; border:none; cursor:pointer;
font-size:14px; font-weight:700;
transition: all 0.3s cubic-bezier(0.16,1,0.32,1);
user-select:none; -webkit-user-select:none;
}
.DC_Btn:hover { filter:brightness(0.9); transform:scale(1.03); }
.DC_Btn:active { filter:brightness(0.85); transform:scale(0.97); }
.DC_Btn_Primary { background:rgb(var(--DC-gold)); color:#1a1a1a; }
.DC_Btn_Blue { background:rgb(var(--DC-blue)); color:#fff; }
.DC_Btn_Green { background:rgb(var(--DC-green)); color:#fff; }
.DC_Btn_Red { background:rgb(var(--DC-red)); color:#fff; }
.DC_Btn_Ghost {
background:rgba(var(--DC-gold),0.1); color:rgb(var(--DC-gold-dark));
outline:2px solid rgba(var(--DC-gold),0.2); outline-offset:-2px;
}
.DC_Btn_Icon { flex:none; width:44px; padding:10px; justify-content:center; }
.DC_Input_Wrap {
display:flex; height:48px; padding:0 16px;
align-items:center; gap:8px; flex:1;
border-radius:12px;
outline:2px solid var(--DC-input-outline); outline-offset:-2px;
background:var(--DC-input-bg);
}
.DC_Input {
border:none; outline:none; background:none;
font-size:16px; font-weight:600; color:rgb(var(--DC-gold-dark));
text-align:right; width:100%; font-family:inherit;
-moz-appearance:textfield;
}
.DC_Input::placeholder { color:rgba(var(--DC-gold-dark),0.4); }
.DC_Input::-webkit-outer-spin-button,
.DC_Input::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
.DC_Input_Btn {
display:flex; height:48px; padding:0 18px;
align-items:center; gap:6px;
border-radius:12px; border:none; cursor:pointer;
font-size:14px; font-weight:800; color:#1a1a1a;
background:rgb(var(--DC-gold));
transition: all 0.3s cubic-bezier(0.16,1,0.32,1);
white-space:nowrap; flex-shrink:0;
}
.DC_Input_Btn:hover { filter:brightness(0.9); transform:scale(1.03); }
.DC_Input_Btn:active { filter:brightness(0.85); transform:scale(0.97); }
.DC_Input_Btn:disabled { opacity:0.4; pointer-events:none; }
.DC_Prog_Wrap { height:0; border-radius:4px; background:rgba(var(--DC-gold),0.1); overflow:hidden; transition:height 0.4s; }
.DC_Prog_Wrap.on { height:4px; }
.DC_Prog_Fill { height:100%; border-radius:4px; background:linear-gradient(90deg, rgb(var(--DC-gold)), rgb(var(--DC-gold-dark))); width:0%; transition:width 0.5s; }
.DC_Page { display:none; flex-direction:column; gap:12px; }
.DC_Page.active { display:flex; }
.DC_Notif {
position:fixed; left:50%; transform:translateX(-50%); bottom:16px;
z-index:2147483647; padding:14px 18px;
border-radius:14px; background:var(--DC-notif-bg);
backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
box-shadow:0 8px 32px var(--DC-shadow);
font-size:13px; font-weight:600; color:var(--DC-text);
transition:all 0.4s cubic-bezier(0.16,1,0.32,1);
opacity:0; pointer-events:none;
}
.DC_Notif.show { opacity:1; pointer-events:auto; }
.DC_User_Row { display:none; gap:10px; }
.DC_Avatar { width:36px; height:36px; border-radius:50%; background:rgba(var(--DC-gold),0.1); overflow:hidden; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:18px; }
.DC_Stat { display:flex; align-items:center; gap:4px; }
.DC_Stat img { width:16px; height:16px; }
.DC_Stat span { font-size:12px; font-weight:600; color:var(--DC-text-sub); }
/* Drag handle cursor */
.DC_Drag_Handle { cursor: grab; }
.DC_Drag_Handle:active { cursor: grabbing; }
/* YouTube link button */
.DC_YT_Btn {
display:flex; align-items:center; gap:6px; height:44px; padding:0 12px;
border-radius:12px; border:none; cursor:pointer; text-decoration:none;
background:rgba(255,0,0,0.1); color:#cc0000;
outline:2px solid rgba(255,0,0,0.18); outline-offset:-2px;
font-size:12px; font-weight:700; transition:all 0.3s cubic-bezier(0.16,1,0.32,1);
white-space:nowrap; flex-shrink:0;
}
#DC_Root.DC_Dark .DC_YT_Btn { background:rgba(255,0,0,0.15); color:#ff5555; outline-color:rgba(255,80,80,0.25); }
.DC_YT_Btn:hover { background:rgba(255,0,0,0.18); transform:scale(1.04); }
.DC_YT_Btn:active { transform:scale(0.97); }
/* Dark mode toggle */
.DC_DarkToggle {
display:flex; align-items:center; justify-content:center;
width:44px; height:44px; border-radius:12px; border:none; cursor:pointer;
background:rgba(var(--DC-gold),0.10); outline:2px solid rgba(var(--DC-gold),0.2); outline-offset:-2px;
transition:all 0.3s cubic-bezier(0.16,1,0.32,1); flex-shrink:0;
}
.DC_DarkToggle:hover { filter:brightness(0.88); transform:scale(1.05); }
.DC_DarkToggle:active { transform:scale(0.95); }
/* Eye icon states */
.DC_Eye_Open { display:block; }
.DC_Eye_Closed { display:none; }
.DC_Hide_Btn_Hidden .DC_Eye_Open { display:none; }
.DC_Hide_Btn_Hidden .DC_Eye_Closed { display:block; }
.DC_Solver_Btn {
position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%);
z-index: 2147483647; display: flex; gap: 10px;
animation: DC_SlideUp 0.3s ease-out;
}
@keyframes DC_SlideUp {
from { opacity:0; transform:translateX(-50%) translateY(20px); }
to { opacity:1; transform:translateX(-50%) translateY(0); }
}
.DC_Solver_Btn button {
padding: 12px 24px; border:none; border-radius: 12px;
font-size: 14px; font-weight: 700; cursor:pointer;
transition: all 0.2s; color:#fff;
}
.DC_Solver_Btn button:hover { filter:brightness(1.1); transform:translateY(-2px); }
.DC_Solver_Btn button:active { transform:translateY(2px); }
.DC_Solve_One { background:rgb(var(--DC-green)); border-bottom:4px solid #46a302; }
.DC_Solve_All { background:rgb(var(--DC-gold)); border-bottom:4px solid rgb(var(--DC-gold-dark)); color:#1a1a1a; }
.DC_Toggle { position:relative; width:44px; height:26px; flex-shrink:0; cursor:pointer; }
.DC_Toggle input { opacity:0; width:0; height:0; }
.DC_Toggle_Slider { position:absolute; inset:0; background:rgba(0,0,0,0.15); border-radius:26px; transition:0.3s; }
.DC_Toggle_Slider:before { content:''; position:absolute; width:20px; height:20px; left:3px; bottom:3px; background:#fff; border-radius:50%; transition:0.3s; box-shadow:0 2px 4px rgba(0,0,0,0.2); }
.DC_Toggle input:checked + .DC_Toggle_Slider { background:rgb(var(--DC-gold)); }
.DC_Toggle input:checked + .DC_Toggle_Slider:before { transform:translateX(18px); }
`);
// ==================== HTML STRUCTURE ====================
const wrap = document.createElement('div');
wrap.id = 'DC_Root';
wrap.innerHTML = `
<div class="DC_Notif" id="DC_Notif"></div>
<div class="DC_Main" id="DC_Main">
<div class="DC_HStack" style="align-self:flex-end; gap:8px;">
<!-- YouTube link -->
<a class="DC_YT_Btn" href="https://www.youtube.com/@JULSEXE_Edits/shorts?app=desktop" target="_blank" rel="noopener noreferrer" title="Follow us on YouTube">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
</svg>
Follow us on YouTube
</a>
<!-- Dark mode toggle -->
<button class="DC_DarkToggle" id="DC_Dark_Btn" title="Toggle dark/light mode">
<svg id="DC_Dark_Icon_Moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
<svg id="DC_Dark_Icon_Sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="rgb(var(--DC-gold))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;">
<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>
<!-- Hide/Show button with eye icon -->
<div class="DC_Btn DC_Drag_Handle" id="DC_Hide_Btn" style="background:rgb(var(--DC-gold)); height:44px; padding:0 14px; gap:8px; border-radius:12px; cursor:pointer; display:flex; align-items:center;" title="Drag to move · Click to show/hide">
<!-- Eye Open (menu visible) -->
<svg class="DC_Eye_Open" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
<!-- Eye Closed (menu hidden) -->
<svg class="DC_Eye_Closed" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1a1a1a" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
<line x1="1" y1="1" x2="23" y2="23"/>
</svg>
<span style="color:#1a1a1a;font-size:13px;font-weight:700;" id="DC_Hide_Label">Hide</span>
</div>
</div>
<div class="DC_Main_Box" id="DC_Main_Box">
<div class="DC_Page active" id="DC_Page_Main">
<div class="DC_HStack">
<div class="DC_Btn DC_Btn_Ghost" id="DC_Conn_Btn" style="flex:1;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" id="DC_Conn_Ico">
<circle cx="12" cy="12" r="10" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" fill="none"/>
</svg>
<span class="DC_Title" id="DC_Conn_Txt" style="color:rgb(var(--DC-gold-dark));">Connecting</span>
</div>
</div>
<div class="DC_User_Row DC_HStack" id="DC_User_Row">
<div class="DC_Avatar" id="DC_Avatar">?</div>
<div style="flex:1;min-width:0;">
<p class="DC_Title" id="DC_UName" style="font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></p>
<div class="DC_HStack" style="gap:12px;">
<div class="DC_Stat"><img src="https://d35aaqx5ub95lt.cloudfront.net/images/profile/01ce3a817dd01842581c3d18debcbc46.svg"><span id="DC_UXP">0</span></div>
<div class="DC_Stat"><img src="https://d35aaqx5ub95lt.cloudfront.net/images/gems/45c14e05be9c1af1d7d0b54c6eed7eee.svg"><span id="DC_UGems">0</span></div>
<div class="DC_Stat"><img src="https://d35aaqx5ub95lt.cloudfront.net/images/icons/398e4298a3b39ce566050e5c041949ef.svg"><span id="DC_UStreak">0</span></div>
</div>
</div>
</div>
<div class="DC_Divider"></div>
<div class="DC_VStack">
<p class="DC_Title">XP to farm</p>
<div class="DC_HStack">
<div class="DC_Input_Wrap">
<input type="number" class="DC_Input" id="DC_XP_Input" placeholder="0" min="30" max="500000">
</div>
<button class="DC_Input_Btn" id="DC_XP_Btn" disabled>
<span>GET XP</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
</button>
</div>
<div class="DC_Prog_Wrap" id="DC_XP_Prog"><div class="DC_Prog_Fill" id="DC_XP_Fill"></div></div>
</div>
<div class="DC_VStack">
<p class="DC_Title">Farm Gems</p>
<div class="DC_HStack">
<div class="DC_Input_Wrap">
<input type="number" class="DC_Input" id="DC_Gem_Input" placeholder="0" readonly>
</div>
<button class="DC_Input_Btn" id="DC_Gem_Btn" disabled>
<span>RUN</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
</button>
</div>
</div>
<div class="DC_VStack">
<p class="DC_Title">Streak days to add</p>
<div class="DC_HStack">
<div class="DC_Input_Wrap">
<input type="number" class="DC_Input" id="DC_Streak_Input" placeholder="0" min="1" max="3650">
</div>
<button class="DC_Input_Btn" id="DC_Streak_Btn" disabled>
<span>RUN</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
</button>
</div>
<div class="DC_Prog_Wrap" id="DC_Streak_Prog"><div class="DC_Prog_Fill" id="DC_Streak_Fill"></div></div>
</div>
<div class="DC_Divider"></div>
<div class="DC_HStack_Between">
<button class="DC_Btn DC_Btn_Ghost" id="DC_Practice_Btn" style="flex:1;justify-content:center;" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" stroke-linecap="round"/></svg>
Practice Farm
</button>
<button class="DC_Btn DC_Btn_Ghost" id="DC_Super_Btn" style="flex:1;justify-content:center;" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14 2 9.27l6.91-1.01L12 2z" fill="rgb(var(--DC-gold-dark))"/></svg>
Free Super
</button>
</div>
<div class="DC_HStack_Between">
<div class="DC_HStack" style="gap:4px;">
<span class="DC_Subtitle">Auto Solver</span>
<label class="DC_Toggle">
<input type="checkbox" id="DC_Solver_Toggle">
<span class="DC_Toggle_Slider"></span>
</label>
</div>
<span class="DC_Subtitle">DuoGang v2.0.2</span>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(wrap);
// ==================== STATE ====================
let jwt = null, sub = null, headers = null, user = null;
let running = false, task = null, hidden = false;
let delay = parseInt(localStorage.getItem('dc_delay') || '500', 10);
let earnedGems = 0;
const sleep = ms => new Promise(r => setTimeout(r, ms));
// ==================== HELPERS ====================
function getJwt() {
const m = document.cookie.match(/(^| )jwt_token=([^;]+)/);
return m ? m[2] : null;
}
function decodeJwt(t) {
try {
const b = t.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
const pad = b.padEnd(b.length + (4 - b.length % 4) % 4, '=');
return JSON.parse(decodeURIComponent(atob(pad).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')));
} catch { return null; }
}
function buildHeaders(j) {
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + j, 'User-Agent': navigator.userAgent };
}
function gm(method, url, data, hdrs) {
return new Promise((res, rej) => GM_xmlhttpRequest({
method, url, headers: hdrs || headers,
data: data ? JSON.stringify(data) : null,
onload: r => res(r), onerror: () => rej(new Error('Network')),
timeout: 15000, ontimeout: () => rej(new Error('Timeout'))
}));
}
function notif(msg, dur = 4) {
const el = document.getElementById('DC_Notif');
el.textContent = msg;
el.classList.add('show');
clearTimeout(window._dcNotifTimer);
window._dcNotifTimer = setTimeout(() => el.classList.remove('show'), dur * 1000);
}
function setConn(state, label) {
const btn = document.getElementById('DC_Conn_Btn');
const txt = document.getElementById('DC_Conn_Txt');
const ico = document.getElementById('DC_Conn_Ico');
txt.textContent = label || state;
ico.innerHTML = state === 'Connected' ?
'<circle cx="12" cy="12" r="10" fill="rgb(var(--DC-green))"/><path d="M7 12l3 3 7-7" stroke="#fff" stroke-width="2" stroke-linecap="round"/>' :
'<circle cx="12" cy="12" r="10" stroke="rgb(var(--DC-gold-dark))" stroke-width="2" fill="none"/>';
if (state === 'Connected') {
document.getElementById('DC_User_Row').style.display = 'flex';
}
}
function setBtnState(btnId, cfg, label) {
const btn = document.getElementById(btnId);
if (!btn) return;
btn.disabled = false;
btn.className = cfg.cls || 'DC_Input_Btn';
const span = btn.querySelector('span');
if (span) span.textContent = label;
}
function resetBtn(btnId, label) {
const btn = document.getElementById(btnId);
if (!btn) return;
btn.disabled = !user;
setBtnState(btnId, { cls: 'DC_Input_Btn' }, label);
const prog = document.getElementById(btnId.replace('_Btn', '_Prog'));
if (prog) setTimeout(() => prog.classList.remove('on'), 2000);
}
function setBtnProgress(btnId, pct) {
const fill = document.getElementById(btnId.replace('_Btn', '_Fill'));
if (fill) fill.style.width = pct + '%';
const btn = document.getElementById(btnId);
if (btn) {
const span = btn.querySelector('span');
if (span) span.textContent = pct + '%';
}
}
function setBtnRunning(btnId) {
const btn = document.getElementById(btnId);
if (!btn) return;
btn.disabled = false;
setBtnState(btnId, { cls: 'DC_Input_Btn DC_Btn_Red' }, 'STOP');
const prog = document.getElementById(btnId.replace('_Btn', '_Prog'));
if (prog) prog.classList.add('on');
}
function setBtnDone(btnId, label) {
setBtnState(btnId, { cls: 'DC_Input_Btn DC_Btn_Green' }, label || 'DONE');
const fill = document.getElementById(btnId.replace('_Btn', '_Fill'));
if (fill) fill.style.width = '100%';
}
// ==================== CONNECTION ====================
async function connect() {
setConn('Connecting');
jwt = getJwt();
if (!jwt) { setConn('Not logged in'); return; }
const dec = decodeJwt(jwt);
if (!dec) { setConn('Invalid token'); return; }
sub = dec.sub;
headers = buildHeaders(jwt);
try {
const r = await gm('GET', `https://www.duolingo.com/2017-06-30/users/${sub}?fields=id,username,fromLanguage,learningLanguage,streak,totalXp,gems,picture,streakData`);
if (r.status !== 200) throw new Error(r.status);
user = JSON.parse(r.responseText);
setConn('Connected');
renderUser(user);
['DC_XP_Btn', 'DC_Gem_Btn', 'DC_Streak_Btn', 'DC_Practice_Btn', 'DC_Super_Btn'].forEach(id => {
const b = document.getElementById(id);
if (b) b.disabled = false;
});
} catch (e) {
setConn('Error - retrying');
setTimeout(connect, 8000);
}
}
function renderUser(u) {
if (!u) return;
document.getElementById('DC_UName').textContent = u.username || '';
document.getElementById('DC_UXP').textContent = (u.totalXp || 0).toLocaleString();
document.getElementById('DC_UGems').textContent = (u.gems || 0).toLocaleString();
document.getElementById('DC_UStreak').textContent = (u.streak || 0).toLocaleString();
if (u.picture) {
let hq = u.picture.replace(/\/(medium|large|small)$/, '/xlarge');
if (!hq.endsWith('/xlarge') && hq.includes('duolingo.com/ssr-avatars')) hq += '/xlarge';
const av = document.getElementById('DC_Avatar');
const img = document.createElement('img');
img.src = hq;
img.style.cssText = 'width:100%;height:100%;object-fit:cover;border-radius:50%;';
img.onerror = function() { av.innerHTML = '?'; };
av.innerHTML = '';
av.appendChild(img);
}
}
// ==================== XP FARM ====================
async function farmXP(txp) {
const MAX = 499;
let loops = Math.floor(txp / MAX), rem = txp % MAX;
if (rem > 0 && rem < 30 && loops > 0) { loops--; rem += MAX; }
const total = loops + (rem >= 30 ? 1 : 0);
let cur = 0, earned = 0;
setBtnRunning('DC_XP_Btn');
for (let i = 0; i < loops; i++) {
if (!running) break;
const ok = await storyXP(469);
if (ok) { earned += MAX; cur++; }
setBtnProgress('DC_XP_Btn', Math.floor((cur / total) * 100));
await sleep(delay);
}
if (rem >= 30 && running) {
const ok = await storyXP(Math.min(rem - 30, 469));
if (ok) { earned += rem; cur++; }
setBtnProgress('DC_XP_Btn', 100);
}
if (running) {
setBtnDone('DC_XP_Btn', 'DONE');
notif('Farmed ' + earned.toLocaleString() + ' XP!');
setTimeout(connect, 1500);
setTimeout(() => resetBtn('DC_XP_Btn', 'GET XP'), 3000);
}
}
async function storyXP(hh) {
try {
const now = Math.floor(Date.now() / 1000), dur = Math.floor(Math.random() * 121 + 300);
const r = await gm('POST', 'https://stories.duolingo.com/api2/stories/fr-en-le-passeport/complete', {
awardXp: true, completedBonusChallenge: true,
fromLanguage: 'fr', learningLanguage: 'en',
hasXpBoost: false, illustrationFormat: 'svg',
isFeaturedStoryInPracticeHub: true, isLegendaryMode: true,
isV2Redo: false, isV2Story: false, masterVersion: true,
maxScore: 0, score: 0, happyHourBonusXp: hh,
startTime: now, endTime: now + dur
});
return r.status === 200;
} catch { return false; }
}
// ==================== GEM FARM ====================
async function getGemRewards() {
try {
const r = await gm('GET', `https://www.duolingo.com/2023-05-23/users/${sub}?fields=rewardBundles`);
if (r.status !== 200) return [];
const bundles = JSON.parse(r.responseText).rewardBundles || [];
const gems = [];
for (const bundle of bundles) {
for (const reward of bundle.rewards || []) {
if (!reward.consumed && (reward.id.includes('GEMS') || reward.currency === 'GEMS')) {
gems.push({ id: reward.id, amount: reward.amount || 0 });
}
}
}
return gems;
} catch { return []; }
}
async function exploitGemReward(rewardId) {
const body = {
consumed: true,
fromLanguage: user.fromLanguage,
learningLanguage: user.learningLanguage,
pathLevelSpecifics: {
anchorSkillId: 'f22fd38157eea63965dc39eeac3c40c1',
indexSinceAnchorSkill: 0,
treeId: '14b1a2672c1bb3b250ebaa31b86c343e',
nodeState: 'active'
}
};
try {
const r = await gm('PATCH', `https://www.duolingo.com/2023-05-23/users/${sub}/rewards/${rewardId}`, body);
return r.status === 200;
} catch { return false; }
}
async function getGemCount() {
try {
const r = await gm('GET', `https://www.duolingo.com/2023-05-23/users/${sub}?fields=gemsConfig`);
if (r.status !== 200) return null;
return JSON.parse(r.responseText).gemsConfig?.gems ?? null;
} catch { return null; }
}
async function farmGems() {
earnedGems = 0;
const gemInput = document.getElementById('DC_Gem_Input');
if (gemInput) gemInput.value = '';
setBtnRunning('DC_Gem_Btn');
outer: while (running && task === 'gems') {
const rewards = await getGemRewards();
if (rewards.length === 0) { await sleep(delay * 2); continue; }
let gemsBefore = await getGemCount() ?? (user?.gems ?? 0);
const THREADS = 3;
for (let i = 0; i < rewards.length; i += THREADS) {
if (!running || task !== 'gems') break outer;
const batch = rewards.slice(i, i + THREADS);
await Promise.all(batch.map(r => exploitGemReward(r.id)));
await sleep(200);
const now = await getGemCount();
if (now !== null) {
const gained = Math.max(0, now - gemsBefore);
earnedGems += gained;
gemsBefore = now;
if (gemInput) gemInput.value = String(earnedGems);
if (user) { user.gems = now; renderUser(user); }
}
await sleep(Math.max(100, delay));
}
await sleep(delay);
}
resetBtn('DC_Gem_Btn', 'RUN');
const btn = document.getElementById('DC_Gem_Btn');
if (btn) btn.disabled = !user;
if (earnedGems > 0) {
notif('+' + earnedGems.toLocaleString() + ' gems gained!');
setTimeout(connect, 1500);
}
}
// ==================== STREAK FARM ====================
async function farmStreak(days) {
const CH = ["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"];
let farmStart;
try {
const s = new Date(user.streakData?.currentStreak?.startDate || Date.now());
s.setDate(s.getDate() - 1);
farmStart = s;
} catch { const n = new Date(); n.setDate(n.getDate() - 1); farmStart = n; }
setBtnRunning('DC_Streak_Btn');
for (let i = 0; i < days; i++) {
if (!running) break;
const simDay = new Date(farmStart);
simDay.setDate(simDay.getDate() - i);
const end = Math.floor(simDay.getTime() / 1000);
try {
const sr = await gm('POST', 'https://www.duolingo.com/2023-05-23/sessions', {
challengeTypes: CH, fromLanguage: user.fromLanguage, isFinalLevel: false, isV2: true,
juicy: true, learningLanguage: user.learningLanguage, smartTipsVersion: 2, type: 'GLOBAL_PRACTICE'
});
if (sr.status === 200) {
const sess = JSON.parse(sr.responseText);
await new Promise((res, rej) => GM_xmlhttpRequest({
method: 'PUT', url: `https://www.duolingo.com/2023-05-23/sessions/${sess.id}`,
headers: headers, data: JSON.stringify({
...sess, heartsLeft: 5, startTime: end - 1, endTime: end,
enableBonusPoints: false, failed: false, maxInLessonStreak: 9, shouldLearnThings: true
}),
onload: r => res(r), onerror: () => rej(), timeout: 15000, ontimeout: () => rej()
}));
}
} catch { }
setBtnProgress('DC_Streak_Btn', Math.floor(((i + 1) / days) * 100));
await sleep(delay);
}
if (running) {
setBtnDone('DC_Streak_Btn', 'DONE');
notif('Restored ' + days + ' streak days!');
setTimeout(connect, 1500);
setTimeout(() => resetBtn('DC_Streak_Btn', 'RUN'), 3000);
}
}
// ==================== RUN LOGIC ====================
async function run(type, val) {
if (running) { running = false; notif('Farm stopped.'); return; }
if (!user) { notif('Please wait for connection.'); return; }
running = true; task = type;
try {
if (type === 'xp') await farmXP(val);
if (type === 'gems') await farmGems();
if (type === 'streak') await farmStreak(val);
} catch (e) { notif('Error: ' + e.message); }
if (!running) {
if (type === 'xp') resetBtn('DC_XP_Btn', 'GET XP');
if (type === 'streak') resetBtn('DC_Streak_Btn', 'RUN');
}
running = false; task = null;
}
// ==================== AUTO SOLVER ====================
let solverUI = null;
let isAutoMode = false;
let solverEnabled = localStorage.getItem('dc_solver') === 'true';
const solver = {
findReact: (dom, traverseUp = 1) => {
const key = Object.keys(dom).find(k => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$"));
if (!key) return null;
const domFiber = dom[key];
if (!domFiber) return null;
const GetCompFiber = fiber => {
let parentFiber = fiber.return;
while (typeof parentFiber.type == "string") parentFiber = parentFiber.return;
return parentFiber;
};
let compFiber = GetCompFiber(domFiber);
for (let i = 0; i < traverseUp; i++) compFiber = GetCompFiber(compFiber);
return compFiber.stateNode;
},
determineType: () => {
try {
const t = window.sol?.type;
if (!t) return false;
if (t === 'speak' || t === 'listenSpeak' || document.querySelector('[data-test*="challenge-speak"]')) return 'Challenge Speak';
if (t === 'listenMatch') return 'Listen Match';
if (document.querySelector('.FmlUF')) {
if (t === 'arrange') return 'Story Arrange';
if (t === 'multiple-choice' || t === 'select-phrases') return 'Story Multiple Choice';
if (t === 'point-to-phrase') return 'Story Point to Phrase';
if (t === 'match') return 'Story Pairs';
}
if (t === 'typeCloze') return 'Type Cloze';
if (t === 'typeClozeTable') return 'Type Cloze Table';
if (t === 'tapClozeTable') return 'Tap Cloze Table';
if (t === 'typeCompleteTable') return 'Type Complete Table';
if (t === 'tapCompleteTable') return 'Tap Complete Table';
if (t === 'patternTapComplete') return 'Pattern Tap Complete';
if (t === 'syllableTap') return 'Syllable Tap';
if (t === 'syllableListenTap') return 'Syllable Listen Tap';
if (t === 'listenTap') return 'Listen Tap';
if (t === 'listen') return 'Listen Type';
if (t === 'translate') return 'Translate';
if (t === 'completeReverseTranslation') return 'Complete Reverse';
if (document.querySelector('[data-test*="challenge-partialReverseTranslate"]')) return 'Partial Reverse';
if (document.querySelector('[data-test="challenge challenge-characterWrite"]')) {
if (document.querySelector('g._25Ktp')) return 'Character Write Drag';
if (document.querySelector('path._1e5Zt')) return 'Character Write Draw';
return 'Character Write Freehand';
}
if (t === 'judge') return 'Judge';
if (t === 'dialogue' || t === 'characterIntro' || t === 'selectTranscription') return 'Dialogue';
if (t === 'characterMatch' || t === 'match') {
if (document.querySelector('[data-test$="challenge-tap-token"]')) return 'Pairs';
}
if (t === 'select' || t === 'characterSelect' || t === 'form' || t === 'readComprehension' || t === 'listenComprehension' || t === 'selectPronunciation') return 'Select Card';
if (document.querySelector('[data-test*="challenge-name"]') && document.querySelector('[data-test="challenge-choice"]')) return 'Challenge Name';
if (document.querySelector('[data-test="challenge-choice"]')) {
if (document.querySelector('[data-test="challenge-text-input"]')) return 'Challenge Choice with Text Input';
return 'Challenge Choice';
}
if (document.querySelector('[data-test$="challenge-tap-token"]')) {
if (window.sol?.pairs !== undefined) return 'Pairs';
if (window.sol?.correctTokens !== undefined) return 'Tokens Run';
if (window.sol?.correctIndices !== undefined) return 'Indices Run';
}
if (document.querySelector('[data-test="challenge-tap-token-text"]')) return 'Fill in the Gap';
if (document.querySelector('[data-test="challenge-text-input"]')) return 'Challenge Text Input';
if (document.querySelector('textarea[data-test="challenge-translate-input"]')) return 'Challenge Translate Input';
return false;
} catch { return false; }
},
setValue: (el, val) => {
const proto = el.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement : window.HTMLInputElement;
const setter = Object.getOwnPropertyDescriptor(proto.prototype, "value").set;
setter.call(el, val);
el.dispatchEvent(new Event('input', { bubbles: true }));
},
handle: async (type) => {
const sleep = ms => new Promise(r => setTimeout(r, ms));
try {
switch (type) {
case 'Challenge Speak':
case 'Listen Match':
case 'Listen Speak':
document.querySelector('button[data-test="player-skip"]')?.click();
break;
case 'Select Card': {
const idx = window.sol.correctIndex ?? 0;
const cards = document.querySelectorAll('[data-test="challenge-choice-card"]');
if (cards.length) cards[idx]?.click();
else document.querySelectorAll('[data-test="challenge-choice"]')[idx]?.click();
break;
}
case 'Judge': {
const ci = window.sol.correctIndices?.[0] ?? 0;
document.querySelectorAll('[data-test="challenge-judge-text"]')[ci]?.click();
break;
}
case 'Dialogue': {
const idx = window.sol.correctIndex ?? 0;
const judges = document.querySelectorAll('[data-test="challenge-judge-text"]');
if (judges.length) judges[idx]?.click();
else document.querySelectorAll('[data-test="challenge-choice"]')[idx]?.click();
break;
}
case 'Translate': {
const { correctTokens, correctSolutions } = window.sol;
if (correctTokens?.length) {
const tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
const used = [];
for (const word of correctTokens) {
for (let i = 0; i < tokens.length; i++) {
if (used.includes(i) || tokens[i].disabled) continue;
if (tokens[i].innerText.trim() === word.trim()) {
tokens[i].click();
used.push(i);
await sleep(40);
break;
}
}
}
} else if (correctSolutions) {
const ta = document.querySelector('textarea[data-test="challenge-translate-input"]');
if (ta) solver.setValue(ta, correctSolutions[0]);
}
break;
}
case 'Listen Tap': {
const tokens = document.querySelectorAll('[data-test$="challenge-tap-token"]');
const used = [];
for (const word of (window.sol.correctTokens || [])) {
for (let i = 0; i < tokens.length; i++) {
if (used.includes(i) || tokens[i].disabled) continue;
if (tokens[i].innerText.trim() === word.trim()) {
tokens[i].click();
used.push(i);
await sleep(40);
break;
}
}
}
break;
}
case 'Listen Type': {
const ans = window.sol.prompt || window.sol.correctSolutions?.[0] || '';
const ta = document.querySelector('textarea[data-test="challenge-translate-input"]') || document.querySelector('[data-test="challenge-text-input"]');
if (ta) solver.setValue(ta, ans);
break;
}
case 'Complete Reverse': {
const blankTokens = window.sol.displayTokens?.filter(t => t.isBlank);
const inputs = document.querySelectorAll('[data-test="challenge-text-input"]');
inputs.forEach((inp, i) => { if (blankTokens?.[i]) solver.setValue(inp, blankTokens[i].text); });
break;
}
case 'Challenge Choice':
document.querySelectorAll("[data-test='challenge-choice']")[window.sol.correctIndex]?.click();
break;
case 'Challenge Choice with Text Input': {
const inp = document.querySelector('[data-test="challenge-text-input"]');
if (inp) {
const ans = window.sol.correctSolutions ? window.sol.correctSolutions[0].split(/(?<=^\S+)\s/)[1] : (window.sol.displayTokens?.find(t => t.isBlank)?.text || window.sol.prompt);
solver.setValue(inp, ans);
}
break;
}
case 'Challenge Text Input': {
const inp = document.querySelector('[data-test="challenge-text-input"]');
if (inp) solver.setValue(inp, window.sol.correctSolutions?.[0] || window.sol.displayTokens?.find(t => t.isBlank)?.text || window.sol.prompt);
break;
}
case 'Challenge Translate Input': {
const ta = document.querySelector('textarea[data-test="challenge-translate-input"]');
if (ta) solver.setValue(ta, window.sol.correctSolutions?.[0] || window.sol.prompt);
break;
}
case 'Partial Reverse': {
const el = document.querySelector('[data-test*="challenge-partialReverseTranslate"]')?.querySelector("span[contenteditable]");
if (el) {
const txt = window.sol?.displayTokens?.filter(t => t.isBlank)?.map(t => t.text)?.join('')?.trim() || '';
el.textContent = txt;
el.dispatchEvent(new Event('input', { bubbles: true }));
}
break;
}
case 'Type Cloze': {
const inp = document.querySelector('input[type="text"].b4jqk');
if (inp) {
const tok = window.sol.displayTokens.find(t => t.damageStart !== undefined);
if (tok) solver.setValue(inp, tok.text.slice(tok.damageStart));
}
break;
}
case 'Type Cloze Table': {
const rows = document.querySelectorAll('tbody tr');
window.sol.displayTableTokens.slice(1).forEach((rowToks, i) => {
const cell = rowToks[1]?.find(t => typeof t.damageStart === "number");
if (cell && rows[i]) {
const inp = rows[i].querySelector('input[type="text"].b4jqk');
if (inp) solver.setValue(inp, cell.text.slice(cell.damageStart));
}
});
break;
}
case 'Tap Cloze Table': {
const rows = document.querySelectorAll('tbody tr');
window.sol.displayTableTokens.slice(1).forEach((rowToks, i) => {
const cell = rowToks[1]?.find(t => typeof t.damageStart === "number");
if (!cell || !rows[i]) return;
const bank = document.querySelector('[data-test="word-bank"]');
const btns = bank ? Array.from(bank.querySelectorAll('button[data-test*="challenge-tap-token"]:not([aria-disabled="true"])')) : [];
const correct = cell.text.slice(cell.damageStart);
let matched = "";
for (const btn of btns) {
if (!correct.startsWith(matched + btn.innerText)) continue;
btn.click();
matched += btn.innerText;
if (matched === correct) break;
}
});
break;
}
case 'Type Complete Table': {
const rows = document.querySelectorAll('tbody tr');
window.sol.displayTableTokens.slice(1).forEach((rowToks, i) => {
const cell = rowToks[1]?.find(t => t.isBlank);
if (cell && rows[i]) {
const inp = rows[i].querySelector('input[type="text"].b4jqk');
if (inp) solver.setValue(inp, cell.text);
}
});
break;
}
case 'Pattern Tap Complete': {
const bank = document.querySelector('[data-test="word-bank"]');
if (bank) {
const idx = window.sol.correctIndex ?? 0;
const txt = window.sol.choices[idx];
const btn = Array.from(bank.querySelectorAll('button[data-test*="challenge-tap-token"]:not([aria-disabled="true"])')).find(b => b.innerText.trim() === txt);
if (btn) btn.click();
}
break;
}
case 'Story Arrange': {
const choices = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
for (const idx of window.sol.phraseOrder) {
choices[idx]?.click();
await sleep(50);
}
break;
}
case 'Story Multiple Choice': {
const choices = document.querySelectorAll('[data-test="stories-choice"]');
choices[window.sol.correctAnswerIndex]?.click();
break;
}
case 'Story Point to Phrase': {
const choices = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
let ci = -1;
for (let i = 0; i < window.sol.parts.length; i++) {
if (window.sol.parts[i].selectable) ci++;
if (window.sol.correctAnswerIndex === i) {
choices[ci]?.parentElement.click();
break;
}
}
break;
}
case 'Story Pairs': {
const btns = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
const texts = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
const map = new Map();
for (let i = 0; i < btns.length; i++) map.set(texts[i].innerText.toLowerCase().trim(), btns[i]);
for (const key in window.sol.dictionary) {
const val = window.sol.dictionary[key];
const k = key.split(":")[1].toLowerCase().trim();
const v = val.toLowerCase().trim();
map.get(k)?.click();
await sleep(50);
map.get(v)?.click();
await sleep(50);
}
break;
}
case 'Challenge Name': {
const arts = window.sol.articles;
const correct = window.sol.correctSolutions[0];
const match = arts.find(a => correct.startsWith(a));
if (match) {
const idx = arts.indexOf(match);
const sel = document.querySelector(`[data-test="challenge-choice"]:nth-child(${idx + 1})`);
if (sel) { sel.click(); await sleep(50); }
const inp = document.querySelector('[data-test="challenge-text-input"]');
if (inp) solver.setValue(inp, correct.substring(match.length).trim());
}
break;
}
case 'Pairs': {
const btns = document.querySelectorAll('[data-test*="challenge-tap-token"]:not(span)');
const texts = document.querySelectorAll('[data-test="challenge-tap-token-text"]');
if (texts.length !== btns.length || !btns.length) break;
for (const pair of window.sol.pairs || []) {
for (let i = 0; i < btns.length; i++) {
if (btns[i].disabled) continue;
const t = texts[i].innerText.toLowerCase().trim();
if (t === pair.transliteration?.toLowerCase().trim() || t === pair.character?.toLowerCase().trim() || t === pair.learningToken?.toLowerCase().trim() || t === pair.fromToken?.toLowerCase().trim()) {
btns[i].click();
await sleep(50);
}
}
}
break;
}
case 'Tokens Run': {
const all = document.querySelectorAll('[data-test$="challenge-tap-token"]');
const clicked = [];
for (const ct of window.sol.correctTokens) {
const match = Array.from(all).filter(el => el.textContent.trim() === ct.trim());
const idx = clicked.filter(t => t.textContent.trim() === ct.trim()).length;
const btn = match[idx] || match[0];
if (btn && !btn.disabled) { btn.click(); clicked.push(btn); }
}
break;
}
case 'Indices Run':
case 'Fill in the Gap': {
if (!window.sol.correctIndices) break;
const bank = document.querySelector('div[data-test="word-bank"]') || document.querySelector('.eSgkc');
if (!bank) break;
const btns = Array.from(bank.querySelectorAll('button[data-test*="challenge-tap-token"]:not(span)'));
for (const idx of window.sol.correctIndices) {
if (idx >= 0 && idx < btns.length && !btns[idx].disabled && btns[idx].getAttribute('aria-disabled') !== 'true') {
btns[idx].click();
await sleep(50);
}
}
break;
}
case 'Tap Complete Table': {
const rows = document.querySelectorAll('tbody tr');
const solRows = window.sol.displayTableTokens.slice(1);
const bank = document.querySelector('div[data-test="word-bank"]');
const bankBtns = bank ? Array.from(bank.querySelectorAll('button[data-test*="-challenge-tap-token"]')) : [];
const used = new Set();
for (let ri = 0; ri < solRows.length; ri++) {
const cell = solRows[ri][1]?.find(t => t.isBlank);
if (!cell) continue;
const txt = cell.text;
const rowBtns = rows[ri]?.querySelectorAll('button[data-test*="-challenge-tap-token"]');
let clicked = false;
if (rowBtns) {
for (const btn of rowBtns) {
const tEl = btn.querySelector('[data-test="challenge-tap-token-text"]');
if (tEl?.innerText.trim() === txt && !btn.disabled) { btn.click(); await sleep(50); clicked = true; break; }
}
}
if (!clicked && bankBtns.length) {
for (let i = 0; i < bankBtns.length; i++) {
if (used.has(i)) continue;
const tEl = bankBtns[i].querySelector('[data-test="challenge-tap-token-text"]');
if (tEl?.innerText.trim() === txt && !bankBtns[i].disabled) { bankBtns[i].click(); await sleep(50); used.add(i); break; }
}
}
}
break;
}
case 'Syllable Tap':
case 'Syllable Listen Tap': {
const indices = window.sol.correctIndices;
const choices = window.sol.choices;
const domBtns = Array.from(document.querySelectorAll('[data-test="word-bank"] [data-test$="challenge-tap-token"]'));
indices.forEach(idx => {
const txt = choices[idx].text;
const btn = domBtns.find(b => b.innerText.trim() === txt);
if (btn) btn.click();
});
break;
}
case 'Character Write Drag': {
const createEvent = (t, x, y, btns) => new MouseEvent(t, { bubbles: true, clientX: x, clientY: y, buttons: btns, button: 0 });
const norm = s => s ? s.replace(/\s/g, '') : '';
for (const stroke of window.sol.strokes) {
const target = norm(stroke.path);
let path, handle;
while (!path || !handle) {
const cands = document.querySelectorAll('path._1e5Zt');
path = Array.from(cands).find(p => norm(p.getAttribute('d')) === target);
handle = document.querySelector('g._25Ktp');
if (!path || !handle) await sleep(10);
}
const mat = path.getScreenCTM(), len = path.getTotalLength();
const start = path.getPointAtLength(0).matrixTransform(mat);
const end = path.getPointAtLength(len).matrixTransform(mat);
handle.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
for (let s = 1; s <= 10; s++) {
const p = path.getPointAtLength((s / 10) * len).matrixTransform(mat);
const m = createEvent('mousemove', p.x, p.y, 1);
handle.dispatchEvent(m); document.dispatchEvent(m);
}
const fm = createEvent('mousemove', end.x, end.y, 1);
handle.dispatchEvent(fm); document.dispatchEvent(fm);
await sleep(5);
handle.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
document.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
}
break;
}
case 'Character Write Draw': {
const createEvent = (t, x, y, btns) => new MouseEvent(t, { bubbles: true, clientX: x, clientY: y, buttons: btns, button: 0 });
const norm = s => s ? s.replace(/\s/g, '') : '';
for (const stroke of window.sol.strokes) {
const target = norm(stroke.path);
let path, cursor;
while (!path || !cursor) {
const cands = document.querySelectorAll('path._1e5Zt');
path = Array.from(cands).find(p => norm(p.getAttribute('d')) === target);
cursor = document.querySelector('g._1h31R:not(._25Ktp)');
if (!path || !cursor) await sleep(10);
}
const mat = path.getScreenCTM(), len = path.getTotalLength();
const start = path.getPointAtLength(0).matrixTransform(mat);
const end = path.getPointAtLength(len).matrixTransform(mat);
cursor.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
document.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
for (let s = 1; s <= 10; s++) {
const p = path.getPointAtLength((s / 10) * len).matrixTransform(mat);
const m = createEvent('mousemove', p.x, p.y, 1);
cursor.dispatchEvent(m); document.dispatchEvent(m);
}
const fm = createEvent('mousemove', end.x, end.y, 1);
cursor.dispatchEvent(fm); document.dispatchEvent(fm);
await sleep(5);
cursor.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
document.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
}
break;
}
case 'Character Write Freehand': {
const createEvent = (t, x, y, btns) => new MouseEvent(t, { bubbles: true, clientX: x, clientY: y, buttons: btns, button: 0 });
const norm = s => s ? s.replace(/\s/g, '') : '';
const freeStrokes = window.sol.strokes.filter(s => s.strokeDrawMode === 'FREEHAND');
for (const stroke of freeStrokes) {
const target = norm(stroke.path);
let path, svg;
while (!path || !svg) {
const cands = document.querySelectorAll('path._22UPm');
path = Array.from(cands).find(p => norm(p.getAttribute('d')) === target);
svg = document.querySelector('svg.o1rqi');
if (!path || !svg) await sleep(10);
}
const mat = path.getScreenCTM(), len = path.getTotalLength();
const start = path.getPointAtLength(0).matrixTransform(mat);
const end = path.getPointAtLength(len).matrixTransform(mat);
svg.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
document.dispatchEvent(createEvent('mousedown', start.x, start.y, 1));
for (let s = 1; s <= 10; s++) {
const p = path.getPointAtLength((s / 10) * len).matrixTransform(mat);
const m = createEvent('mousemove', p.x, p.y, 1);
svg.dispatchEvent(m); document.dispatchEvent(m);
}
const fm = createEvent('mousemove', end.x, end.y, 1);
svg.dispatchEvent(fm); document.dispatchEvent(fm);
await sleep(5);
svg.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
document.dispatchEvent(createEvent('mouseup', end.x, end.y, 0));
}
break;
}
}
} catch (e) { console.error('Solver error:', e); }
},
clickNext: () => {
const nb = document.querySelector('[data-test="player-next"]') || document.querySelector('[data-test="stories-player-continue"]') || document.querySelector('[data-test="stories-player-done"]');
if (nb && nb.getAttribute('aria-disabled') !== 'true' && !nb.disabled) nb.click();
},
solve: async () => {
if (solver._busy) return;
solver._busy = true;
try {
const skipSel = [
'[data-test="practice-hub-ad-no-thanks-button"]', '[data-test="plus-no-thanks"]',
'[data-test="story-start"]', '.vpDIE'
];
skipSel.forEach(sel => document.querySelector(sel)?.click());
let mainEl = document.querySelector('._3yE3H') || document.querySelector('[data-test="challenge"]') || document.querySelector('[class*="challenge"]');
if (!mainEl) { solver.clickNext(); solver._busy = false; return; }
const ri = solver.findReact(mainEl);
window.sol = ri?.props?.currentChallenge;
if (!window.sol) { solver.clickNext(); solver._busy = false; return; }
const ct = solver.determineType();
if (ct && ct !== 'Challenge Speak' && ct !== 'Listen Match' && ct !== 'Listen Speak') {
await Promise.race([solver.handle(ct), new Promise(r => setTimeout(r, 2000))]);
await new Promise(r => setTimeout(r, 80));
} else if (ct) {
const skipBtn = document.querySelector('button[data-test="player-skip"]');
if (!skipBtn) await new Promise(r => setTimeout(r, 500));
document.querySelector('button[data-test="player-skip"]')?.click();
await new Promise(r => setTimeout(r, 150));
}
solver.clickNext();
await new Promise(r => setTimeout(r, 120));
} catch { solver.clickNext(); }
solver._busy = false;
},
_busy: false,
createUI: () => {
if (solverUI) return;
solverUI = document.createElement('div');
solverUI.className = 'DC_Solver_Btn';
solverUI.innerHTML = `
<button class="DC_Solve_One" id="DC_Solve_One">SOLVE</button>
<button class="DC_Solve_All" id="DC_Solve_All">SOLVE ALL</button>
`;
document.body.appendChild(solverUI);
document.getElementById('DC_Solve_One').addEventListener('click', () => solver.solve());
document.getElementById('DC_Solve_All').addEventListener('click', () => solver.toggleAuto());
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
if (e.shiftKey) solver.toggleAuto();
else solver.solve();
}
});
},
removeUI: () => {
if (solverUI) { solverUI.remove(); solverUI = null; }
isAutoMode = false;
},
toggleAuto: () => {
isAutoMode = !isAutoMode;
const btn = document.getElementById('DC_Solve_All');
if (btn) {
btn.textContent = isAutoMode ? 'PAUSE' : 'SOLVE ALL';
btn.style.background = isAutoMode ? 'rgb(var(--DC-red))' : 'rgb(var(--DC-gold))';
btn.style.borderBottomColor = isAutoMode ? '#cc0000' : 'rgb(var(--DC-gold-dark))';
}
if (isAutoMode) {
(async function loop() {
while (isAutoMode) {
await solver.solve();
await new Promise(r => setTimeout(r, 400));
}
})();
}
}
};
// Auto-solver observer
setInterval(() => {
const inLesson = window.location.pathname.includes('/lesson') || window.location.pathname.includes('/practice');
if (inLesson && solverEnabled) {
setTimeout(() => solver.createUI(), 500);
} else {
solver.removeUI();
}
}, 1000);
// ==================== FREE SUPER ====================
function installFakeSuper() {
if (localStorage.getItem('dc_super') !== 'true') return;
const RX = /https:\/\/www\.duolingo\.com\/\d{4}-\d{2}-\d{2}\/users\/.+/;
const ITEMS = { gold_subscription: { itemName: 'gold_subscription', subscriptionInfo: { vendor: 'STRIPE', renewing: true, isFamilyPlan: true, expectedExpiration: 9999999999000 } } };
function mod(j) {
try {
const d = JSON.parse(j);
d.hasPlus = true;
if (!d.trackingProperties) d.trackingProperties = {};
d.trackingProperties.has_item_gold_subscription = true;
d.shopItems = { ...d.shopItems, ...ITEMS };
return JSON.stringify(d);
} catch { return j; }
}
const uw = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
const orig = uw.fetch;
uw.fetch = function(resource, options) {
const url = resource instanceof Request ? resource.url : resource;
const method = resource instanceof Request ? resource.method : (options?.method || 'GET');
if (method.toUpperCase() === 'GET' && RX.test(url) && !url.includes('/shop-items')) {
return orig.apply(this, arguments).then(async r => {
const text = await r.clone().text();
return new Response(mod(text), { status: r.status, statusText: r.statusText, headers: new Headers(r.headers) });
});
}
return orig.apply(this, arguments);
};
}
installFakeSuper();
// ==================== EVENT HANDLERS ====================
// --- Dark Mode ---
const dcRoot = document.getElementById('DC_Root');
let isDark = localStorage.getItem('dc_dark') === 'true';
function applyDarkMode() {
if (isDark) {
dcRoot.classList.add('DC_Dark');
document.getElementById('DC_Dark_Icon_Moon').style.display = 'none';
document.getElementById('DC_Dark_Icon_Sun').style.display = 'block';
} else {
dcRoot.classList.remove('DC_Dark');
document.getElementById('DC_Dark_Icon_Moon').style.display = 'block';
document.getElementById('DC_Dark_Icon_Sun').style.display = 'none';
}
}
applyDarkMode();
document.getElementById('DC_Dark_Btn').addEventListener('click', () => {
isDark = !isDark;
localStorage.setItem('dc_dark', isDark ? 'true' : 'false');
applyDarkMode();
});
// --- Hide/Show with eye icon ---
document.getElementById('DC_Hide_Btn').addEventListener('click', (e) => {
// Prevent click firing right after drag
if (window._dcDragMoved) return;
hidden = !hidden;
const main = document.getElementById('DC_Main');
const box = document.getElementById('DC_Main_Box');
const btn = document.getElementById('DC_Hide_Btn');
const label = document.getElementById('DC_Hide_Label');
box.style.transition = 'opacity 0.6s cubic-bezier(0.16,1,0.32,1), filter 0.6s cubic-bezier(0.16,1,0.32,1)';
if (hidden) {
box.style.opacity = '0';
box.style.filter = 'blur(8px)';
box.style.pointerEvents = 'none';
label.textContent = 'Show';
btn.classList.add('DC_Hide_Btn_Hidden');
} else {
box.style.opacity = '';
box.style.filter = '';
box.style.pointerEvents = '';
label.textContent = 'Hide';
btn.classList.remove('DC_Hide_Btn_Hidden');
}
setTimeout(() => { box.style.transition = ''; }, 600);
});
// --- Draggable panel ---
(function() {
const main = document.getElementById('DC_Main');
let dragging = false, startX, startY, origRight, origBottom;
window._dcDragMoved = false;
main.addEventListener('mousedown', (e) => {
// Only drag on the hide button (the handle) or the top bar area
const hideBtn = document.getElementById('DC_Hide_Btn');
if (!hideBtn.contains(e.target) && e.target !== hideBtn) return;
dragging = true;
window._dcDragMoved = false;
const rect = main.getBoundingClientRect();
startX = e.clientX;
startY = e.clientY;
origRight = window.innerWidth - rect.right;
origBottom = window.innerHeight - rect.bottom;
main.classList.add('DC_Dragging');
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!dragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
if (Math.abs(dx) > 4 || Math.abs(dy) > 4) window._dcDragMoved = true;
const newRight = Math.max(0, Math.min(window.innerWidth - 50, origRight - dx));
const newBottom = Math.max(0, Math.min(window.innerHeight - 50, origBottom - dy));
main.style.right = newRight + 'px';
main.style.bottom = newBottom + 'px';
});
document.addEventListener('mouseup', () => {
if (!dragging) return;
dragging = false;
main.classList.remove('DC_Dragging');
// Save position
localStorage.setItem('dc_pos', JSON.stringify({ right: main.style.right, bottom: main.style.bottom }));
setTimeout(() => { window._dcDragMoved = false; }, 50);
});
// Restore saved position
try {
const saved = JSON.parse(localStorage.getItem('dc_pos') || 'null');
if (saved && saved.right && saved.bottom) {
main.style.right = saved.right;
main.style.bottom = saved.bottom;
}
} catch {}
})();
// XP button
const xpI = document.getElementById('DC_XP_Input'), xpB = document.getElementById('DC_XP_Btn');
xpI.addEventListener('input', () => { xpB.disabled = !user || !xpI.value || +xpI.value < 30; });
xpB.addEventListener('click', () => {
if (running && task === 'xp') { run('xp', 0); return; }
if (running) { notif('Stop current farm first.'); return; }
const v = +xpI.value;
if (v < 30) { notif('Min 30 XP'); return; }
run('xp', v);
});
xpI.addEventListener('keydown', e => { if (e.key === 'Enter' && !xpB.disabled) xpB.click(); });
// Gems button
const gmB = document.getElementById('DC_Gem_Btn');
gmB.addEventListener('click', () => {
if (running && task === 'gems') { run('gems', 0); return; }
if (running) { notif('Stop current farm first.'); return; }
run('gems', 0);
});
// Streak button
const stI = document.getElementById('DC_Streak_Input'), stB = document.getElementById('DC_Streak_Btn');
stI.addEventListener('input', () => { stB.disabled = !user || !stI.value || +stI.value < 1; });
stB.addEventListener('click', () => {
if (running && task === 'streak') { run('streak', 0); return; }
if (running) { notif('Stop current farm first.'); return; }
const v = +stI.value;
if (v < 1) return;
run('streak', v);
});
stI.addEventListener('keydown', e => { if (e.key === 'Enter' && !stB.disabled) stB.click(); });
// Practice button
document.getElementById('DC_Practice_Btn').addEventListener('click', () => {
if (!window.location.pathname.startsWith('/practice')) {
window.location.assign('/practice');
}
});
// Super button
document.getElementById('DC_Super_Btn').addEventListener('click', () => {
localStorage.setItem('dc_super', localStorage.getItem('dc_super') === 'true' ? 'false' : 'true');
notif(localStorage.getItem('dc_super') === 'true' ? 'Free Super activated! Reload page.' : 'Free Super deactivated. Reload page.');
});
// Solver toggle
const solverT = document.getElementById('DC_Solver_Toggle');
solverT.checked = solverEnabled;
solverT.addEventListener('change', () => {
solverEnabled = solverT.checked;
localStorage.setItem('dc_solver', solverEnabled ? 'true' : 'false');
if (!solverEnabled) solver.removeUI();
});
// ==================== INITIALIZATION ====================
// Initial animation
const main = document.getElementById('DC_Main');
const box = document.getElementById('DC_Main_Box');
box.style.opacity = '0';
box.style.filter = 'blur(8px)';
setTimeout(() => {
box.style.transition = 'opacity 0.6s cubic-bezier(0.16,1,0.32,1), filter 0.6s cubic-bezier(0.16,1,0.32,1)';
box.style.opacity = '';
box.style.filter = '';
setTimeout(() => { box.style.transition = ''; }, 600);
}, 300);
connect();
})();