DUO_KEEPSTREAK

Automatically maintains the daily streak on Duolingo (NEW UPDATE)

目前为 2025-03-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name DUO_KEEPSTREAK
  3. // @namespace ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜×
  4. // @version v1.0.2
  5. // @description Automatically maintains the daily streak on Duolingo (NEW UPDATE)
  6. // @author ´꒳`ⓎⒶⓂⒾⓈⒸⓇⒾⓅⓉ×͜×
  7. // @match https://*.duolingo.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=duolingo.com
  9. // ==/UserScript==
  10.  
  11. const getToken = () => {
  12. const tokenRow = document.cookie.split('; ').find(row => row.startsWith('jwt_token='));
  13. return tokenRow ? tokenRow.split('=')[1] : null;
  14. };
  15.  
  16. const parseJwt = (token) => {
  17. try {
  18. return JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
  19. } catch (e) {
  20. console.error("JWT parsing error", e);
  21. return null;
  22. }
  23. };
  24.  
  25. const getHeaders = (token) => ({
  26. "Content-Type": "application/json",
  27. "Authorization": `Bearer ${token}`,
  28. "User-Agent": navigator.userAgent
  29. });
  30.  
  31. const fetchUserData = async (userId, headers) => {
  32. try {
  33. const response = await fetch(`https://www.duolingo.com/2017-06-30/users/${userId}?fields=fromLanguage,learningLanguage,streakData`, { headers });
  34. if (!response.ok) throw new Error("Failed to fetch user data");
  35. return response.json();
  36. } catch (error) {
  37. console.error("Error fetching user data:", error);
  38. return null;
  39. }
  40. };
  41.  
  42. const hasStreakToday = (data) => {
  43. const today = new Date().toISOString().split('T')[0];
  44. return data?.streakData?.currentStreak?.endDate === today;
  45. };
  46.  
  47. const startSession = async (fromLang, learningLang, headers) => {
  48. try {
  49. const payload = {
  50. challengeTypes: ["translate", "match", "tapComplete", "reverseAssist", "judge"],
  51. fromLanguage: fromLang,
  52. learningLanguage: learningLang,
  53. type: "GLOBAL_PRACTICE"
  54. };
  55. const response = await fetch("https://www.duolingo.com/2017-06-30/sessions", {
  56. method: 'POST',
  57. headers,
  58. body: JSON.stringify(payload)
  59. });
  60. if (!response.ok) throw new Error("Failed to start session");
  61. return response.json();
  62. } catch (error) {
  63. console.error("Error starting session:", error);
  64. return null;
  65. }
  66. };
  67.  
  68. const completeSession = async (session, headers) => {
  69. try {
  70. const payload = { ...session, heartsLeft: 0, failed: false, shouldLearnThings: true };
  71. const response = await fetch(`https://www.duolingo.com/2017-06-30/sessions/${session.id}`, {
  72. method: 'PUT',
  73. headers,
  74. body: JSON.stringify(payload)
  75. });
  76. if (!response.ok) throw new Error("Failed to complete session");
  77. return response.json();
  78. } catch (error) {
  79. console.error("Error completing session:", error);
  80. return null;
  81. }
  82. };
  83.  
  84. // 🎉 Hiệu ứng giấy màu rơi xuống 🎉
  85. const createConfetti = () => {
  86. const confettiCount = 100;
  87. for (let i = 0; i < confettiCount; i++) {
  88. const confetti = document.createElement("div");
  89. confetti.classList.add("confetti");
  90. document.body.appendChild(confetti);
  91.  
  92. // Tạo màu sắc ngẫu nhiên
  93. confetti.style.backgroundColor = `hsl(${Math.random() * 360}, 100%, 50%)`;
  94. confetti.style.width = `${6 + Math.random() * 6}px`;
  95. confetti.style.height = confetti.style.width;
  96. confetti.style.position = "fixed";
  97. confetti.style.top = "-10px";
  98. confetti.style.left = `${Math.random() * 100}vw`;
  99. confetti.style.opacity = "0.8";
  100. confetti.style.borderRadius = "50%";
  101.  
  102. // Random tốc độ rơi
  103. const duration = 2 + Math.random() * 3;
  104. confetti.style.animation = `fall ${duration}s ease-out forwards, spin ${duration}s linear infinite`;
  105.  
  106. // Xóa sau khi animation kết thúc
  107. setTimeout(() => {
  108. confetti.remove();
  109. }, duration * 1000);
  110. }
  111. };
  112.  
  113. // Thêm CSS animation
  114. const addConfettiStyle = () => {
  115. const style = document.createElement("style");
  116. style.innerHTML = `
  117. .confetti {
  118. position: fixed;
  119. top: 0;
  120. }
  121. @keyframes fall {
  122. to {
  123. transform: translateY(100vh);
  124. opacity: 0;
  125. }
  126. }
  127. @keyframes spin {
  128. from { transform: rotate(0deg); }
  129. to { transform: rotate(360deg); }
  130. }
  131. `;
  132. document.head.appendChild(style);
  133. };
  134.  
  135. const attemptStreak = async () => {
  136. const token = getToken();
  137. if (!token) return alert("❌ You are not logged into Duolingo!");
  138.  
  139. const userId = parseJwt(token)?.sub;
  140. if (!userId) return alert("❌ Error retrieving user ID.");
  141.  
  142. const headers = getHeaders(token);
  143. const userData = await fetchUserData(userId, headers);
  144. if (!userData) return alert("⚠️ Error fetching user data, try again!");
  145.  
  146. if (hasStreakToday(userData)) return alert("✅ You have already maintained your streak today!");
  147.  
  148. const session = await startSession(userData.fromLanguage, userData.learningLanguage, headers);
  149. if (!session) return alert("⚠️ Error starting session, try again!");
  150.  
  151. const completed = await completeSession(session, headers);
  152. if (completed) {
  153. alert("🎉 Streak has been maintained! Reload the page to check.");
  154. createConfetti();
  155. } else {
  156. alert("⚠️ Error maintaining streak, try again!");
  157. }
  158. };
  159.  
  160. const addButton = () => {
  161. if (document.getElementById("get-streak-btn")) return;
  162.  
  163. const button = document.createElement("button");
  164. button.id = "get-streak-btn";
  165. button.innerText = "🔥 Get Streak 🔥";
  166. button.style.position = "fixed";
  167. button.style.bottom = "20px";
  168. button.style.right = "20px";
  169. button.style.padding = "14px 24px";
  170. button.style.backgroundColor = "#58cc02";
  171. button.style.color = "white";
  172. button.style.fontSize = "18px";
  173. button.style.fontWeight = "bold";
  174. button.style.border = "none";
  175. button.style.borderRadius = "30px";
  176. button.style.boxShadow = "0px 6px 12px rgba(0, 0, 0, 0.2)";
  177. button.style.cursor = "pointer";
  178. button.style.zIndex = "1000";
  179. button.style.transition = "all 0.2s ease-in-out";
  180.  
  181. button.onmouseover = () => {
  182. button.style.backgroundColor = "#46a102";
  183. button.style.transform = "scale(1.1)";
  184. };
  185.  
  186. button.onmouseout = () => {
  187. button.style.backgroundColor = "#58cc02";
  188. button.style.transform = "scale(1)";
  189. };
  190.  
  191. button.onclick = attemptStreak;
  192. document.body.appendChild(button);
  193. };
  194.  
  195. window.onload = () => {
  196. addConfettiStyle();
  197. setTimeout(addButton, 2000);
  198. };

QingJ © 2025

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