// ==UserScript==
// @name YM DuoCheat_Hub
// @namespace http://tampermonkey.net/
// @version v1.0BETA
// @description XP + Gems farming tool for Duolingo, with multi-language UI and floating Discord chat
// @author ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜×
// @match https://www.duolingo.com/*
// @grant none
// @run-at document-idle
// @icon https://www.google.com/s2/favicons?sz=64&domain=duolingo.com
// ==/UserScript==
(function () {
'use strict';
const SERVER_ID = '1377275722342858973';
const WIDGET_URL = `https://discord.com/widget?id=${SERVER_ID}&theme=dark`;
const VERSION = 'v1.0BETA';
let lang = 'en';
const LANG = {
en: {
header: '🌈 DUO HUB TOOL', farmLabel: '🔧 What do you want to farm?', start: '🚀 Start farm',
done: '🚜 Farm finished', copied: 'Token copied!', placeholder: 'Enter target amount',
settings: 'Settings', support: 'Support', profile: 'Profile', discord: 'Discord',
version: 'Version', madeby: 'Made by'
}
};
const t = k => LANG[lang][k];
const style = document.createElement('style');
style.textContent = `
@keyframes rainbowBorder {
0%,100%{border-color:red}14%{border-color:orange}28%{border-color:yellow}
42%{border-color:green}57%{border-color:blue}71%{border-color:indigo}85%{border-color:violet}
}
.duo-rainbow-panel {
position:fixed;bottom:100px;left:20px;background:#f0f9ff;color:#222;padding:16px;border:3px solid;
border-radius:16px;z-index:9999;font-size:14px;box-shadow:0 4px 10px rgba(0,0,0,.2);width:280px;
display:flex;flex-direction:column;gap:12px;transition:opacity 0.4s ease, transform 0.4s ease;
font-family:'Segoe UI',sans-serif;animation:rainbowBorder 5s infinite linear;
}
.duo-panel-hidden{opacity:0!important;transform:translateY(30px)!important;pointer-events:none!important;}
.duo-toggle-btn {
position:fixed;bottom:20px;right:20px;width:64px;height:64px;background:#58cc02;color:#fff;
border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:30px;cursor:pointer;
z-index:10000;border:5px solid;animation:rainbowBorder 3s infinite linear;
box-shadow:0 6px 16px rgba(0,0,0,.35);
}
.duo-toggle-version {
position:fixed;bottom:75px;right:22px;background:rgba(0,0,0,.5);color:#fff;font-size:10px;
padding:2px 6px;border-radius:6px;z-index:10000;font-family:monospace;
}
.duo-discord-btn {
position:fixed;bottom:100px;right:20px;width:60px;height:60px;background:#5865F2;color:#fff;
border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:26px;cursor:pointer;
z-index:10000;box-shadow:0 6px 16px rgba(0,0,0,.35);
}
.duo-notification-btn {
position:fixed;bottom:170px;right:20px;width:60px;height:60px;background:#ffb703;color:#000;
border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:26px;cursor:pointer;
z-index:10000;box-shadow:0 6px 16px rgba(0,0,0,.35);
}
.duo-discord-chat {
position:fixed;bottom:180px;right:20px;width:350px;height:500px;background:#fff;border:3px solid #5865F2;
border-radius:10px;overflow:hidden;z-index:10000;display:none;box-shadow:0 6px 16px rgba(0,0,0,.4);
}
`;
document.head.appendChild(style);
const toggleBtn = Object.assign(document.createElement('div'), {className:'duo-toggle-btn', textContent:'⚙️'});
const versionTag = Object.assign(document.createElement('div'), {className:'duo-toggle-version',textContent:VERSION});
const panel = Object.assign(document.createElement('div'), {className:'duo-rainbow-panel'});
document.body.append(toggleBtn, versionTag, panel);
const discordBtn = Object.assign(document.createElement('div'), {
className: 'duo-discord-btn',
innerHTML: '💬'
});
const discordChat = document.createElement('div');
discordChat.className = 'duo-discord-chat';
const iframe = document.createElement('iframe');
iframe.src = WIDGET_URL;
iframe.style.border = 'none';
iframe.width = '100%';
iframe.height = '100%';
iframe.setAttribute('allowtransparency', 'true');
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('sandbox', 'allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts');
discordChat.appendChild(iframe);
const notificationBtn = Object.assign(document.createElement('div'), {
className: 'duo-notification-btn',
innerHTML: '🔔'
});
notificationBtn.onclick = () => {
alert('📢 Notice from admin: Currently, XP and GEMS farming is working. STREAK farming is under development! Thank you! (ADMIN: YAMISCRIPT_DEV)');
navigator.vibrate?.(40);
};
let chatVisible = false;
discordBtn.onclick = () => {
chatVisible = !chatVisible;
discordChat.style.display = chatVisible ? 'block' : 'none';
navigator.vibrate?.(40);
};
document.body.append(discordBtn, notificationBtn, discordChat);
const header = Object.assign(document.createElement('div'), {
textContent: t('header'),
style: 'font-weight:bold;font-size:17px;text-align:center;color:#0a4d2f'
});
const createBtn = (label, emoji, onClick) => {
const b = document.createElement('button');
b.innerHTML = `${emoji} ${label}`;
Object.assign(b.style,{
padding:'10px',background:'#fff',color:'#1c1c1c',border:'2px solid #58CC02',borderRadius:'10px',
fontSize:'14px',cursor:'pointer',fontWeight:'600',width:'100%'
});
b.onclick = onClick;
return b;
};
const supportBtn = createBtn(t('support'), '❤️', () => alert('[email protected]'));
const profileBtn = createBtn(t('profile'), '👤', () => window.open('https://guns.lol/yamiscript_dev','_blank'));
const discordBtn2 = createBtn(t('discord'), '💬', () => window.open('https://discord.gg/F3uWQNpt','_blank'));
const settingsBtn = createBtn(t('settings'),'⚙️', () => alert('⚙️Feature under development...'));
const btnGrid = document.createElement('div');
Object.assign(btnGrid.style,{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'10px'});
btnGrid.append(supportBtn, profileBtn, discordBtn2, settingsBtn);
const farmDiv = document.createElement('div');
farmDiv.innerHTML = `
<div id="farmLabel">${t('farmLabel')}</div>
<select id="farmType" style="width:100%;padding:6px;border:1px solid #ccc;border-radius:6px;margin:6px 0;">
<option value="xp">XP</option><option value="gems">Gems</option><option value="streak">Streak</option>
</select>
<input id="targetValue" type="number" placeholder="${t('placeholder')}"
style="width:100%;padding:6px;border:1px solid #ccc;border-radius:6px;margin-bottom:6px;">
<button id="startFarm" style="background:#58cc02;color:#fff;width:100%;padding:8px;border:none;border-radius:8px;cursor:pointer;">
${t('start')}
</button>
<div id="progressText" style="margin-top:6px;font-size:13px;color:#333;">❌Chưa hoạt động</div>
<div style="height:10px;background:#ccc;border-radius:6px;overflow:hidden;">
<div id="progressBar" style="height:100%;width:0%;background:#58cc02;transition:width .3s;"></div>
</div>
`;
const footer = document.createElement('div');
footer.innerHTML = `
<div style="font-size:12px;color:#444;">${t('version')}: ${VERSION}</div>
<div style="font-size:11px;color:#888;margin-top:2px;">${t('madeby')} <b>YAMISCRIPT_DEV</b></div>
`;
footer.style.textAlign = 'center';
panel.append(header, btnGrid, farmDiv, footer);
let visible = true;
toggleBtn.onclick = () => {
visible = !visible;
panel.classList.toggle('duo-panel-hidden', !visible);
navigator.vibrate?.(50);
};
const getCookie = n => document.cookie.match(new RegExp(`${n}=([^;]+)`))?.[1] || null;
const getJwtToken = () => getCookie('jwt_token');
const decodeJwt = t => JSON.parse(atob(t.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')));
const farmGems = async target => {
try {
const userId = getCookie('logged_out_uuid');
const jwt = getJwtToken();
if (!userId || !jwt) return alert('⚠️ Unable to get token!');
for (let i = 0; i < target; i++) {
await fetch(`https://www.duolingo.com/2017-06-30/users/${userId}/rewards/CAPSTONE_COMPLETION-xxxx-2-GEMS`,{
method:'PATCH',
headers:{accept:'application/json',authorization:`Bearer ${jwt}`,'content-type':'application/json'},
body:JSON.stringify({amount:0,consumed:true,skillId:'xxx',type:'mission'})
});
document.getElementById('progressText').textContent = `💎 Đã farm ${i+1}/${target} gems`;
document.getElementById('progressBar').style.width = `${((i+1)/target)*100}%`;
await new Promise(r=>setTimeout(r,0));
}
document.getElementById('progressText').textContent = '✅Gems farming completed!';
} catch(e){ console.error('Lỗi farm gems:',e); }
};
const farmXp = async (headers, sessionPayload, updatePayload, targetXP) => {
let currentXP = 0;
while (currentXP < targetXP) {
const s = await fetch('https://www.duolingo.com/2017-06-30/sessions',
{method:'POST',headers,body:JSON.stringify(sessionPayload)}).then(r=>r.json());
const u = await fetch(`https://www.duolingo.com/2017-06-30/sessions/${s.id}`,
{method:'PUT',headers,body:JSON.stringify({...s,...updatePayload})}).then(r=>r.json());
currentXP += u.xpGain || 10;
document.getElementById('progressText').textContent = `🧠 Đã farm ${currentXP}/${targetXP} XP`;
document.getElementById('progressBar').style.width = `${(currentXP/targetXP)*100}%`;
await new Promise(r=>setTimeout(r,0));
}
document.getElementById('progressText').textContent = t('done');
};
const duoxpanel = async xp => {
const jwt = getJwtToken(); if (!jwt) return alert('⚠️ Not logged in!');
const sub = decodeJwt(jwt).sub;
const headers = {'Authorization':`Bearer ${jwt}`,'Content-Type':'application/json'};
const info = await fetch(`https://www.duolingo.com/2017-06-30/users/${sub}?fields=username,fromLanguage,learningLanguage`,{headers}).then(r=>r.json());
const sessionPayload = {challengeTypes:['definition','translate','assist'],fromLanguage:info.fromLanguage,learningLanguage:info.learningLanguage,type:'GLOBAL_PRACTICE'};
const updatePayload = {heartsLeft:0,startTime:Math.floor(Date.now()/1000),endTime:Math.floor(Date.now()/1000)+100,failed:false,maxInLessonStreak:10,shouldLearnThings:true};
await farmXp(headers, sessionPayload, updatePayload, xp);
};
document.getElementById('startFarm').onclick = async () => {
const type = document.getElementById('farmType').value;
const amount = parseInt(document.getElementById('targetValue').value||'0');
if (!amount || amount<=0) return alert('❌ Invalid amount!');
document.getElementById('progressText').textContent = '⏳ Đang farm...';
document.getElementById('progressBar').style.width = '0%';
if (type === 'xp') await duoxpanel(amount);
else if (type === 'gems') await farmGems(amount);
else alert('🚧 This feature is not supported yet.');
};
})();