DUO_FARMEDGEMS

THIS TOOL AUTO FARMED GEMS FOR YOU

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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;
})();