// ==UserScript== // @name DUO_KEEPSTREAK // @namespace ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜× // @version v1.0.3 // @description Automatically maintains the daily streak on Duolingo (ADD WORD"PROCESSING" NEW UPDATE) // @author ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜× // @match https://*.duolingo.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=duolingo.com // @downloadURL https://update.greasyfork.icu/scripts/525768/DUO_KEEPSTREAK.user.js // @updateURL https://update.greasyfork.icu/scripts/525768/DUO_KEEPSTREAK.meta.js // ==/UserScript== 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`, { 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; } }; // 🎉 Hiệu ứng giấy màu rơi xuống 🎉 const createConfetti = () => { const confettiCount = 100; for (let i = 0; i < confettiCount; i++) { const confetti = document.createElement("div"); confetti.classList.add("confetti"); document.body.appendChild(confetti); // Tạo màu sắc ngẫu nhiên confetti.style.backgroundColor = `hsl(${Math.random() * 360}, 100%, 50%)`; confetti.style.width = `${6 + Math.random() * 6}px`; confetti.style.height = confetti.style.width; confetti.style.position = "fixed"; confetti.style.top = "-10px"; confetti.style.left = `${Math.random() * 100}vw`; confetti.style.opacity = "0.8"; confetti.style.borderRadius = "50%"; // Random tốc độ rơi const duration = 2 + Math.random() * 3; confetti.style.animation = `fall ${duration}s ease-out forwards, spin ${duration}s linear infinite`; // Xóa sau khi animation kết thúc setTimeout(() => { confetti.remove(); }, duration * 1000); } }; // Thêm CSS animation const addConfettiStyle = () => { const style = document.createElement("style"); style.innerHTML = ` .confetti { position: fixed; top: 0; } @keyframes fall { to { transform: translateY(100vh); opacity: 0; } } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } `; document.head.appendChild(style); }; const attemptStreak = async (button) => { button.innerText = "Processing..."; button.disabled = true; button.style.opacity = "0.7"; const token = getToken(); if (!token) { alert("❌ You are not logged into Duolingo!"); } else { const userId = parseJwt(token)?.sub; if (!userId) { alert("❌ Error retrieving user ID."); } else { const headers = getHeaders(token); const userData = await fetchUserData(userId, headers); if (!userData) { alert("⚠️ Error fetching user data, try again!"); } else if (hasStreakToday(userData)) { alert("✅ You have already maintained your streak today!"); } else { const session = await startSession(userData.fromLanguage, userData.learningLanguage, headers); if (!session) { alert("⚠️ Error starting session, try again!"); } else { const completed = await completeSession(session, headers); if (completed) { alert("🎉 Streak has been maintained! Reload the page to check."); createConfetti(); } else { alert("⚠️ Error maintaining streak, try again!"); } } } } } button.innerText = "🔥 Get Streak 🔥"; button.disabled = false; button.style.opacity = "1"; }; const addButton = () => { if (document.getElementById("get-streak-btn")) return; const button = document.createElement("button"); button.id = "get-streak-btn"; button.innerText = "🔥 Get Streak 🔥"; button.style.position = "fixed"; button.style.bottom = "20px"; button.style.right = "20px"; button.style.padding = "14px 24px"; button.style.backgroundColor = "#58cc02"; button.style.color = "white"; button.style.fontSize = "18px"; button.style.fontWeight = "bold"; button.style.border = "none"; button.style.borderRadius = "30px"; button.style.boxShadow = "0px 6px 12px rgba(0, 0, 0, 0.2)"; button.style.cursor = "pointer"; button.style.zIndex = "1000"; button.style.transition = "all 0.2s ease-in-out"; button.onmouseover = () => { button.style.backgroundColor = "#46a102"; button.style.transform = "scale(1.1)"; }; button.onmouseout = () => { button.style.backgroundColor = "#58cc02"; button.style.transform = "scale(1)"; }; button.onclick = () => attemptStreak(button); document.body.appendChild(button); }; window.onload = () => { addConfettiStyle(); setTimeout(addButton, 2000); };