DuoHelper (beta)

This tool helps you listen to music while studying,auto solve

当前为 2024-10-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DuoHelper (beta)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.6
  5. // @description This tool helps you listen to music while studying,auto solve
  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. const keys = () => {
  16. const d = (t) => `[data-test="${t}"]`;
  17. return {
  18. AUDIO_BUTTON: d('audio-button'),
  19. BLAME_INCORRECT: d('blame blame-incorrect'),
  20. CHALLENGE: '[data-test~="challenge"]',
  21. CHALLENGE_CHOICE: d('challenge-choice'),
  22. CHALLENGE_CHOICE_CARD: d('challenge-choice-card'),
  23. CHALLENGE_JUDGE_TEXT: d('challenge-judge-text'),
  24. CHALLENGE_LISTEN_SPELL: d('challenge challenge-listenSpell'),
  25. CHALLENGE_LISTEN_TAP: d('challenge-listenTap'),
  26. CHALLENGE_TAP_TOKEN: '[data-test*="challenge-tap-token"]',
  27. CHALLENGE_TAP_TOKEN_TEXT: d('challenge-tap-token-text'),
  28. CHALLENGE_TEXT_INPUT: d('challenge-text-input'),
  29. CHALLENGE_TRANSLATE_INPUT: d('challenge-translate-input'),
  30. CHALLENGE_TYPE_CLOZE: d('challenge challenge-typeCloze'),
  31. CHALLENGE_TYPE_CLOZE_TABLE: d('challenge challenge-typeClozeTable'),
  32. CHARACTER_MATCH: d('challenge challenge-characterMatch'),
  33. PLAYER_NEXT: [d('player-next'), d('story-start')].join(','),
  34. PLAYER_SKIP: d('player-skip'),
  35. STORIES_CHOICE: d('stories-choice'),
  36. STORIES_ELEMENT: d('stories-element'),
  37. STORIES_PLAYER_DONE: d('stories-player-done'),
  38. STORIES_PLAYER_NEXT: d('stories-player-continue'),
  39. STORIES_PLAYER_START: d('story-start'),
  40. TYPE_COMPLETE_TABLE: d('challenge challenge-typeCompleteTable'),
  41. WORD_BANK: d('word-bank'),
  42. PLUS_NO_THANKS: d('plus-no-thanks'),
  43. PRACTICE_HUB_AD_NO_THANKS_BUTTON: d('practice-hub-ad-no-thanks-button')
  44. };
  45. };
  46.  
  47. const TIME_OUT = 650;
  48.  
  49. window.dynamicInput = (element, text) => {
  50. const tag = element.tagName === 'SPAN' ? 'textContent' : 'value';
  51. const input = element;
  52. const lastValue = input[tag];
  53. input[tag] = text;
  54. const event = new Event('input', { bubbles: true });
  55. event.simulated = true;
  56. const tracker = input._valueTracker;
  57. if (tracker) {
  58. tracker.setValue(lastValue);
  59. }
  60. input.dispatchEvent(event);
  61. };
  62.  
  63. window.clickEvent = new MouseEvent('click', {
  64. view: window,
  65. bubbles: true,
  66. cancelable: true,
  67. });
  68.  
  69. window.getReactFiber = (dom) => {
  70. const key = Object.keys(dom).find((key) => {
  71. return (
  72. key.startsWith('__reactFiber$') || // react 17+
  73. key.startsWith('__reactInternalInstance$') // react <17
  74. );
  75. });
  76. return dom[key];
  77. };
  78.  
  79. // Gets Challenge Object
  80. function getElementIndex(element) {
  81. let result = null;
  82. if (element instanceof Array) {
  83. for (let i = 0; i < element.length; i++) {
  84. result = getElementIndex(element[i]);
  85. if (result) break;
  86. }
  87. } else {
  88. for (let prop in element) {
  89. if (prop == 'challenge') {
  90. if (typeof element[prop] == 'object')
  91. return element;
  92. return element[prop];
  93. }
  94. if (element[prop] instanceof Object || element[prop] instanceof Array) {
  95. result = getElementIndex(element[prop]);
  96. if (result) break;
  97. }
  98. }
  99. }
  100. return result;
  101. }
  102.  
  103. function getProps(element) {
  104. let propsClass = Object.keys(element).filter((att) => /^__reactProps/g.test(att))[0];
  105. return element[propsClass];
  106. }
  107.  
  108. // Gets the Challenge
  109. function getChallenge() {
  110. const dataTestDOM = document.querySelectorAll(keys().CHALLENGE);
  111. if (dataTestDOM.length > 0) {
  112. let current = 0;
  113. for (let i = 0; i < dataTestDOM.length; i++) {
  114. if (dataTestDOM[i].childNodes.length > 0)
  115. current = i;
  116. }
  117. const currentDOM = dataTestDOM[current];
  118. const propsValues = getProps(currentDOM);
  119. const { challenge } = getElementIndex(propsValues);
  120. return challenge;
  121. }
  122. }
  123.  
  124. // Solves the Challenge
  125. function classify() {
  126. const challenge = getChallenge();
  127. if (!challenge) return;
  128. window.actions[challenge.type](challenge);
  129. }
  130.  
  131. function pressEnter() {
  132. const clickEvent = new MouseEvent('click', {
  133. view: window,
  134. bubbles: true,
  135. cancelable: false,
  136. });
  137.  
  138. const isPlayerNext = document.querySelector(keys().PLAYER_NEXT);
  139. if (isPlayerNext !== null)
  140. isPlayerNext.dispatchEvent(clickEvent);
  141. }
  142.  
  143. // Main Function
  144. function main() {
  145. try {
  146. const isPlayerNext = document.querySelectorAll(keys().PLAYER_NEXT);
  147. const isAdScreen = document.querySelector([keys().PLUS_NO_THANKS, keys().PRACTICE_HUB_AD_NO_THANKS_BUTTON].join(','));
  148. if (isPlayerNext !== null && isPlayerNext.length > 0) {
  149. if (isPlayerNext[0].getAttribute('aria-disabled') === 'true')
  150. classify();
  151. } else if (isAdScreen !== null && isAdScreen.length > 0) {
  152. isAdScreen.click();
  153. }
  154. setTimeout(pressEnter, 15); // pressEnter();
  155. } catch (e) {
  156. // terminal.log(e);
  157. }
  158. }
  159.  
  160. // To not mess duolingo's own log
  161. function setConsole() {
  162. const iframe = document.createElement('iframe');
  163. iframe.id = 'logger';
  164. iframe.style.display = 'none';
  165. document.body.appendChild(iframe);
  166. window.terminal = iframe.contentWindow.console;
  167. }
  168.  
  169. // Calls main()
  170. let mainInterval;
  171. function solveChallenge() {
  172. if (document.getElementById('logger') == null)
  173. setConsole();
  174.  
  175. // Check if its a Skill / Alphabet / Checkpoint URL
  176. if (/lesson|practice/gi.test(window.location.href) == true) {
  177. clearInterval(mainInterval);
  178. mainInterval = setInterval(main, TIME_OUT);
  179. }
  180.  
  181. }
  182.  
  183. (solveChallenge)();
  184.  
  185. window.keys = keys();
  186. window.actions = {};
  187.  
  188. window.actions.assist =
  189. window.actions.definition = (challenge) => {
  190. const { choices, correctIndex } = challenge;
  191. const tokens = document.querySelectorAll(window.keys.CHALLENGE_CHOICE);
  192. tokens.forEach((e, i) => {
  193. if (i == correctIndex)
  194. e.dispatchEvent(clickEvent);
  195. });
  196. return { choices, correctIndex };
  197. };
  198.  
  199. window.actions.characterMatch = (challenge) => {
  200. const { pairs } = challenge;
  201. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN);
  202. pairs.forEach((pair) => {
  203. for (let i = 0; i < tokens.length; i++) {
  204. if (
  205. tokens[i].innerText === pair.fromToken ||
  206. tokens[i].innerText === pair.learningToken
  207. ) {
  208. tokens[i].dispatchEvent(clickEvent);
  209. }
  210. }
  211. });
  212. return { pairs };
  213. };
  214.  
  215. window.actions.select =
  216. window.actions.gapFill =
  217. window.actions.readComprehension =
  218. window.actions.selectPronunciation =
  219. window.actions.listenComprehension =
  220. window.actions.characterSelect = (challenge) => {
  221. const { choices, correctIndex } = challenge;
  222. const { CHALLENGE_CHOICE } = window.keys;
  223. document.querySelectorAll(CHALLENGE_CHOICE)[correctIndex].dispatchEvent(clickEvent);
  224. return { choices, correctIndex };
  225. };
  226.  
  227. window.actions.completeReverseTranslation = (challenge) => {
  228. const { displayTokens } = challenge;
  229. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
  230. let i = 0;
  231. displayTokens.forEach((token) => {
  232. if (token.isBlank) {
  233. dynamicInput(tokens[i], token.text);
  234. i++;
  235. }
  236. });
  237. return { displayTokens };
  238. };
  239.  
  240. window.actions.characterIntro =
  241. window.actions.dialogue = (challenge) => {
  242. const { choices, correctIndex } = challenge;
  243. document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent);
  244. return { choices, correctIndex };
  245. };
  246.  
  247. window.actions.judge = (challenge) => {
  248. const { correctIndices } = challenge;
  249. document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndices[0]].dispatchEvent(clickEvent);
  250. return { correctIndices };
  251. };
  252.  
  253. window.actions.listen = (challenge) => {
  254. const { prompt } = challenge;
  255. let textInputElement = document.querySelectorAll(window.keys.CHALLENGE_TRANSLATE_INPUT)[0];
  256. dynamicInput(textInputElement, prompt);
  257. return { prompt };
  258. };
  259.  
  260. window.actions.listenComplete = (challenge) => {
  261. const { displayTokens } = challenge;
  262. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
  263. let i = 0;
  264. displayTokens.forEach((token) => {
  265. if (token.isBlank)
  266. dynamicInput(tokens[i], token.text);
  267. });
  268. return { displayTokens };
  269. };
  270.  
  271. window.actions.listenIsolation = (challenge) => {
  272. const { correctIndex } = challenge;
  273. const tokens = document.querySelectorAll(window.keys.CHALLENGE_CHOICE);
  274. tokens.forEach((e, i) => {
  275. if (i == correctIndex) {
  276. e.dispatchEvent(clickEvent);
  277. }
  278. });
  279. return { correctIndex };
  280. };
  281.  
  282. window.actions.listenMatch = (challenge) => {
  283. const { pairs } = challenge;
  284. const tokens = document.querySelectorAll('button'.concat(window.keys.CHALLENGE_TAP_TOKEN));
  285. for (let i = 0; i <= 3; i++) {
  286. const dataset = getReactFiber(tokens[i]).return.child.stateNode.dataset.test;
  287. const word = dataset.split('-')[0];
  288. tokens[i].dispatchEvent(clickEvent);
  289. for (let j = 4; j <= 7; j++) {
  290. const text = tokens[j].querySelector(window.keys.CHALLENGE_TAP_TOKEN_TEXT).innerText;
  291. if (/\s/g.test(dataset) && text.endsWith(` ${word}`)) {
  292. tokens[j].dispatchEvent(clickEvent);
  293. } else if (text == word) {
  294. tokens[j].dispatchEvent(clickEvent);
  295. }
  296. }
  297. }
  298. return { pairs }
  299. };
  300.  
  301. window.actions.listenSpell = (challenge) => {
  302. const { displayTokens } = challenge;
  303. const tokens = document.querySelectorAll(window.keys.CHALLENGE_LISTEN_SPELL.concat(' input[type="text"]:not([readonly])'));
  304. let i = 0;
  305. displayTokens.forEach((word) => {
  306. if (!isNaN(word.damageStart)) {
  307. for (let c of word.text.substring(word.damageStart, word.damageEnd ?? word.text.length)) {
  308. dynamicInput(tokens[i], c);
  309. i++;
  310. }
  311. }
  312. });
  313. return { displayTokens };
  314. };
  315.  
  316. window.actions.listenTap = (challenge) => {
  317. const { correctTokens } = challenge;
  318. const tokens = Array.from(document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN)).filter(e => e.tagName === 'BUTTON');
  319. for (let word of correctTokens) {
  320. for (let i of Object.keys(tokens)) {
  321. if (tokens[i].innerText === word) {
  322. tokens[i].dispatchEvent(clickEvent);
  323. tokens.splice(i, 1);
  324. break;
  325. }
  326. }
  327. }
  328. return { correctTokens };
  329. };
  330.  
  331. window.actions.match = (challenge) => {
  332. const { pairs } = challenge;
  333. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
  334. pairs.forEach((pair) => {
  335. for (let i = 0; i < tokens.length; i++) {
  336. if (
  337. tokens[i].innerText === pair.fromToken ||
  338. tokens[i].innerText === pair.learningToken
  339. ) {
  340. tokens[i].dispatchEvent(clickEvent);
  341. }
  342. }
  343. });
  344. return { pairs };
  345. };
  346.  
  347. window.actions.name = (challenge) => {
  348. const { correctSolutions, articles, grader } = challenge;
  349. let tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
  350. if (articles) {
  351. correctSolutions.forEach((solution) => {
  352. solution = solution.split(' ');
  353. solution.forEach((word) => {
  354. let i = articles.indexOf(word);
  355. if (i > -1) {
  356. document.querySelectorAll(window.keys.CHALLENGE_CHOICE)[i].dispatchEvent(clickEvent);
  357. solution.splice(solution.indexOf(word), 1);
  358. dynamicInput(tokens[0], solution.join(' '));
  359. }
  360. });
  361. });
  362. } else {
  363. correctSolutions.forEach((solution) => {
  364. dynamicInput(tokens[0], solution);
  365. });
  366. }
  367. return { correctSolutions, articles, grader };
  368. };
  369.  
  370. window.actions.partialReverseTranslate = (challenge) => {
  371. const { displayTokens, grader } = challenge;
  372. let tokens = document.querySelectorAll('[contenteditable=true]');
  373. let value = '';
  374. displayTokens.forEach((token) => {
  375. if (token.isBlank)
  376. value = value + token.text;
  377. });
  378. dynamicInput(tokens[0], value);
  379. return { displayTokens, grader };
  380. };
  381.  
  382. window.actions.speak = (challenge) => {
  383. const { prompt } = challenge;
  384. document.querySelectorAll(window.keys.PLAYER_SKIP)[0].dispatchEvent(clickEvent);
  385. return { prompt };
  386. };
  387.  
  388. window.actions.selectTranscription = (challenge) => {
  389. const { choices, correctIndex } = challenge;
  390. document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent);
  391. return { choices, correctIndex };
  392. };
  393.  
  394. window.actions.tapCloze = (challenge) => {
  395. const { choices, correctIndices } = challenge;
  396. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN);
  397. for (let i = 0; i < correctIndices.length; i++) {
  398. choices.forEach((value, j) => {
  399. if (correctIndices[i] == j) {
  400. for (let k = 0; k < tokens.length; k++) {
  401. if (tokens[k].innerText == value) {
  402. tokens[k].dispatchEvent(clickEvent);
  403. }
  404. }
  405. }
  406. });
  407. }
  408. return { choices, correctIndices };
  409. };
  410.  
  411. window.actions.tapClozeTable = (challenge) => {
  412. const { displayTokens } = challenge;
  413. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
  414. displayTokens.forEach((line) => {
  415. line.forEach((column) => {
  416. column.forEach((word) => {
  417. if (word.damageStart) {
  418. tokens.forEach((token) => {
  419. if (token.innerText == word.text.substring(word.damageStart, word.text.length)) {
  420. token.dispatchEvent(clickEvent);
  421. }
  422. });
  423. }
  424. });
  425. });
  426. });
  427. return { displayTokens };
  428. };
  429.  
  430. window.actions.tapComplete = (challenge) => {
  431. const { choices, correctIndices } = challenge;
  432. const tokens = document.querySelectorAll(window.keys.WORD_BANK.concat(' ', window.keys.CHALLENGE_TAP_TOKEN_TEXT));
  433. correctIndices.forEach((i) => {
  434. tokens[i].dispatchEvent(clickEvent);
  435. });
  436. return { choices, correctIndices };
  437. };
  438.  
  439. window.actions.tapCompleteTable = (challenge) => {
  440. const { choices, displayTokens } = challenge;
  441. const tokens = document.querySelectorAll(window.keys.WORD_BANK.concat(' ', window.keys.CHALLENGE_TAP_TOKEN));
  442. displayTokens.forEach((line) => {
  443. line.forEach((column) => {
  444. if (column[0].isBlank == true) {
  445. tokens.forEach((e) => {
  446. if (e.innerText == column[0].text) {
  447. e.dispatchEvent(clickEvent);
  448. }
  449. });
  450. }
  451. });
  452. });
  453. return { choices, displayTokens };
  454. };
  455.  
  456. window.actions.translate = (challenge) => {
  457. const { correctTokens, correctSolutions } = challenge;
  458. if (correctTokens) {
  459. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
  460. let ignoreTokeIndexes = [];
  461. for (let correctTokenIndex in correctTokens) {
  462. for (let tokenIndex in tokens) {
  463. const token = tokens[tokenIndex];
  464. if (ignoreTokeIndexes.includes(tokenIndex)) continue;
  465. if (token.innerText === correctTokens[correctTokenIndex]) {
  466. token.dispatchEvent(clickEvent);
  467. ignoreTokeIndexes.push(tokenIndex);
  468. break;
  469. }
  470. }
  471. }
  472. } else if (correctSolutions) {
  473. let textInputElement = document.querySelectorAll(
  474. window.keys.CHALLENGE_TRANSLATE_INPUT
  475. )[0];
  476. dynamicInput(textInputElement, correctSolutions[0]);
  477. }
  478. return { correctTokens };
  479. };
  480.  
  481. window.actions.typeCloze = (challenge) => {
  482. const { displayTokens } = challenge;
  483. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TYPE_CLOZE.concat(' input'));
  484. let i = 0;
  485. displayTokens.forEach((word) => {
  486. if (word.damageStart) {
  487. dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
  488. i++;
  489. }
  490. });
  491. return { displayTokens };
  492. };
  493.  
  494. window.actions.typeClozeTable = (challenge) => {
  495. const { displayTokens } = challenge;
  496. const tokens = document.querySelectorAll(window.keys.CHALLENGE_TYPE_CLOZE_TABLE.concat(' input'));
  497. let i = 0;
  498. displayTokens.forEach((line) => {
  499. line.forEach((column) => {
  500. column.forEach((word) => {
  501. if (word.damageStart) {
  502. dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
  503. i++;
  504. }
  505. });
  506. });
  507. });
  508. return { displayTokens };
  509. };
  510.  
  511. window.actions.typeCompleteTable = (challenge) => {
  512. const { displayTokens } = challenge;
  513. const tokens = document.querySelectorAll(window.keys.TYPE_COMPLETE_TABLE.concat(' input'));
  514. let index = 0;
  515. displayTokens.forEach((line) => {
  516. line.forEach((column) => {
  517. if (column[0].isBlank == true) {
  518. dynamicInput(tokens[index], column[0].text);
  519. index++;
  520. }
  521. });
  522. });
  523. return { displayTokens };
  524. };
  525. (function() {
  526. 'use strict';
  527.  
  528. let basePing = 100; // Basic Ping (ms)
  529. let baseFps = 60; // Basic FPS
  530.  
  531. let ping = basePing; // Current ping value (ms)
  532. let fps = baseFps; // Current FPS value
  533. let sessionStartTime = Date.now(); // Session start time
  534.  
  535. function isLocalStorageSupported() {
  536. try {
  537. const testKey = '__testKey';
  538. localStorage.setItem(testKey, testKey);
  539. localStorage.removeItem(testKey);
  540. return true;
  541. } catch (error) {
  542. return false;
  543. }
  544. }
  545.  
  546. if (!isLocalStorageSupported()) {
  547. console.error('LocalStorage is not supported.');
  548. return;
  549. }
  550.  
  551. const style = document.createElement('style');
  552. style.textContent = `
  553. :root {
  554. --text-color: black; /* Default text color */
  555. --background-color: white; /* Default background color */
  556. }
  557.  
  558. @keyframes rainbow-border {
  559. 0% { border-color: red; }
  560. 14% { border-color: orange; }
  561. 28% { border-color: yellow; }
  562. 42% { border-color: green; }
  563. 57% { border-color: blue; }
  564. 71% { border-color: indigo; }
  565. 85% { border-color: violet; }
  566. 100% { border-color: red; }
  567. }
  568.  
  569. #performanceMonitor {
  570. position: fixed;
  571. top: 10px;
  572. right: 10px;
  573. padding: 8px;
  574. border: 5px solid;
  575. border-radius: 8px;
  576. font-family: Arial, sans-serif;
  577. font-size: 14px;
  578. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  579. width: 200px;
  580. height: auto;
  581. text-align: left;
  582. overflow: hidden;
  583. cursor: pointer;
  584. z-index: 9999;
  585. transition: opacity 0.3s ease-in-out, width 0.3s ease-in-out, transform 0.3s ease-in-out;
  586. background-color: var(--background-color); /* Use the CSS variable for background color */
  587. background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAA...');
  588. background-size: 32px 32px;
  589. background-repeat: no-repeat;
  590. background-position: 10px center;
  591. animation: rainbow-border 3s linear infinite;
  592. color: var(--text-color); /* Use the CSS variable for text color */
  593. }
  594. #performanceMonitor.hidden {
  595. width: 80px;
  596. transform: scale(0.9);
  597. }
  598. #performanceContentWrapper {
  599. transition: opacity 0.3s ease-in-out;
  600. }
  601. #performanceContentWrapper.hidden {
  602. opacity: 0;
  603. height: 0;
  604. overflow: hidden;
  605. }
  606. #performanceMonitor button {
  607. display: block;
  608. margin-bottom: 5px;
  609. cursor: pointer;
  610. background-color: #444; /* Fixed button background color */
  611. color: white; /* Fixed button text color */
  612. border: none;
  613. border-radius: 4px;
  614. padding: 4px 8px;
  615. font-size: 12px;
  616. transition: background-color 0.3s, transform 0.3s;
  617. }
  618. #performanceMonitor button:hover {
  619. background-color: #666;
  620. transform: scale(1.05);
  621. }
  622. .modal {
  623. position: fixed;
  624. left: 50%;
  625. top: 50%;
  626. transform: translate(-50%, -50%) scale(0);
  627. background-color: rgba(255, 255, 255, 0.9); /* Default modal background */
  628. color: var(--text-color); /* Use the CSS variable for modal text color */
  629. border-radius: 8px;
  630. padding: 20px;
  631. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  632. z-index: 10000;
  633. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  634. opacity: 0;
  635. }
  636. .modal.show {
  637. transform: translate(-50%, -50%) scale(1);
  638. opacity: 1;
  639. }
  640. .modal h3 {
  641. margin-bottom: 10px;
  642. }
  643. .modal label {
  644. display: block;
  645. margin-bottom: 5px;
  646. }
  647. .modal input[type="email"], .modal textarea {
  648. width: 100%;
  649. padding: 8px;
  650. margin-bottom: 10px;
  651. border: 1px solid #666;
  652. border-radius: 4px;
  653. background-color: #f9f9f9; /* Default input background color */
  654. color: var(--text-color); /* Use the CSS variable for input text color */
  655. }
  656. .modal textarea {
  657. height: 100px; /* Set height for feedback textarea */
  658. resize: vertical; /* Allow vertical resizing */
  659. }
  660. .modal button {
  661. margin-top: 10px;
  662. padding: 8px 16px;
  663. background-color: #1cb0f6;
  664. color: white;
  665. border: none;
  666. border-radius: 4px;
  667. cursor: pointer;
  668. transition: background-color 0.3s;
  669. }
  670. .modal button:hover {
  671. background-color: #0a7bb0;
  672. }
  673. #settingsPanel {
  674. position: fixed;
  675. left: 50%;
  676. top: 50%;
  677. transform: translate(-50%, -50%) scale(0);
  678. background-color: rgba(255, 255, 255, 0.9); /* Default settings panel background */
  679. color: var(--text-color); /* Use the CSS variable for settings panel text color */
  680. border-radius: 8px;
  681. padding: 20px;
  682. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  683. z-index: 10001;
  684. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  685. opacity: 0;
  686. }
  687. #settingsPanel.show {
  688. transform: translate(-50%, -50%) scale(1);
  689. opacity: 1;
  690. }
  691. #settingsPanel h3 {
  692. margin-bottom: 10px;
  693. }
  694. #musicMenu {
  695. position: fixed;
  696. left: 50%;
  697. top: 50%;
  698. transform: translate(-50%, -50%) scale(0);
  699. background-color: rgba(255, 255, 255, 0.9);
  700. color: var(--text-color);
  701. border-radius: 8px;
  702. padding: 20px;
  703. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  704. z-index: 10001;
  705. transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
  706. opacity: 0;
  707. max-height: 80%;
  708. overflow-y: auto;
  709. }
  710. #musicMenu.show {
  711. transform: translate(-50%, -50%) scale(1);
  712. opacity: 1;
  713. }
  714. #musicMenu button {
  715. display: block;
  716. margin-bottom: 5px;
  717. cursor: pointer;
  718. background-color: #444;
  719. color: white;
  720. border: none;
  721. border-radius: 4px;
  722. padding: 4px 8px;
  723. font-size: 12px;
  724. transition: background-color 0.3s, transform 0.3s;
  725. }
  726. #musicMenu button:hover {
  727. background-color: #666;
  728. transform: scale(1.05);
  729. }
  730. `;
  731. document.head.appendChild(style);
  732.  
  733. // Add audio element
  734. const audio = document.createElement('audio');
  735. audio.id = 'backgroundMusic';
  736. 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
  737. audio.loop = true; // Lặp lại nhạc liên tục
  738. document.body.appendChild(audio);
  739.  
  740. const container = document.createElement('div');
  741. container.id = 'performanceMonitor';
  742. container.title = 'Click to hide/show';
  743. document.body.appendChild(container);
  744.  
  745. const contentWrapper = document.createElement('div');
  746. contentWrapper.id = 'performanceContentWrapper';
  747. container.appendChild(contentWrapper);
  748.  
  749. const content = document.createElement('div');
  750. content.id = 'performanceContent';
  751. contentWrapper.appendChild(content);
  752.  
  753. const toggleButton = document.createElement('button');
  754. toggleButton.textContent = 'Hide';
  755. toggleButton.addEventListener('mouseover', () => {
  756. toggleButton.style.backgroundColor = '#666';
  757. });
  758. toggleButton.addEventListener('mouseout', () => {
  759. toggleButton.style.backgroundColor = '#444';
  760. });
  761. toggleButton.addEventListener('click', () => {
  762. const isVisible = !contentWrapper.classList.contains('hidden');
  763. contentWrapper.classList.toggle('hidden', isVisible);
  764. toggleButton.textContent = isVisible ? 'Show' : 'Hide';
  765.  
  766. const monitor = document.getElementById('performanceMonitor');
  767. monitor.classList.toggle('hidden', isVisible);
  768. });
  769. container.appendChild(toggleButton);
  770.  
  771. const reloadButton = document.createElement('button');
  772. reloadButton.textContent = 'Reload';
  773. reloadButton.addEventListener('click', () => {
  774. location.reload();
  775. });
  776. contentWrapper.appendChild(reloadButton);
  777.  
  778. const discordButton = document.createElement('button');
  779. discordButton.textContent = 'Discord';
  780. discordButton.addEventListener('click', () => {
  781. window.open('https://discord.gg/XSXPtD5hD4', '_blank');
  782. });
  783. contentWrapper.appendChild(discordButton);
  784.  
  785. // New Choose Music Button
  786. const chooseMusicButton = document.createElement('button');
  787. chooseMusicButton.textContent = 'Choose Music';
  788. chooseMusicButton.addEventListener('click', () => {
  789. showMusicMenu();
  790. });
  791. contentWrapper.appendChild(chooseMusicButton);
  792.  
  793. // Music Menu
  794. const musicMenu = document.createElement('div');
  795. musicMenu.id = 'musicMenu';
  796. musicMenu.innerHTML = `
  797. <h3>Select Music</h3>
  798. <button data-music-url="https://ia600605.us.archive.org/8/items/NeverGonnaGiveYouUp/jocofullinterview41.mp3">Secret :))</button>
  799.  
  800. <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>
  801. <button data-music-url="https://ia601604.us.archive.org/29/items/gio-jank/GI%C3%93%20-%20JANK.mp3">Gió (Song by JanK)</button>
  802. <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>
  803. <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>
  804. <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>
  805. <button data-music-url="https://ia801709.us.archive.org/20/items/10-lies/06%20Runaway.mp3">Runaway</button>
  806. <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>
  807. <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>
  808. <button data-music-url="https://ia601409.us.archive.org/9/items/youtube-Ko63BameVgI/Ko63BameVgI.mp4">少女A</button>
  809. <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>
  810. <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>
  811. <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>
  812. <button data-music-url="https://ia800106.us.archive.org/30/items/LacTroiSonTungMTP/Lac-Troi-Son-Tung-M-TP.mp3">Lc Trôi</button>
  813. <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>
  814. <button data-music-url="https://ia600304.us.archive.org/16/items/soundcloud-295595865/Alan_Walker_-_Fade-295595865.mp3">Faded</button>
  815. <button data-music-url="https://ia800909.us.archive.org/0/items/AlanWalkerAlone_201902/Alan_Walker_-_Alone.mp3">Alone by Alan Walker</button>
  816. <button data-music-url="https://ia801503.us.archive.org/26/items/soundcloud-251045088/Janji_Heroes_Tonight_feat_Johnning_SNC-251045088.mp3">Heroes Tonight</button>
  817. <button data-music-url="https://ia601403.us.archive.org/24/items/soundcloud-1013787601/1013787601.mp3">Royalty</button>
  818. <button data-music-url="https://dn720301.ca.archive.org/0/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>
  819. <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>
  820. <button data-music-url="https://ia804705.us.archive.org/27/items/grimace-cg-5/GRIMACE%20-%20CG5.mp3">CG5 - Grimace</button>
  821. <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>
  822. <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>
  823. <button data-music-url="https://example.com/your-other-music.mp3">Stop Music</button>
  824. <button id="closeMusicMenu">Close</button
  825. `;
  826. document.body.appendChild(musicMenu);
  827.  
  828. document.getElementById('closeMusicMenu').addEventListener('click', () => {
  829. musicMenu.classList.remove('show');
  830. });
  831.  
  832. musicMenu.querySelectorAll('button[data-music-url]').forEach(button => {
  833. button.addEventListener('click', () => {
  834. const musicUrl = button.getAttribute('data-music-url');
  835. const audioElement = document.getElementById('backgroundMusic');
  836. audioElement.src = musicUrl;
  837. audioElement.play();
  838. chooseMusicButton.textContent = 'Choose Music';
  839. musicMenu.classList.remove('show');
  840. });
  841. });
  842.  
  843. const feedbackButton = document.createElement('button');
  844. feedbackButton.textContent = 'Feedback';
  845. feedbackButton.addEventListener('click', () => {
  846. showFeedbackModal();
  847. });
  848. contentWrapper.appendChild(feedbackButton);
  849.  
  850. const settingsButton = document.createElement('button');
  851. settingsButton.textContent = 'Settings';
  852. settingsButton.addEventListener('click', () => {
  853. showSettingsPanel();
  854. });
  855. contentWrapper.appendChild(settingsButton);
  856.  
  857. async function measurePing(url) {
  858. try {
  859. const start = performance.now();
  860. const response = await fetch(url, { method: 'HEAD' });
  861. await response;
  862. const end = performance.now();
  863. const pingValue = Math.round(end - start) + ' ms';
  864. updateDisplay(pingValue);
  865. } catch (error) {
  866. console.error('Ping Error:', error);
  867. updateDisplay('Error');
  868. }
  869. }
  870.  
  871. let lastFrameTime = performance.now();
  872. let frameCount = 0;
  873.  
  874. function measureFPS() {
  875. const now = performance.now();
  876. const delta = now - lastFrameTime;
  877. frameCount++;
  878.  
  879. if (delta >= 1000) {
  880. const fpsValue = Math.round((frameCount * 1000) / delta);
  881. updateDisplay(null, fpsValue);
  882. frameCount = 0;
  883. lastFrameTime = now;
  884. }
  885.  
  886. requestAnimationFrame(measureFPS);
  887. }
  888.  
  889. function updateDisplay(pingValue, fpsValue) {
  890. if (pingValue !== undefined) {
  891. ping = pingValue;
  892. }
  893. if (fpsValue !== undefined) {
  894. fps = fpsValue;
  895. }
  896.  
  897. const elapsedTime = formatSessionTime(Date.now() - sessionStartTime);
  898.  
  899. const display = document.getElementById('performanceContent');
  900. display.innerHTML = `
  901. <div><strong>Ping:</strong> ${ping}</div>
  902. <div><strong>FPS:</strong> ${fps}</div>
  903. <div><strong>Session Time:</strong> ${elapsedTime}</div>
  904. `;
  905. }
  906.  
  907. function formatSessionTime(milliseconds) {
  908. let seconds = Math.floor(milliseconds / 1000);
  909. const hours = Math.floor(seconds / 3600);
  910. seconds %= 3600;
  911. const minutes = Math.floor(seconds / 60);
  912. seconds %= 60;
  913. return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  914. }
  915.  
  916. const feedbackModal = document.createElement('div');
  917. feedbackModal.className = 'modal';
  918. feedbackModal.innerHTML = `
  919. <h3>Send Feedback</h3>
  920. <div>
  921. <label for="feedbackMessage">Your Report:</label>
  922. <textarea id="feedbackMessage"></textarea>
  923. </div>
  924. <button id="sendFeedback" style="margin-top: 10px;">Submit</button>
  925. <button id="cancelFeedback" style="margin-top: 10px;">Cancel</button>
  926. `;
  927. document.body.appendChild(feedbackModal);
  928.  
  929. const settingsPanel = document.createElement('div');
  930. settingsPanel.id = 'settingsPanel';
  931. settingsPanel.innerHTML = `
  932. <h3>Settings</h3>
  933. <div>
  934. <label for="fontSize">Font Size:</label>
  935. <input type="number" id="fontSize" value="14" min="10" max="30" />
  936. </div>
  937. <div>
  938. <label for="backgroundColor">Background Color:</label>
  939. <input type="color" id="backgroundColor" value="#ffffff" />
  940. </div>
  941. <div>
  942. <label for="transparentBackground">Transparent Background:</label>
  943. <input type="checkbox" id="transparentBackground" />
  944. </div>
  945. <button id="applySettings" style="margin-top: 10px;">Apply</button>
  946. <button id="resetSettings" style="margin-top: 10px;">Reset to Default</button>
  947. <button id="cancelSettings" style="margin-top: 10px;">Cancel</button>
  948. `;
  949. document.body.appendChild(settingsPanel);
  950.  
  951. function hideAllPanels() {
  952. feedbackModal.classList.remove('show');
  953. settingsPanel.classList.remove('show');
  954. musicMenu.classList.remove('show');
  955. document.getElementById('performanceContentWrapper').classList.remove('hidden');
  956. }
  957.  
  958. document.getElementById('sendFeedback').addEventListener('click', () => {
  959. const feedback = document.getElementById('feedbackMessage').value;
  960. if (feedback) {
  961. // Normally, you would send the feedback to your backend here.
  962. console.log('Feedback:', feedback);
  963. alert('Feedback sent!');
  964. feedbackModal.classList.remove('show');
  965. } else {
  966. alert('Please enter your feedback.');
  967. }
  968. });
  969.  
  970. document.getElementById('cancelFeedback').addEventListener('click', () => {
  971. feedbackModal.classList.remove('show');
  972. });
  973.  
  974. document.getElementById('applySettings').addEventListener('click', () => {
  975. const fontSize = document.getElementById('fontSize').value + 'px';
  976. const backgroundColor = document.getElementById('backgroundColor').value;
  977. const isTransparent = document.getElementById('transparentBackground').checked;
  978.  
  979. const performanceMonitor = document.getElementById('performanceMonitor');
  980. performanceMonitor.style.fontSize = fontSize;
  981. performanceMonitor.style.backgroundColor = isTransparent ? 'rgba(255, 255, 255, 0)' : backgroundColor;
  982.  
  983. // Update text color based on background color
  984. const textColor = getContrastColor(backgroundColor);
  985. document.documentElement.style.setProperty('--text-color', textColor);
  986.  
  987. alert('Settings applied.');
  988. });
  989.  
  990. document.getElementById('resetSettings').addEventListener('click', () => {
  991. document.getElementById('fontSize').value = '14';
  992. document.getElementById('backgroundColor').value = '#ffffff';
  993. document.getElementById('transparentBackground').checked = false;
  994.  
  995. const performanceMonitor = document.getElementById('performanceMonitor');
  996. performanceMonitor.style.fontSize = '14px';
  997. performanceMonitor.style.backgroundColor = 'white';
  998.  
  999. // Reset text color to default
  1000. document.documentElement.style.setProperty('--text-color', 'black');
  1001.  
  1002. alert('Settings reset to default.');
  1003. });
  1004.  
  1005. document.getElementById('cancelSettings').addEventListener('click', () => {
  1006. settingsPanel.classList.remove('show');
  1007. });
  1008.  
  1009. function showFeedbackModal() {
  1010. hideAllPanels();
  1011. feedbackModal.classList.add('show');
  1012. }
  1013.  
  1014. function showSettingsPanel() {
  1015. hideAllPanels();
  1016. settingsPanel.classList.add('show');
  1017. }
  1018.  
  1019. function showMusicMenu() {
  1020. hideAllPanels();
  1021. musicMenu.classList.add('show');
  1022. }
  1023.  
  1024. function getContrastColor(hex) {
  1025. // Calculate luminance and return black or white based on contrast
  1026. const r = parseInt(hex.substring(1, 3), 16);
  1027. const g = parseInt(hex.substring(3, 5), 16);
  1028. const b = parseInt(hex.substring(5, 7), 16);
  1029.  
  1030. const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
  1031. return luminance > 128 ? 'black' : 'white';
  1032. }
  1033.  
  1034. measureFPS();
  1035.  
  1036. setInterval(() => {
  1037. measurePing('https://www.google.com');
  1038. }, 30000);
  1039.  
  1040. })();