YM Duo_KeepStreak

Automatically maintains the daily streak on Duolingo (NEW VERSION V1.0.5)

// ==UserScript==
// @name         YM Duo_KeepStreak
// @namespace    ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜×
// @version      v1.0.5
// @description  Automatically maintains the daily streak on Duolingo (NEW VERSION V1.0.5)
// @author       ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜×
// @match        https://*.duolingo.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=duolingo.com
// ==/UserScript==

(function () {
    'use strict';

    const getToken = () => {
        const tokenRow = document.cookie.split('; ').find(row => row.startsWith('jwt_token='));
        return tokenRow ? tokenRow.split('=')[1] : null;
    };

    const parseJwt = (token) => {
        try {
            return JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
        } catch (e) {
            console.error("JWT parsing error", e);
            return null;
        }
    };

    const getHeaders = (token) => ({
        "Content-Type": "application/json",
        "Authorization": `Bearer ${token}`,
        "User-Agent": navigator.userAgent
    });

    const fetchUserData = async (userId, headers) => {
        try {
            const response = await fetch(`https://www.duolingo.com/2017-06-30/users/${userId}?fields=fromLanguage,learningLanguage,streakData,subscriptions`, { headers });
            if (!response.ok) throw new Error("Failed to fetch user data");
            return response.json();
        } catch (error) {
            console.error("Error fetching user data:", error);
            return null;
        }
    };

    const hasStreakToday = (data) => {
        const today = new Date().toISOString().split('T')[0];
        return data?.streakData?.currentStreak?.endDate === today;
    };

    const startSession = async (fromLang, learningLang, headers) => {
        try {
            const payload = {
                challengeTypes: ["translate", "match", "tapComplete", "reverseAssist", "judge"],
                fromLanguage: fromLang,
                learningLanguage: learningLang,
                type: "GLOBAL_PRACTICE"
            };
            const response = await fetch("https://www.duolingo.com/2017-06-30/sessions", {
                method: 'POST',
                headers,
                body: JSON.stringify(payload)
            });
            if (!response.ok) throw new Error("Failed to start session");
            return response.json();
        } catch (error) {
            console.error("Error starting session:", error);
            return null;
        }
    };

    const completeSession = async (session, headers) => {
        try {
            const payload = { ...session, heartsLeft: 0, failed: false, shouldLearnThings: true };
            const response = await fetch(`https://www.duolingo.com/2017-06-30/sessions/${session.id}`, {
                method: 'PUT',
                headers,
                body: JSON.stringify(payload)
            });
            if (!response.ok) throw new Error("Failed to complete session");
            return response.json();
        } catch (error) {
            console.error("Error completing session:", error);
            return null;
        }
    };

    const isVipUser = (userData) => {
        return userData?.subscriptions?.some(sub => sub.type === "VIP");
    };

    const createConfetti = () => {
        const confettiContainer = document.createElement("div");
        confettiContainer.className = "confetti";
        for (let i = 0; i < 100; i++) {
            const div = document.createElement("div");
            div.style.left = `${Math.random() * 100}%`;
            div.style.backgroundColor = `hsl(${Math.random() * 360}, 100%, 50%)`;
            div.style.animationDelay = `${Math.random() * 3}s`;
            confettiContainer.appendChild(div);
        }
        document.body.appendChild(confettiContainer);
        setTimeout(() => confettiContainer.remove(), 3000);
    };

    const addConfettiStyle = () => {
        const style = document.createElement("style");
        style.innerHTML = `
            @keyframes confetti {
                0% { transform: translateY(0); opacity: 1; }
                100% { transform: translateY(100vh); opacity: 0; }
            }
            .confetti {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                pointer-events: none;
                z-index: 9999;
            }
            .confetti div {
                position: absolute;
                width: 10px;
                height: 10px;
                opacity: 0.8;
                animation: confetti 3s infinite;
            }
        `;
        document.head.appendChild(style);
    };

    const attemptStreak = async (button) => {
        button.innerText = "⏳ Processing...";
        button.disabled = true;

        const token = getToken();
        if (!token) {
            alert("❌ Bạn chưa đăng nhập vào Duolingo!");
        } else {
            const userId = parseJwt(token)?.sub;
            if (!userId) {
                alert("❌ Không thể lấy thông tin người dùng.");
            } else {
                const headers = getHeaders(token);
                const userData = await fetchUserData(userId, headers);

                if (!userData) {
                    alert("⚠️ Không thể tải dữ liệu người dùng.");
                } else if (hasStreakToday(userData)) {
                    alert("✅ Bạn đã duy trì streak hôm nay!");
                } else {
                    if (isVipUser(userData)) {
                        alert("🌟 VIP User! Tận hưởng quyền lợi cao cấp.");
                    }

                    const session = await startSession(userData.fromLanguage, userData.learningLanguage, headers);
                    if (!session) {
                        alert("⚠️ Không thể bắt đầu phiên học.");
                    } else {
                        const completed = await completeSession(session, headers);
                        if (completed) {
                            const xpBonus = isVipUser(userData) ? 20 : 10;
                            alert(`🎉 Đã duy trì streak! Nhận được ${xpBonus} XP.`);
                            createConfetti();
                        } else {
                            alert("⚠️ Không thể hoàn thành phiên học.");
                        }
                    }
                }
            }
        }

        button.innerText = "🔥 Get Streak 🔥";
        button.disabled = false;
    };

    const createControlPanel = async () => {
        if (document.getElementById("duo-panel")) return;

        const panel = document.createElement("div");
        panel.id = "duo-panel";
        panel.style.position = "fixed";
        panel.style.bottom = "20px";
        panel.style.right = "20px";
        panel.style.background = "white";
        panel.style.border = "2px solid #58cc02";
        panel.style.borderRadius = "16px";
        panel.style.padding = "12px";
        panel.style.zIndex = "9999";
        panel.style.boxShadow = "0 4px 10px rgba(0,0,0,0.15)";
        panel.style.transition = "all 0.3s ease-in-out";
        panel.style.minWidth = "180px";

        const btn = document.createElement("button");
        btn.innerText = "🔥 Get Streak 🔥";
        btn.style.width = "100%";
        btn.style.padding = "10px";
        btn.style.marginBottom = "10px";
        btn.style.backgroundColor = "#58cc02";
        btn.style.color = "white";
        btn.style.border = "none";
        btn.style.borderRadius = "10px";
        btn.style.cursor = "pointer";
        btn.onclick = () => attemptStreak(btn);

        const checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.id = "duo-auto-run";
        checkbox.checked = localStorage.getItem("duo_auto_run") === "true";
        checkbox.onchange = () => {
            localStorage.setItem("duo_auto_run", checkbox.checked);
        };

        const label = document.createElement("label");
        label.htmlFor = "duo-auto-run";
        label.style.fontSize = "14px";
        label.style.color = "#333";
        label.style.display = "flex";
        label.style.alignItems = "center";
        label.innerHTML = `<span style="margin-left: 8px;">Auto Run</span>`;
        label.prepend(checkbox);

        const toggleBtn = document.createElement("button");
        toggleBtn.innerText = "🔽";
        toggleBtn.style.position = "fixed";
        toggleBtn.style.bottom = "20px";
        toggleBtn.style.right = "220px";
        toggleBtn.style.width = "32px";
        toggleBtn.style.height = "32px";
        toggleBtn.style.borderRadius = "50%";
        toggleBtn.style.border = "none";
        toggleBtn.style.background = "#58cc02";
        toggleBtn.style.color = "white";
        toggleBtn.style.cursor = "pointer";
        toggleBtn.style.zIndex = "9999";
        toggleBtn.title = "Hiện/Ẩn Panel";

        let visible = true;
        toggleBtn.onclick = () => {
            visible = !visible;
            panel.style.display = visible ? "block" : "none";
            toggleBtn.innerText = visible ? "🔽" : "🔼";
        };

        panel.appendChild(btn);
        panel.appendChild(label);
        document.body.appendChild(panel);
        document.body.appendChild(toggleBtn);

        // Tự chạy nếu Auto Run bật và chưa có streak
        if (checkbox.checked) {
            const token = getToken();
            if (token) {
                const userId = parseJwt(token)?.sub;
                if (userId) {
                    const headers = getHeaders(token);
                    const userData = await fetchUserData(userId, headers);
                    if (userData && !hasStreakToday(userData)) {
                        setTimeout(() => attemptStreak(btn), 1000);
                    }
                }
            }
        }
    };

    window.onload = () => {
        addConfettiStyle();
        setTimeout(createControlPanel, 2000);
    };
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址