DuoHelper

This tool helps you listen to music while studying

  1. // ==UserScript==
  2. // @name DuoHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3.7
  5. // @description This tool helps you listen to music while studying
  6. // @author @kietxx_163 and @bot1.py
  7. // @match https://*.duolingo.com/*
  8. // @license MIT
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_deleteValue
  12. // @icon https://d35aaqx5ub95lt.cloudfront.net/images/leagues/da3da435ad26e5c57d4c5235406ff938.svg
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. let basePing = 100; // Basic Ping (ms)
  19. let baseFps = 60; // Basic FPS
  20.  
  21. let ping = basePing; // Current ping value (ms)
  22. let fps = baseFps; // Current FPS value
  23. let sessionStartTime = Date.now(); // Session start time
  24.  
  25. function isLocalStorageSupported() {
  26. try {
  27. const testKey = '__testKey';
  28. localStorage.setItem(testKey, testKey);
  29. localStorage.removeItem(testKey);
  30. return true;
  31. } catch (error) {
  32. return false;
  33. }
  34. }
  35.  
  36. if (!isLocalStorageSupported()) {
  37. console.error('LocalStorage is not supported.');
  38. return;
  39. }
  40.  
  41. const style = document.createElement('style');
  42. style.textContent = `
  43. :root {
  44. --text-color: black; /* Default text color */
  45. --background-color: white; /* Default background color */
  46. }
  47.  
  48. @keyframes rainbow-border {
  49. 100% { border-color: black; }
  50. }
  51.  
  52. #performanceMonitor {
  53. position: fixed;
  54. top: 10px;
  55. right: 10px;
  56. padding: 8px;
  57. border: 5px solid;
  58. border-radius: 8px;
  59. font-family: Arial, sans-serif;
  60. font-size: 14px;
  61. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  62. width: 200px;
  63. height: auto;
  64. text-align: left;
  65. overflow: hidden;
  66. cursor: pointer;
  67. z-index: 9999;
  68. transition: opacity 0.3s ease-in-out, width 0.3s ease-in-out, transform 0.3s ease-in-out;
  69. background-color: var(--background-color); /* Use the CSS variable for background color */
  70. background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAA...');
  71. background-size: 32px 32px;
  72. background-repeat: no-repeat;
  73. background-position: 10px center;
  74. animation: rainbow-border 3s linear infinite;
  75. color: var(--text-color); /* Use the CSS variable for text color */
  76. }
  77. #performanceMonitor.hidden {
  78. width: 80px;
  79. transform: scale(0.9);
  80. }
  81. #performanceContentWrapper {
  82. transition: opacity 0.3s ease-in-out;
  83. }
  84. #performanceContentWrapper.hidden {
  85. opacity: 0;
  86. height: 0;
  87. overflow: hidden;
  88. }
  89. #performanceMonitor button {
  90. display: block;
  91. margin-bottom: 5px;
  92. cursor: pointer;
  93. background-color: #444; /* Fixed button background color */
  94. color: white; /* Fixed button text color */
  95. border: none;
  96. border-radius: 4px;
  97. padding: 4px 8px;
  98. font-size: 12px;
  99. transition: background-color 0.3s, transform 0.3s;
  100. }
  101. #performanceMonitor button:hover {
  102. background-color: #666;
  103. transform: scale(1.05);
  104. }
  105. .modal {
  106. position: fixed;
  107. left: 50%;
  108. top: 50%;
  109. transform: translate(-50%, -50%) scale(0);
  110. background-color: rgba(255, 255, 255, 0.9); /* Default modal background */
  111. color: var(--text-color); /* Use the CSS variable for modal text color */
  112. border-radius: 8px;
  113. padding: 20px;
  114. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  115. z-index: 10000;
  116. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  117. opacity: 0;
  118. }
  119. .modal.show {
  120. transform: translate(-50%, -50%) scale(1);
  121. opacity: 1;
  122. }
  123. .modal h3 {
  124. margin-bottom: 10px;
  125. }
  126. .modal label {
  127. display: block;
  128. margin-bottom: 5px;
  129. }
  130. .modal input[type="email"], .modal textarea {
  131. width: 100%;
  132. padding: 8px;
  133. margin-bottom: 10px;
  134. border: 1px solid #666;
  135. border-radius: 4px;
  136. background-color: #f9f9f9; /* Default input background color */
  137. color: var(--text-color); /* Use the CSS variable for input text color */
  138. }
  139. .modal textarea {
  140. height: 100px; /* Set height for feedback textarea */
  141. resize: vertical; /* Allow vertical resizing */
  142. }
  143. .modal button {
  144. margin-top: 10px;
  145. padding: 8px 16px;
  146. background-color: #1cb0f6;
  147. color: white;
  148. border: none;
  149. border-radius: 4px;
  150. cursor: pointer;
  151. transition: background-color 0.3s;
  152. }
  153. .modal button:hover {
  154. background-color: #0a7bb0;
  155. }
  156. #settingsPanel {
  157. position: fixed;
  158. left: 50%;
  159. top: 50%;
  160. transform: translate(-50%, -50%) scale(0);
  161. background-color: rgba(255, 255, 255, 0.9); /* Default settings panel background */
  162. color: var(--text-color); /* Use the CSS variable for settings panel text color */
  163. border-radius: 8px;
  164. padding: 20px;
  165. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  166. z-index: 10001;
  167. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  168. opacity: 0;
  169. }
  170. #settingsPanel.show {
  171. transform: translate(-50%, -50%) scale(1);
  172. opacity: 1;
  173. }
  174. #settingsPanel h3 {
  175. margin-bottom: 10px;
  176. }
  177. #musicMenu {
  178. position: fixed;
  179. left: 50%;
  180. top: 50%;
  181. transform: translate(-50%, -50%) scale(0);
  182. background-color: rgba(255, 255, 255, 0.9);
  183. color: var(--text-color);
  184. border-radius: 8px;
  185. padding: 20px;
  186. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  187. z-index: 10001;
  188. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  189. opacity: 0;
  190. max-height: 80%;
  191. overflow-y: auto;
  192. }
  193. #musicMenu.show {
  194. transform: translate(-50%, -50%) scale(1);
  195. opacity: 1;
  196. }
  197. #musicMenu button {
  198. display: block;
  199. margin-bottom: 5px;
  200. cursor: pointer;
  201. background-color: #444;
  202. color: white;
  203. border: none;
  204. border-radius: 4px;
  205. padding: 4px 8px;
  206. font-size: 12px;
  207. transition: background-color 0.3s, transform 0.3s;
  208. }
  209. #musicMenu button:hover {
  210. background-color: #666;
  211. transform: scale(1.05);
  212. }
  213. `;
  214. document.head.appendChild(style);
  215.  
  216. // Add audio element
  217. const audio = document.createElement('audio');
  218. audio.id = 'backgroundMusic';
  219. audio.src = 'https://ia600605.us.archive.org/8/items/NeverGonnaGiveYouUp/jocofullinterview41.mp3';
  220. audio.loop = true; // Lặp lại nhạc liên tục
  221. document.body.appendChild(audio);
  222.  
  223. const container = document.createElement('div');
  224. container.id = 'performanceMonitor';
  225. container.title = 'Click to hide/show';
  226. document.body.appendChild(container);
  227.  
  228. const contentWrapper = document.createElement('div');
  229. contentWrapper.id = 'performanceContentWrapper';
  230. container.appendChild(contentWrapper);
  231.  
  232. const content = document.createElement('div');
  233. content.id = 'performanceContent';
  234. contentWrapper.appendChild(content);
  235.  
  236. const toggleButton = document.createElement('button');
  237. toggleButton.textContent = 'Hide';
  238. toggleButton.addEventListener('mouseover', () => {
  239. toggleButton.style.backgroundColor = '#666';
  240. });
  241. toggleButton.addEventListener('mouseout', () => {
  242. toggleButton.style.backgroundColor = '#444';
  243. });
  244. toggleButton.addEventListener('click', () => {
  245. const isVisible = !contentWrapper.classList.contains('hidden');
  246. contentWrapper.classList.toggle('hidden', isVisible);
  247. toggleButton.textContent = isVisible ? 'Show' : 'Hide';
  248.  
  249. const monitor = document.getElementById('performanceMonitor');
  250. monitor.classList.toggle('hidden', isVisible);
  251. });
  252. container.appendChild(toggleButton);
  253.  
  254. const reloadButton = document.createElement('button');
  255. reloadButton.textContent = 'Reload';
  256. reloadButton.addEventListener('click', () => {
  257. location.reload();
  258. });
  259. contentWrapper.appendChild(reloadButton);
  260.  
  261. const discordButton = document.createElement('button');
  262. discordButton.textContent = 'Discord';
  263. discordButton.addEventListener('click', () => {
  264. window.open('https://discord.gg/XSXPtD5hD4', '_blank');
  265. window.open('https://discord.gg/TcFcpsPVNm', '_blank');
  266. });
  267. contentWrapper.appendChild(discordButton);
  268.  
  269. // New Music Button
  270. const chooseMusicButton = document.createElement('button');
  271. chooseMusicButton.textContent = 'Music';
  272. chooseMusicButton.addEventListener('click', () => {
  273. showMusicMenu();
  274. });
  275. contentWrapper.appendChild(chooseMusicButton);
  276.  
  277. // Music Menu
  278. const musicMenu = document.createElement('div');
  279. musicMenu.id = 'musicMenu';
  280. musicMenu.innerHTML = `
  281. <h3>Select Music</h3>
  282. <button data-music-url="https://ia601604.us.archive.org/29/items/gio-jank/GI%C3%93%20-%20JANK.mp3">Gió (Song by JanK)</button>
  283. <button data-music-url="https://ia803408.us.archive.org/29/items/keo-bong-gon-xuan-ken/Keo-Bong-Gon-XuanKen.mp3">Ko Bông Gòn</button>
  284. <button data-music-url="https://ia904609.us.archive.org/24/items/VicetoneFeat.CoziZuehlsdorff-Nevadamp3edm.eu/Vicetone%20feat.%20Cozi%20Zuehlsdorff%20%E2%80%93%20Nevada%20%5Bmp3edm.eu%5D.mp3">Nevada</button>
  285. <button data-music-url="https://ia801709.us.archive.org/20/items/10-lies/06%20Runaway.mp3">Runaway</button>
  286. <button data-music-url="https://ia902307.us.archive.org/35/items/the-kid-laroi-justin-bieber-stay_20211019/The%20Kid%20LAROI%20Justin%20Bieber%20STAY.mp3">STAY</button>
  287. <button data-music-url="https://ia801801.us.archive.org/26/items/tuyet-sac-orinn-remix-nam-duc-nhac-tre-mo-xuyen-tet-v.-a-playlist-nhac-cua-tui/Tuy%E1%BB%87t%20S%E1%BA%AFc%20%28Orinn%20Remix%29%20-%20Nam%20%C4%90%E1%BB%A9c%20-%20Nh%E1%BA%A1c%20Tr%E1%BA%BB%20M%E1%BB%9F%20Xuy%C3%AAn%20T%E1%BA%BFt%20-%20V.A%20-%20Playlist%20NhacCuaTui.mp3">Tuyt Sc</button>
  288. <button data-music-url="https://ia601409.us.archive.org/9/items/youtube-Ko63BameVgI/Ko63BameVgI.mp4">少女A</button>
  289. <button data-music-url="https://ia601502.us.archive.org/0/items/NoiNayCoAnhSonTungMTPZingMP3/N%C6%A1i%20N%C3%A0y%20C%C3%B3%20Anh%20-%20S%C6%A1n%20T%C3%B9ng%20M-TP%20_%20Zing%20MP3.MP3">Nơi Này Có Anh</button>
  290. <button data-music-url="https://ia801404.us.archive.org/28/items/duong-toi-cho-em-ve-cukak-remix-buitruonglinh-cukak/%C4%90%C6%B0%E1%BB%9Dng%20T%C3%B4i%20Ch%E1%BB%9F%20Em%20V%E1%BB%81%20%28Cukak%20Remix%29%20-%20buitruonglinh%2C%20Cukak.mp3">đường tôi ch em về</button>
  291. <button data-music-url="https://ia600304.us.archive.org/16/items/soundcloud-295595865/Alan_Walker_-_Fade-295595865.mp3">Faded</button>
  292. <button data-music-url="https://ia800909.us.archive.org/0/items/AlanWalkerAlone_201902/Alan_Walker_-_Alone.mp3">Alone by Alan Walker</button>
  293. <button data-music-url="https://ia801503.us.archive.org/26/items/soundcloud-251045088/Janji_Heroes_Tonight_feat_Johnning_SNC-251045088.mp3">Heroes Tonight</button>
  294. <button data-music-url="https://ia601403.us.archive.org/24/items/soundcloud-1013787601/1013787601.mp3">Royalty</button>
  295. <button data-music-url="https://ia903402.us.archive.org/5/items/100-years-love-nam-duc-hello-lover-v.-a-playlist-nhac-cua-tui/100%20Years%20LOVE%20-%20Nam%20%C4%90%E1%BB%A9c%20-%20Hello%20Lover%20-%20V.A%20-%20Playlist%20NhacCuaTui.mp3">100 Years Love</button>
  296. <button data-music-url="https://ia801808.us.archive.org/20/items/eternxlkz-slay-official-audio/Eternxlkz%20-%20SLAY%21%20%28Official%20Audio%29.mp3">SLAY</button>
  297. <button data-music-url="https://ia804705.us.archive.org/27/items/grimace-cg-5/GRIMACE%20-%20CG5.mp3">CG5 - Grimace</button>
  298. <button data-music-url="https://ia601407.us.archive.org/19/items/dom-dom-jack_202210/%C4%90om%20%C4%90%C3%B3m%20-%20Jack.mp3"om đóm</button>
  299. <button data-music-url="https://ia801607.us.archive.org/21/items/mice-on-venus-vinyl/Mice%20on%20Venus.mp3">Mice On Venus by C418</button>
  300. <button data-music-url="https://ia804708.us.archive.org/17/items/mice-on-venus-but-make-it-extra-nostalgic-1-hour/Mice%20On%20Venus%20but%20make%20it%20extra%20nostalgic%20%281%20hour%29.mp3">Mice On Venus But Make It Extra Nostalgic</button>
  301. <button data-music-url="https://ia800407.us.archive.org/3/items/avicii-the-nights_202409/Avicii_-_The_Nights.mp3">Avicii The Nights</button>
  302. <button data-music-url="https://ia902309.us.archive.org/5/items/avicii-waiting-for-love_202109/Avicii%20-%20Waiting%20For%20Love.mp4">Avicii Waiting For Love</button>
  303. <button data-music-url="https://example.com/your-other-music.mp3">Stop Music</button>
  304. <button id="closeMusicMenu">Close</button
  305. `;
  306. document.body.appendChild(musicMenu);
  307.  
  308. document.getElementById('closeMusicMenu').addEventListener('click', () => {
  309. musicMenu.classList.remove('show');
  310. });
  311.  
  312. musicMenu.querySelectorAll('button[data-music-url]').forEach(button => {
  313. button.addEventListener('click', () => {
  314. const musicUrl = button.getAttribute('data-music-url');
  315. const audioElement = document.getElementById('backgroundMusic');
  316. audioElement.src = musicUrl;
  317. audioElement.play();
  318. chooseMusicButton.textContent = 'Music';
  319. musicMenu.classList.remove('show');
  320. });
  321. });
  322.  
  323. const feedbackButton = document.createElement('button');
  324. feedbackButton.textContent = 'Feedback';
  325. feedbackButton.addEventListener('click', () => {
  326. showFeedbackModal();
  327. });
  328. contentWrapper.appendChild(feedbackButton);
  329.  
  330. const settingsButton = document.createElement('button');
  331. settingsButton.textContent = 'Settings';
  332. settingsButton.addEventListener('click', () => {
  333. showSettingsPanel();
  334. });
  335. contentWrapper.appendChild(settingsButton);
  336.  
  337. async function measurePing(url) {
  338. try {
  339. const start = performance.now();
  340. const response = await fetch(url, { method: 'HEAD' });
  341. await response;
  342. const end = performance.now();
  343. const pingValue = Math.round(end - start) + ' ms';
  344. updateDisplay(pingValue);
  345. } catch (error) {
  346. console.error('Ping Error:', error);
  347. updateDisplay('Error');
  348. }
  349. }
  350.  
  351. let lastFrameTime = performance.now();
  352. let frameCount = 0;
  353.  
  354. function measureFPS() {
  355. const now = performance.now();
  356. const delta = now - lastFrameTime;
  357. frameCount++;
  358.  
  359. if (delta >= 1000) {
  360. const fpsValue = Math.round((frameCount * 1000) / delta);
  361. updateDisplay(null, fpsValue);
  362. frameCount = 0;
  363. lastFrameTime = now;
  364. }
  365.  
  366. requestAnimationFrame(measureFPS);
  367. }
  368.  
  369. function updateDisplay(pingValue, fpsValue) {
  370. if (pingValue !== undefined) {
  371. ping = pingValue;
  372. }
  373. if (fpsValue !== undefined) {
  374. fps = fpsValue;
  375. }
  376.  
  377. const elapsedTime = formatSessionTime(Date.now() - sessionStartTime);
  378.  
  379. const display = document.getElementById('performanceContent');
  380. display.innerHTML = `
  381. <div><strong>Ping:</strong> ${ping}</div>
  382. <div><strong>FPS:</strong> ${fps}</div>
  383. <div><strong>Session Time:</strong> ${elapsedTime}</div>
  384. `;
  385. }
  386.  
  387. function formatSessionTime(milliseconds) {
  388. let seconds = Math.floor(milliseconds / 1000);
  389. const hours = Math.floor(seconds / 3600);
  390. seconds %= 3600;
  391. const minutes = Math.floor(seconds / 60);
  392. seconds %= 60;
  393. return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  394. }
  395.  
  396. const feedbackModal = document.createElement('div');
  397. feedbackModal.className = 'modal';
  398. feedbackModal.innerHTML = `
  399. <h3>Send Feedback</h3>
  400. <div>
  401. <label for="feedbackMessage">Your Report:</label>
  402. <textarea id="feedbackMessage"></textarea>
  403. </div>
  404. <button id="sendFeedback" style="margin-top: 10px;">Submit</button>
  405. <button id="cancelFeedback" style="margin-top: 10px;">Cancel</button>
  406. `;
  407. document.body.appendChild(feedbackModal);
  408.  
  409. const settingsPanel = document.createElement('div');
  410. settingsPanel.id = 'settingsPanel';
  411. settingsPanel.innerHTML = `
  412. <h3>Settings</h3>
  413. <div>
  414. <label for="fontSize">Font Size:</label>
  415. <input type="number" id="fontSize" value="14" min="10" max="30" />
  416. </div>
  417. <div>
  418. <label for="backgroundColor">Background Color:</label>
  419. <input type="color" id="backgroundColor" value="#ffffff" />
  420. </div>
  421. <div>
  422. <label for="transparentBackground">Transparent Background:</label>
  423. <input type="checkbox" id="transparentBackground" />
  424. </div>
  425. <button id="applySettings" style="margin-top: 10px;">Apply</button>
  426. <button id="resetSettings" style="margin-top: 10px;">Reset to Default</button>
  427. <button id="cancelSettings" style="margin-top: 10px;">Cancel</button>
  428. `;
  429. document.body.appendChild(settingsPanel);
  430.  
  431. function hideAllPanels() {
  432. feedbackModal.classList.remove('show');
  433. settingsPanel.classList.remove('show');
  434. musicMenu.classList.remove('show');
  435. document.getElementById('performanceContentWrapper').classList.remove('hidden');
  436. }
  437.  
  438. document.getElementById('sendFeedback').addEventListener('click', () => {
  439. const feedback = document.getElementById('feedbackMessage').value;
  440. if (feedback) {
  441. // Normally, you would send the feedback to your backend here.
  442. console.log('Feedback:', feedback);
  443. alert('Feedback sent!');
  444. feedbackModal.classList.remove('show');
  445. } else {
  446. alert('Please enter your feedback.');
  447. }
  448. });
  449.  
  450. document.getElementById('cancelFeedback').addEventListener('click', () => {
  451. feedbackModal.classList.remove('show');
  452. });
  453.  
  454. document.getElementById('applySettings').addEventListener('click', () => {
  455. const fontSize = document.getElementById('fontSize').value + 'px';
  456. const backgroundColor = document.getElementById('backgroundColor').value;
  457. const isTransparent = document.getElementById('transparentBackground').checked;
  458.  
  459. const performanceMonitor = document.getElementById('performanceMonitor');
  460. performanceMonitor.style.fontSize = fontSize;
  461. performanceMonitor.style.backgroundColor = isTransparent ? 'rgba(255, 255, 255, 0)' : backgroundColor;
  462.  
  463. // Update text color based on background color
  464. const textColor = getContrastColor(backgroundColor);
  465. document.documentElement.style.setProperty('--text-color', textColor);
  466.  
  467. alert('Settings applied.');
  468. });
  469.  
  470. document.getElementById('resetSettings').addEventListener('click', () => {
  471. document.getElementById('fontSize').value = '14';
  472. document.getElementById('backgroundColor').value = '#ffffff';
  473. document.getElementById('transparentBackground').checked = false;
  474.  
  475. const performanceMonitor = document.getElementById('performanceMonitor');
  476. performanceMonitor.style.fontSize = '14px';
  477. performanceMonitor.style.backgroundColor = 'white';
  478.  
  479. // Reset text color to default
  480. document.documentElement.style.setProperty('--text-color', 'black');
  481.  
  482. alert('Settings reset to default.');
  483. });
  484.  
  485. document.getElementById('cancelSettings').addEventListener('click', () => {
  486. settingsPanel.classList.remove('show');
  487. });
  488.  
  489. function showFeedbackModal() {
  490. hideAllPanels();
  491. feedbackModal.classList.add('show');
  492. }
  493.  
  494. function showSettingsPanel() {
  495. hideAllPanels();
  496. settingsPanel.classList.add('show');
  497. }
  498.  
  499. function showMusicMenu() {
  500. hideAllPanels();
  501. musicMenu.classList.add('show');
  502. }
  503.  
  504. function getContrastColor(hex) {
  505. // Calculate luminance and return black or white based on contrast
  506. const r = parseInt(hex.substring(1, 3), 16);
  507. const g = parseInt(hex.substring(3, 5), 16);
  508. const b = parseInt(hex.substring(5, 7), 16);
  509.  
  510. const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
  511. return luminance > 128 ? 'black' : 'white';
  512. }
  513.  
  514. measureFPS();
  515.  
  516. setInterval(() => {
  517. measurePing('https://www.google.com');
  518. }, 30000);
  519.  
  520. })();

QingJ © 2025

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