DUO_FARMEDGEMS

THIS TOOL AUTO FARMED GEMS FOR YOU

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         DUO_FARMEDGEMS
// @namespace    http://tampermonkey.net/
// @version      v1.0BETA
// @description  THIS TOOL AUTO FARMED GEMS FOR YOU
// @author       ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜×
// @match        https://www.duolingo.com/learn
// @icon         https://www.google.com/s2/favicons?sz=64&domain=duolingo.com
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const colors = {
        primary: '#58CC02',
        secondary: '#1DA462',
        danger: '#FF4757',
        background: '#003049',
        text: '#FFFFFF'
    };

    function createElement(tag, options = {}) {
        const el = document.createElement(tag);
        if (options.styles) Object.assign(el.style, options.styles);
        if (options.content) el.innerHTML = options.content;
        if (options.events) {
            for (const [event, handler] of Object.entries(options.events)) {
                el.addEventListener(event, handler);
            }
        }
        return el;
    }

    function createBtn(label, bg, shadow, onClick) {
        return createElement('button', {
            styles: {
                flex: '1 1 120px',
                padding: '12px 0',
                fontSize: '16px',
                fontWeight: '600',
                background: bg,
                color: colors.text,
                border: 'none',
                borderRadius: '15px',
                cursor: 'pointer',
                transition: 'transform 0.3s ease',
                boxShadow: `0 8px 25px ${shadow}66`,
                minWidth: '120px'
            },
            content: label,
            events: {
                click: onClick,
                mouseenter: e => e.currentTarget.style.transform = 'translateY(-2px)',
                mouseleave: e => e.currentTarget.style.transform = 'translateY(0)'
            }
        });
    }

    function createSmallBtn(label, bg, shadow, onClick) {
        return createElement('button', {
            styles: {
                padding: '8px 16px',
                fontSize: '13px',
                fontWeight: '500',
                background: bg,
                color: colors.text,
                border: 'none',
                borderRadius: '10px',
                cursor: 'pointer',
                transition: 'transform 0.2s ease',
                boxShadow: `0 5px 15px ${shadow}44`,
                margin: '5px'
            },
            content: label,
            events: {
                click: onClick,
                mouseenter: e => e.currentTarget.style.transform = 'translateY(-1px)',
                mouseleave: e => e.currentTarget.style.transform = 'translateY(0)'
            }
        });
    }

    const gemBtn = createElement('button', {
        styles: {
            position: 'fixed',
            bottom: '25px',
            right: '10px',
            width: '60px',
            height: '60px',
            borderRadius: '50%',
            background: `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`,
            color: colors.text,
            fontSize: '32px',
            cursor: 'pointer',
            zIndex: 10000,
            boxShadow: '0 8px 20px rgba(88, 204, 2, 0.3)',
            transition: 'transform 0.3s ease',
            border: 'none',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
        },
        content: '💎',
        events: { click: togglePanel }
    });

    const panel = createElement('div', {
        styles: {
            position: 'fixed',
            top: '50%',
            left: '50%',
            width: '460px',
            background: colors.background,
            borderRadius: '25px',
            zIndex: 10000,
            padding: '30px',
            fontFamily: "'Segoe UI', system-ui",
            color: colors.text,
            opacity: '0',
            transform: 'translate(-50%, -50%) scale(0.9)',
            pointerEvents: 'none',
            transition: 'opacity 0.4s ease, transform 0.4s ease',
            overflow: 'hidden',
            minHeight: '320px',
            display: 'flex',
            flexDirection: 'column'
        }
    });
    panel.id = 'duoPanel';

    const style = document.createElement('style');
    style.textContent = `
        @keyframes borderAnim {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
        #duoPanel {
            border: 2px solid transparent;
            background-origin: border-box;
            background-clip: padding-box, border-box;
            background-image:
                linear-gradient(${colors.background}, ${colors.background}),
                linear-gradient(270deg, #58CC02, #1DA462, #FF4757, #58CC02);
            background-size: 400% 400%;
            background-repeat: no-repeat;
            animation: borderAnim 10s linear infinite;
        }
        #duoPanel.active {
            opacity: 1 !important;
            transform: translate(-50%, -50%) scale(1) !important;
            pointer-events: auto !important;
        }
    `;
    document.head.appendChild(style);

    function togglePanel() {
        panel.classList.toggle('active');
        gemBtn.style.transform = panel.classList.contains('active') ? 'rotate(360deg)' : 'rotate(0deg)';
    }

    const header = createElement('div', {
        styles: { textAlign: 'center', marginBottom: '10px' },
        content: `
            <h1 style="margin: 0 0 10px 0;
                       font-size: 32px;
                       background: linear-gradient(45deg, ${colors.primary}, ${colors.secondary}, ${colors.text}cc);
                       -webkit-background-clip: text;
                       background-clip: text;
                       color: transparent;
                       font-weight: 700;
                       letter-spacing: 1px;">
            DUO_FARMEDGEMS</h1>
            <p style="margin: 0; color: ${colors.text}; font-size: 14px;">MADE BY YAMISCRIPT_DEV</p>
            <p id="_loginStatus">LOADING...</p>
        `
    });

    const usernameGreeting = createElement('div', {
        styles: {
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            gap: '12px',
            marginBottom: '20px',
            fontSize: '18px',
            color: colors.text
        },
    });

    async function fetchUsername() {
        const userId = getCookie('logged_out_uuid');
        const jwt = getCookie('jwt_token');
        if (!userId || !jwt) return;
        try {
            const res = await fetch(`https://www.duolingo.com/2017-06-30/users/${userId}?fields=username`, {
                headers: { authorization: `Bearer ${jwt}` }
            });
            const data = await res.json();
            if (data.username) {
                document.getElementById('_loginStatus').innerHTML = ``;
                usernameGreeting.innerHTML = `<span>👋Welcome, <b style="color: ${colors.primary}">${data.username}</b>!</span>`;
            }
        } catch (e) {
            document.getElementById('_loginStatus').innerText = 'BRUH!';
        }
    }

    const gemCountDiv = createElement('div', {
        styles: { textAlign: 'center', margin: '20px 0' },
        content: `<div id="gemCount" style="font-size: 42px;
                                            font-weight: 700;
                                            color: ${colors.primary};
                                            text-shadow: 0 4px 12px rgba(88,204,2,0.2);
                                            margin-top: 10px;">0</div>`
    });

    const btnGroup = createElement('div', {
        styles: {
            display: 'flex',
            gap: '15px',
            justifyContent: 'center',
            margin: '25px 0',
            flexWrap: 'wrap'
        }
    });

    const statusText = createElement('div', {
        styles: {
            textAlign: 'center',
            color: colors.text + '99',
            fontSize: '14px',
            marginTop: '5px'
        },
        content: '<span id="statusText">NON-ACTIVE:❌</span>'
    });

    const startBtn = createBtn('START', `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`, colors.primary, startFarm);
    const stopBtn = createBtn('STOP', `linear-gradient(135deg, ${colors.danger}, #CC2E3D)`, colors.danger, stopFarm);
    stopBtn.style.display = 'none';

    const discordBtn = createBtn('DISCORD', `linear-gradient(135deg, ${colors.secondary}, ${colors.primary})`, colors.secondary, () => window.open('https://discord.gg/aDD9DMz6', '_blank'));
    const profileBtn = createBtn('PROFILE', `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`, colors.primary, () => window.open('https://guns.lol/yamiscript_dev'));
    const settingBtn = createBtn('SETTING', `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`, colors.primary, showTokenPanel);

    const tokenPanel = createElement('div', {
        styles: {
            display: 'none',
            flexDirection: 'column',
            justifyContent: 'center',
            alignItems: 'center',
            padding: '20px',
            borderRadius: '15px',
            backgroundColor: '#144d1d',
            marginTop: '15px',
            textAlign: 'center',
            fontSize: '14px',
            userSelect: 'text'
        }
    });

    const tokenTextarea = createElement('textarea', {
        styles: {
            width: '100%',
            height: '80px',
            borderRadius: '10px',
            border: 'none',
            resize: 'none',
            padding: '10px',
            fontSize: '14px',
            fontFamily: 'monospace',
            marginBottom: '12px',
            backgroundColor: '#0b2e0f',
            color: '#a6e22e'
        },
        events: {
            focus: (e) => e.target.select()
        }
    });

    const copyTokenBtn = createSmallBtn('Copy token', `linear-gradient(135deg, ${colors.primary}, ${colors.secondary})`, colors.primary, () => {
        tokenTextarea.select();
        document.execCommand('copy');
        copyTokenBtn.textContent = 'Copied!';
        setTimeout(() => copyTokenBtn.textContent = 'Copy token', 2000);
    });

    const closeTokenBtn = createSmallBtn('Close', colors.danger, colors.danger, showMainPanel);

    tokenPanel.append(
        createElement('div', { content: '<b>Token JWT of you:</b>', styles: { marginBottom: '10px', fontSize: '10px' } }),
        tokenTextarea,
        copyTokenBtn,
        closeTokenBtn
    );

    btnGroup.append(startBtn, stopBtn, discordBtn, profileBtn, settingBtn);
    panel.append(header, usernameGreeting, gemCountDiv, btnGroup, statusText, tokenPanel);
    document.body.append(gemBtn, panel);

    let farming = false;
    let gemCount = parseInt(localStorage.getItem('gemsCount')) || 0;
    let farmInterval;

    function updateGemCount() {
        localStorage.setItem('gemsCount', gemCount);
        document.getElementById('gemCount').textContent = gemCount;
    }

    function showTokenPanel() {
        const token = getCookie('jwt_token') || 'Không tìm thấy token';
        tokenTextarea.value = token;
        btnGroup.style.display = 'none';
        statusText.style.display = 'none';
        gemCountDiv.style.display = 'none';
        tokenPanel.style.display = 'flex';
    }

    function showMainPanel() {
        tokenPanel.style.display = 'none';
        btnGroup.style.display = 'flex';
        statusText.style.display = 'block';
        gemCountDiv.style.display = 'block';
    }

    function getCookie(name) {
        const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
        return match ? match[2] : null;
    }

    async function farmGems() {
        try {
            const userId = getCookie('logged_out_uuid');
            const jwt = getCookie('jwt_token');
            const res = 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' })
            });
            if (res.status === 200) {
                for (let i = 1; i <= 15; i++) {
                    setTimeout(() => {
                        gemCount++;
                        updateGemCount();
                    }, i * 250);
                }
            }
        } catch (err) {
            console.error('Lỗi farm:', err);
        }
    }

    function startFarm() {
        if (!farming) {
            farming = true;
            startBtn.style.display = 'none';
            stopBtn.style.display = 'block';
            document.getElementById('statusText').textContent = 'ACTIVE:✅';
            farmInterval = setInterval(farmGems, 100);
        }
    }

    function stopFarm() {
        farming = false;
        clearInterval(farmInterval);
        stopBtn.style.display = 'none';
        startBtn.style.display = 'block';
        document.getElementById('statusText').textContent = 'NON-ACTIVE:❌';
        updateGemCount();
    }

    window.addEventListener('load', () => {
        fetchUsername();
        updateGemCount();
    });

    window.onbeforeunload = () => farming ? 'Đang farm gems - Bạn có chắc chắn muốn thoát?' : null;
})();