Duolingo Helper

This tool helps you listen to music while studying

当前为 2024-09-08 提交的版本,查看 最新版本

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