DuoHelper

This tool helps you listen to music while studying

当前为 2024-12-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DuoHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3.5
  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. 0% { border-color: red; }
  50. 14% { border-color: orange; }
  51. 28% { border-color: yellow; }
  52. 42% { border-color: green; }
  53. 57% { border-color: blue; }
  54. 71% { border-color: indigo; }
  55. 85% { border-color: violet; }
  56. 100% { border-color: red; }
  57. }
  58.  
  59. #performanceMonitor {
  60. position: fixed;
  61. top: 10px;
  62. right: 10px;
  63. padding: 8px;
  64. border: 5px solid;
  65. border-radius: 8px;
  66. font-family: Arial, sans-serif;
  67. font-size: 14px;
  68. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  69. width: 200px;
  70. height: auto;
  71. text-align: left;
  72. overflow: hidden;
  73. cursor: pointer;
  74. z-index: 9999;
  75. transition: opacity 0.3s ease-in-out, width 0.3s ease-in-out, transform 0.3s ease-in-out;
  76. background-color: var(--background-color); /* Use the CSS variable for background color */
  77. background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAA...');
  78. background-size: 32px 32px;
  79. background-repeat: no-repeat;
  80. background-position: 10px center;
  81. animation: rainbow-border 3s linear infinite;
  82. color: var(--text-color); /* Use the CSS variable for text color */
  83. }
  84. #performanceMonitor.hidden {
  85. width: 80px;
  86. transform: scale(0.9);
  87. }
  88. #performanceContentWrapper {
  89. transition: opacity 0.3s ease-in-out;
  90. }
  91. #performanceContentWrapper.hidden {
  92. opacity: 0;
  93. height: 0;
  94. overflow: hidden;
  95. }
  96. #performanceMonitor button {
  97. display: block;
  98. margin-bottom: 5px;
  99. cursor: pointer;
  100. background-color: #444; /* Fixed button background color */
  101. color: white; /* Fixed button text color */
  102. border: none;
  103. border-radius: 4px;
  104. padding: 4px 8px;
  105. font-size: 12px;
  106. transition: background-color 0.3s, transform 0.3s;
  107. }
  108. #performanceMonitor button:hover {
  109. background-color: #666;
  110. transform: scale(1.05);
  111. }
  112. .modal {
  113. position: fixed;
  114. left: 50%;
  115. top: 50%;
  116. transform: translate(-50%, -50%) scale(0);
  117. background-color: rgba(255, 255, 255, 0.9); /* Default modal background */
  118. color: var(--text-color); /* Use the CSS variable for modal text color */
  119. border-radius: 8px;
  120. padding: 20px;
  121. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  122. z-index: 10000;
  123. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  124. opacity: 0;
  125. }
  126. .modal.show {
  127. transform: translate(-50%, -50%) scale(1);
  128. opacity: 1;
  129. }
  130. .modal h3 {
  131. margin-bottom: 10px;
  132. }
  133. .modal label {
  134. display: block;
  135. margin-bottom: 5px;
  136. }
  137. .modal input[type="email"], .modal textarea {
  138. width: 100%;
  139. padding: 8px;
  140. margin-bottom: 10px;
  141. border: 1px solid #666;
  142. border-radius: 4px;
  143. background-color: #f9f9f9; /* Default input background color */
  144. color: var(--text-color); /* Use the CSS variable for input text color */
  145. }
  146. .modal textarea {
  147. height: 100px; /* Set height for feedback textarea */
  148. resize: vertical; /* Allow vertical resizing */
  149. }
  150. .modal button {
  151. margin-top: 10px;
  152. padding: 8px 16px;
  153. background-color: #1cb0f6;
  154. color: white;
  155. border: none;
  156. border-radius: 4px;
  157. cursor: pointer;
  158. transition: background-color 0.3s;
  159. }
  160. .modal button:hover {
  161. background-color: #0a7bb0;
  162. }
  163. #settingsPanel {
  164. position: fixed;
  165. left: 50%;
  166. top: 50%;
  167. transform: translate(-50%, -50%) scale(0);
  168. background-color: rgba(255, 255, 255, 0.9); /* Default settings panel background */
  169. color: var(--text-color); /* Use the CSS variable for settings panel text color */
  170. border-radius: 8px;
  171. padding: 20px;
  172. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  173. z-index: 10001;
  174. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  175. opacity: 0;
  176. }
  177. #settingsPanel.show {
  178. transform: translate(-50%, -50%) scale(1);
  179. opacity: 1;
  180. }
  181. #settingsPanel h3 {
  182. margin-bottom: 10px;
  183. }
  184. #musicMenu {
  185. position: fixed;
  186. left: 50%;
  187. top: 50%;
  188. transform: translate(-50%, -50%) scale(0);
  189. background-color: rgba(255, 255, 255, 0.9);
  190. color: var(--text-color);
  191. border-radius: 8px;
  192. padding: 20px;
  193. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  194. z-index: 10001;
  195. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  196. opacity: 0;
  197. max-height: 80%;
  198. overflow-y: auto;
  199. }
  200. #musicMenu.show {
  201. transform: translate(-50%, -50%) scale(1);
  202. opacity: 1;
  203. }
  204. #musicMenu button {
  205. display: block;
  206. margin-bottom: 5px;
  207. cursor: pointer;
  208. background-color: #444;
  209. color: white;
  210. border: none;
  211. border-radius: 4px;
  212. padding: 4px 8px;
  213. font-size: 12px;
  214. transition: background-color 0.3s, transform 0.3s;
  215. }
  216. #musicMenu button:hover {
  217. background-color: #666;
  218. transform: scale(1.05);
  219. }
  220. `;
  221. document.head.appendChild(style);
  222.  
  223. // Add audio element
  224. const audio = document.createElement('audio');
  225. audio.id = 'backgroundMusic';
  226. audio.src = 'https://ia600605.us.archive.org/8/items/NeverGonnaGiveYouUp/jocofullinterview41.mp3'; // Thay thế URL bằng liên kết đến file nhạc của bạn
  227. audio.loop = true; // Lặp lại nhạc liên tục
  228. document.body.appendChild(audio);
  229.  
  230. const container = document.createElement('div');
  231. container.id = 'performanceMonitor';
  232. container.title = 'Click to hide/show';
  233. document.body.appendChild(container);
  234.  
  235. const contentWrapper = document.createElement('div');
  236. contentWrapper.id = 'performanceContentWrapper';
  237. container.appendChild(contentWrapper);
  238.  
  239. const content = document.createElement('div');
  240. content.id = 'performanceContent';
  241. contentWrapper.appendChild(content);
  242.  
  243. const toggleButton = document.createElement('button');
  244. toggleButton.textContent = 'Hide';
  245. toggleButton.addEventListener('mouseover', () => {
  246. toggleButton.style.backgroundColor = '#666';
  247. });
  248. toggleButton.addEventListener('mouseout', () => {
  249. toggleButton.style.backgroundColor = '#444';
  250. });
  251. toggleButton.addEventListener('click', () => {
  252. const isVisible = !contentWrapper.classList.contains('hidden');
  253. contentWrapper.classList.toggle('hidden', isVisible);
  254. toggleButton.textContent = isVisible ? 'Show' : 'Hide';
  255.  
  256. const monitor = document.getElementById('performanceMonitor');
  257. monitor.classList.toggle('hidden', isVisible);
  258. });
  259. container.appendChild(toggleButton);
  260.  
  261. const reloadButton = document.createElement('button');
  262. reloadButton.textContent = 'Reload';
  263. reloadButton.addEventListener('click', () => {
  264. location.reload();
  265. });
  266. contentWrapper.appendChild(reloadButton);
  267.  
  268. const discordButton = document.createElement('button');
  269. discordButton.textContent = 'Discord';
  270. discordButton.addEventListener('click', () => {
  271. window.open('https://discord.gg/XSXPtD5hD4', '_blank');
  272. window.open('https://discord.gg/TcFcpsPVNm', '_blank');
  273. });
  274. contentWrapper.appendChild(discordButton);
  275.  
  276. // New Music Button
  277. const chooseMusicButton = document.createElement('button');
  278. chooseMusicButton.textContent = 'Music';
  279. chooseMusicButton.addEventListener('click', () => {
  280. showMusicMenu();
  281. });
  282. contentWrapper.appendChild(chooseMusicButton);
  283.  
  284. // Music Menu
  285. const musicMenu = document.createElement('div');
  286. musicMenu.id = 'musicMenu';
  287. musicMenu.innerHTML = `
  288. <h3>Select Music</h3>
  289. <button data-music-url="https://ia601604.us.archive.org/29/items/gio-jank/GI%C3%93%20-%20JANK.mp3">Gió (Song by JanK)</button>
  290. <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>
  291. <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>
  292. <button data-music-url="https://ia801709.us.archive.org/20/items/10-lies/06%20Runaway.mp3">Runaway</button>
  293. <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>
  294. <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>
  295. <button data-music-url="https://ia601409.us.archive.org/9/items/youtube-Ko63BameVgI/Ko63BameVgI.mp4">少女A</button>
  296. <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>
  297. <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>
  298. <button data-music-url="https://ia600304.us.archive.org/16/items/soundcloud-295595865/Alan_Walker_-_Fade-295595865.mp3">Faded</button>
  299. <button data-music-url="https://ia800909.us.archive.org/0/items/AlanWalkerAlone_201902/Alan_Walker_-_Alone.mp3">Alone by Alan Walker</button>
  300. <button data-music-url="https://ia801503.us.archive.org/26/items/soundcloud-251045088/Janji_Heroes_Tonight_feat_Johnning_SNC-251045088.mp3">Heroes Tonight</button>
  301. <button data-music-url="https://ia601403.us.archive.org/24/items/soundcloud-1013787601/1013787601.mp3">Royalty</button>
  302. <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>
  303. <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>
  304. <button data-music-url="https://ia804705.us.archive.org/27/items/grimace-cg-5/GRIMACE%20-%20CG5.mp3">CG5 - Grimace</button>
  305. <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>
  306. <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>
  307. <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>
  308. <button data-music-url="https://ia800407.us.archive.org/3/items/avicii-the-nights_202409/Avicii_-_The_Nights.mp3">Avicii The Nights</button>
  309. <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>
  310. <button data-music-url="https://example.com/your-other-music.mp3">Stop Music</button>
  311. <button id="closeMusicMenu">Close</button
  312. `;
  313. document.body.appendChild(musicMenu);
  314.  
  315. document.getElementById('closeMusicMenu').addEventListener('click', () => {
  316. musicMenu.classList.remove('show');
  317. });
  318.  
  319. musicMenu.querySelectorAll('button[data-music-url]').forEach(button => {
  320. button.addEventListener('click', () => {
  321. const musicUrl = button.getAttribute('data-music-url');
  322. const audioElement = document.getElementById('backgroundMusic');
  323. audioElement.src = musicUrl;
  324. audioElement.play();
  325. chooseMusicButton.textContent = 'Music';
  326. musicMenu.classList.remove('show');
  327. });
  328. });
  329.  
  330. const feedbackButton = document.createElement('button');
  331. feedbackButton.textContent = 'Feedback';
  332. feedbackButton.addEventListener('click', () => {
  333. showFeedbackModal();
  334. });
  335. contentWrapper.appendChild(feedbackButton);
  336.  
  337. const settingsButton = document.createElement('button');
  338. settingsButton.textContent = 'Settings';
  339. settingsButton.addEventListener('click', () => {
  340. showSettingsPanel();
  341. });
  342. contentWrapper.appendChild(settingsButton);
  343.  
  344. async function measurePing(url) {
  345. try {
  346. const start = performance.now();
  347. const response = await fetch(url, { method: 'HEAD' });
  348. await response;
  349. const end = performance.now();
  350. const pingValue = Math.round(end - start) + ' ms';
  351. updateDisplay(pingValue);
  352. } catch (error) {
  353. console.error('Ping Error:', error);
  354. updateDisplay('Error');
  355. }
  356. }
  357.  
  358. let lastFrameTime = performance.now();
  359. let frameCount = 0;
  360.  
  361. function measureFPS() {
  362. const now = performance.now();
  363. const delta = now - lastFrameTime;
  364. frameCount++;
  365.  
  366. if (delta >= 1000) {
  367. const fpsValue = Math.round((frameCount * 1000) / delta);
  368. updateDisplay(null, fpsValue);
  369. frameCount = 0;
  370. lastFrameTime = now;
  371. }
  372.  
  373. requestAnimationFrame(measureFPS);
  374. }
  375.  
  376. function updateDisplay(pingValue, fpsValue) {
  377. if (pingValue !== undefined) {
  378. ping = pingValue;
  379. }
  380. if (fpsValue !== undefined) {
  381. fps = fpsValue;
  382. }
  383.  
  384. const elapsedTime = formatSessionTime(Date.now() - sessionStartTime);
  385.  
  386. const display = document.getElementById('performanceContent');
  387. display.innerHTML = `
  388. <div><strong>Ping:</strong> ${ping}</div>
  389. <div><strong>FPS:</strong> ${fps}</div>
  390. <div><strong>Session Time:</strong> ${elapsedTime}</div>
  391. `;
  392. }
  393.  
  394. function formatSessionTime(milliseconds) {
  395. let seconds = Math.floor(milliseconds / 1000);
  396. const hours = Math.floor(seconds / 3600);
  397. seconds %= 3600;
  398. const minutes = Math.floor(seconds / 60);
  399. seconds %= 60;
  400. return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  401. }
  402.  
  403. const feedbackModal = document.createElement('div');
  404. feedbackModal.className = 'modal';
  405. feedbackModal.innerHTML = `
  406. <h3>Send Feedback</h3>
  407. <div>
  408. <label for="feedbackMessage">Your Report:</label>
  409. <textarea id="feedbackMessage"></textarea>
  410. </div>
  411. <button id="sendFeedback" style="margin-top: 10px;">Submit</button>
  412. <button id="cancelFeedback" style="margin-top: 10px;">Cancel</button>
  413. `;
  414. document.body.appendChild(feedbackModal);
  415.  
  416. const settingsPanel = document.createElement('div');
  417. settingsPanel.id = 'settingsPanel';
  418. settingsPanel.innerHTML = `
  419. <h3>Settings</h3>
  420. <div>
  421. <label for="fontSize">Font Size:</label>
  422. <input type="number" id="fontSize" value="14" min="10" max="30" />
  423. </div>
  424. <div>
  425. <label for="backgroundColor">Background Color:</label>
  426. <input type="color" id="backgroundColor" value="#ffffff" />
  427. </div>
  428. <div>
  429. <label for="transparentBackground">Transparent Background:</label>
  430. <input type="checkbox" id="transparentBackground" />
  431. </div>
  432. <button id="applySettings" style="margin-top: 10px;">Apply</button>
  433. <button id="resetSettings" style="margin-top: 10px;">Reset to Default</button>
  434. <button id="cancelSettings" style="margin-top: 10px;">Cancel</button>
  435. `;
  436. document.body.appendChild(settingsPanel);
  437.  
  438. function hideAllPanels() {
  439. feedbackModal.classList.remove('show');
  440. settingsPanel.classList.remove('show');
  441. musicMenu.classList.remove('show');
  442. document.getElementById('performanceContentWrapper').classList.remove('hidden');
  443. }
  444.  
  445. document.getElementById('sendFeedback').addEventListener('click', () => {
  446. const feedback = document.getElementById('feedbackMessage').value;
  447. if (feedback) {
  448. // Normally, you would send the feedback to your backend here.
  449. console.log('Feedback:', feedback);
  450. alert('Feedback sent!');
  451. feedbackModal.classList.remove('show');
  452. } else {
  453. alert('Please enter your feedback.');
  454. }
  455. });
  456.  
  457. document.getElementById('cancelFeedback').addEventListener('click', () => {
  458. feedbackModal.classList.remove('show');
  459. });
  460.  
  461. document.getElementById('applySettings').addEventListener('click', () => {
  462. const fontSize = document.getElementById('fontSize').value + 'px';
  463. const backgroundColor = document.getElementById('backgroundColor').value;
  464. const isTransparent = document.getElementById('transparentBackground').checked;
  465.  
  466. const performanceMonitor = document.getElementById('performanceMonitor');
  467. performanceMonitor.style.fontSize = fontSize;
  468. performanceMonitor.style.backgroundColor = isTransparent ? 'rgba(255, 255, 255, 0)' : backgroundColor;
  469.  
  470. // Update text color based on background color
  471. const textColor = getContrastColor(backgroundColor);
  472. document.documentElement.style.setProperty('--text-color', textColor);
  473.  
  474. alert('Settings applied.');
  475. });
  476.  
  477. document.getElementById('resetSettings').addEventListener('click', () => {
  478. document.getElementById('fontSize').value = '14';
  479. document.getElementById('backgroundColor').value = '#ffffff';
  480. document.getElementById('transparentBackground').checked = false;
  481.  
  482. const performanceMonitor = document.getElementById('performanceMonitor');
  483. performanceMonitor.style.fontSize = '14px';
  484. performanceMonitor.style.backgroundColor = 'white';
  485.  
  486. // Reset text color to default
  487. document.documentElement.style.setProperty('--text-color', 'black');
  488.  
  489. alert('Settings reset to default.');
  490. });
  491.  
  492. document.getElementById('cancelSettings').addEventListener('click', () => {
  493. settingsPanel.classList.remove('show');
  494. });
  495.  
  496. function showFeedbackModal() {
  497. hideAllPanels();
  498. feedbackModal.classList.add('show');
  499. }
  500.  
  501. function showSettingsPanel() {
  502. hideAllPanels();
  503. settingsPanel.classList.add('show');
  504. }
  505.  
  506. function showMusicMenu() {
  507. hideAllPanels();
  508. musicMenu.classList.add('show');
  509. }
  510.  
  511. function getContrastColor(hex) {
  512. // Calculate luminance and return black or white based on contrast
  513. const r = parseInt(hex.substring(1, 3), 16);
  514. const g = parseInt(hex.substring(3, 5), 16);
  515. const b = parseInt(hex.substring(5, 7), 16);
  516.  
  517. const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
  518. return luminance > 128 ? 'black' : 'white';
  519. }
  520.  
  521. measureFPS();
  522.  
  523. setInterval(() => {
  524. measurePing('https://www.google.com');
  525. }, 30000);
  526.  
  527. })();