DuoHelper

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         DuoHelper
// @namespace    http://tampermonkey.net/
// @version      1.2.7
// @description  This tool helps you listen to music while studying,auto solve
// @author       @kietxx_163 and @bot1.py
// @match        https://*.duolingo.com/*
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @icon         https://d35aaqx5ub95lt.cloudfront.net/images/leagues/da3da435ad26e5c57d4c5235406ff938.svg
// ==/UserScript==

const keys = () => {
    const d = (t) => `[data-test="${t}"]`;
    return {
        AUDIO_BUTTON: d('audio-button'),
        BLAME_INCORRECT: d('blame blame-incorrect'),
        CHALLENGE: '[data-test~="challenge"]',
        CHALLENGE_CHOICE: d('challenge-choice'),
        CHALLENGE_CHOICE_CARD: d('challenge-choice-card'),
        CHALLENGE_JUDGE_TEXT: d('challenge-judge-text'),
        CHALLENGE_LISTEN_SPELL: d('challenge challenge-listenSpell'),
        CHALLENGE_LISTEN_TAP: d('challenge-listenTap'),
        CHALLENGE_TAP_TOKEN: '[data-test*="challenge-tap-token"]',
        CHALLENGE_TAP_TOKEN_TEXT: d('challenge-tap-token-text'),
        CHALLENGE_TEXT_INPUT: d('challenge-text-input'),
        CHALLENGE_TRANSLATE_INPUT: d('challenge-translate-input'),
        CHALLENGE_TYPE_CLOZE: d('challenge challenge-typeCloze'),
        CHALLENGE_TYPE_CLOZE_TABLE: d('challenge challenge-typeClozeTable'),
        CHARACTER_MATCH: d('challenge challenge-characterMatch'),
        PLAYER_NEXT: [d('player-next'), d('story-start')].join(','),
        PLAYER_SKIP: d('player-skip'),
        STORIES_CHOICE: d('stories-choice'),
        STORIES_ELEMENT: d('stories-element'),
        STORIES_PLAYER_DONE: d('stories-player-done'),
        STORIES_PLAYER_NEXT: d('stories-player-continue'),
        STORIES_PLAYER_START: d('story-start'),
        TYPE_COMPLETE_TABLE: d('challenge challenge-typeCompleteTable'),
        WORD_BANK: d('word-bank'),
        PLUS_NO_THANKS: d('plus-no-thanks'),
        PRACTICE_HUB_AD_NO_THANKS_BUTTON: d('practice-hub-ad-no-thanks-button')
    };
};

const TIME_OUT = 700;

window.dynamicInput = (element, text) => {
    const tag = element.tagName === 'SPAN' ? 'textContent' : 'value';
    const input = element;
    const lastValue = input[tag];
    input[tag] = text;
    const event = new Event('input', { bubbles: true });
    event.simulated = true;
    const tracker = input._valueTracker;
    if (tracker) {
        tracker.setValue(lastValue);
    }
    input.dispatchEvent(event);
};

window.clickEvent = new MouseEvent('click', {
    view: window,
    bubbles: true,
    cancelable: true,
});

window.getReactFiber = (dom) => {
    const key = Object.keys(dom).find((key) => {
        return (
            key.startsWith('__reactFiber$') || // react 17+
            key.startsWith('__reactInternalInstance$') // react <17
        );
    });
    return dom[key];
};

// Gets Challenge Object
function getElementIndex(element) {
    let result = null;
    if (element instanceof Array) {
        for (let i = 0; i < element.length; i++) {
            result = getElementIndex(element[i]);
            if (result) break;
        }
    } else {
        for (let prop in element) {
            if (prop == 'challenge') {
                if (typeof element[prop] == 'object')
                    return element;
                return element[prop];
            }
            if (element[prop] instanceof Object || element[prop] instanceof Array) {
                result = getElementIndex(element[prop]);
                if (result) break;
            }
        }
    }
    return result;
}

function getProps(element) {
    let propsClass = Object.keys(element).filter((att) => /^__reactProps/g.test(att))[0];
    return element[propsClass];
}

// Gets the Challenge
function getChallenge() {
    const dataTestDOM = document.querySelectorAll(keys().CHALLENGE);
    if (dataTestDOM.length > 0) {
        let current = 0;
        for (let i = 0; i < dataTestDOM.length; i++) {
            if (dataTestDOM[i].childNodes.length > 0)
                current = i;
        }
        const currentDOM = dataTestDOM[current];
        const propsValues = getProps(currentDOM);
        const { challenge } = getElementIndex(propsValues);
        return challenge;
    }
}

// Solves the Challenge
function classify() {
    const challenge = getChallenge();
    if (!challenge) return;
    window.actions[challenge.type](challenge);
}

function pressEnter() {
    const clickEvent = new MouseEvent('click', {
        view: window,
        bubbles: true,
        cancelable: false,
    });

    const isPlayerNext = document.querySelector(keys().PLAYER_NEXT);
    if (isPlayerNext !== null)
        isPlayerNext.dispatchEvent(clickEvent);
}

// Main Function
function main() {
    try {
        const isPlayerNext = document.querySelectorAll(keys().PLAYER_NEXT);
        const isAdScreen = document.querySelector([keys().PLUS_NO_THANKS, keys().PRACTICE_HUB_AD_NO_THANKS_BUTTON].join(','));
        if (isPlayerNext !== null && isPlayerNext.length > 0) {
            if (isPlayerNext[0].getAttribute('aria-disabled') === 'true')
                classify();
        } else if (isAdScreen !== null && isAdScreen.length > 0) {
            isAdScreen.click();
        }
        setTimeout(pressEnter, 15); // pressEnter();
    } catch (e) {
        // terminal.log(e);
    }
}

// To not mess duolingo's own log
function setConsole() {
    const iframe = document.createElement('iframe');
    iframe.id = 'logger';
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
    window.terminal = iframe.contentWindow.console;
}

// Calls main()
let mainInterval;
function solveChallenge() {
    if (document.getElementById('logger') == null)
        setConsole();

    // Check if its a Skill / Alphabet / Checkpoint URL
    if (/lesson|practice/gi.test(window.location.href) == true) {
        clearInterval(mainInterval);
        mainInterval = setInterval(main, TIME_OUT);
    }

}

(solveChallenge)();

window.keys = keys();
window.actions = {};

window.actions.assist =
window.actions.definition = (challenge) => {
    const { choices, correctIndex } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_CHOICE);
    tokens.forEach((e, i) => {
        if (i == correctIndex)
            e.dispatchEvent(clickEvent);
    });
    return { choices, correctIndex };
};

window.actions.characterMatch = (challenge) => {
    const { pairs } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN);
    pairs.forEach((pair) => {
        for (let i = 0; i < tokens.length; i++) {
            if (
                tokens[i].innerText === pair.fromToken ||
                tokens[i].innerText === pair.learningToken
            ) {
                tokens[i].dispatchEvent(clickEvent);
            }
        }
    });
    return { pairs };
};

window.actions.select =
window.actions.gapFill =
window.actions.readComprehension =
window.actions.selectPronunciation =
window.actions.listenComprehension =
window.actions.characterSelect = (challenge) => {
    const { choices, correctIndex } = challenge;
    const { CHALLENGE_CHOICE } = window.keys;
    document.querySelectorAll(CHALLENGE_CHOICE)[correctIndex].dispatchEvent(clickEvent);
    return { choices, correctIndex };
};

window.actions.completeReverseTranslation = (challenge) => {
    const { displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
    let i = 0;
    displayTokens.forEach((token) => {
        if (token.isBlank) {
            dynamicInput(tokens[i], token.text);
            i++;
        }
    });
    return { displayTokens };
};

window.actions.characterIntro =
window.actions.dialogue = (challenge) => {
    const { choices, correctIndex } = challenge;
    document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent);
    return { choices, correctIndex };
};

window.actions.judge = (challenge) => {
    const { correctIndices } = challenge;
    document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndices[0]].dispatchEvent(clickEvent);
    return { correctIndices };
};

window.actions.listen = (challenge) => {
    const { prompt } = challenge;
    let textInputElement = document.querySelectorAll(window.keys.CHALLENGE_TRANSLATE_INPUT)[0];
    dynamicInput(textInputElement, prompt);
    return { prompt };
};

window.actions.listenComplete = (challenge) => {
    const { displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
    let i = 0;
    displayTokens.forEach((token) => {
        if (token.isBlank)
            dynamicInput(tokens[i], token.text);
    });
    return { displayTokens };
};

window.actions.listenIsolation = (challenge) => {
    const { correctIndex } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_CHOICE);
    tokens.forEach((e, i) => {
        if (i == correctIndex) {
            e.dispatchEvent(clickEvent);
        }
    });
    return { correctIndex };
};

window.actions.listenMatch = (challenge) => {
    const { pairs } = challenge;
    const tokens = document.querySelectorAll('button'.concat(window.keys.CHALLENGE_TAP_TOKEN));
    for (let i = 0; i <= 3; i++) {
        const dataset = getReactFiber(tokens[i]).return.child.stateNode.dataset.test;
        const word = dataset.split('-')[0];
        tokens[i].dispatchEvent(clickEvent);
        for (let j = 4; j <= 7; j++) {
            const text = tokens[j].querySelector(window.keys.CHALLENGE_TAP_TOKEN_TEXT).innerText;
            if (/\s/g.test(dataset) && text.endsWith(` ${word}`)) {
                tokens[j].dispatchEvent(clickEvent);
            } else if (text == word) {
                tokens[j].dispatchEvent(clickEvent);
            }
        }
    }
    return { pairs }
};

window.actions.listenSpell = (challenge) => {
    const { displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_LISTEN_SPELL.concat(' input[type="text"]:not([readonly])'));
    let i = 0;
    displayTokens.forEach((word) => {
        if (!isNaN(word.damageStart)) {
            for (let c of word.text.substring(word.damageStart, word.damageEnd ?? word.text.length)) {
                dynamicInput(tokens[i], c);
                i++;
            }
        }
    });
    return { displayTokens };
};

window.actions.listenTap = (challenge) => {
    const { correctTokens } = challenge;
    const tokens = Array.from(document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN)).filter(e => e.tagName === 'BUTTON');
    for (let word of correctTokens) {
        for (let i of Object.keys(tokens)) {
            if (tokens[i].innerText === word) {
                tokens[i].dispatchEvent(clickEvent);
                tokens.splice(i, 1);
                break;
            }
        }
    }
    return { correctTokens };
};

window.actions.match = (challenge) => {
    const { pairs } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
    pairs.forEach((pair) => {
        for (let i = 0; i < tokens.length; i++) {
            if (
                tokens[i].innerText === pair.fromToken ||
                tokens[i].innerText === pair.learningToken
            ) {
                tokens[i].dispatchEvent(clickEvent);
            }
        }
    });
    return { pairs };
};

window.actions.name = (challenge) => {
    const { correctSolutions, articles, grader } = challenge;
    let tokens = document.querySelectorAll(window.keys.CHALLENGE_TEXT_INPUT);
    if (articles) {
        correctSolutions.forEach((solution) => {
            solution = solution.split(' ');
            solution.forEach((word) => {
                let i = articles.indexOf(word);
                if (i > -1) {
                    document.querySelectorAll(window.keys.CHALLENGE_CHOICE)[i].dispatchEvent(clickEvent);
                    solution.splice(solution.indexOf(word), 1);
                    dynamicInput(tokens[0], solution.join(' '));
                }
            });
        });
    } else {
        correctSolutions.forEach((solution) => {
            dynamicInput(tokens[0], solution);
        });
    }
    return { correctSolutions, articles, grader };
};

window.actions.partialReverseTranslate = (challenge) => {
    const { displayTokens, grader } = challenge;
    let tokens = document.querySelectorAll('[contenteditable=true]');
    let value = '';
    displayTokens.forEach((token) => {
        if (token.isBlank)
            value = value + token.text;
    });
    dynamicInput(tokens[0], value);
    return { displayTokens, grader };
};

window.actions.speak = (challenge) => {
    const { prompt } = challenge;
    document.querySelectorAll(window.keys.PLAYER_SKIP)[0].dispatchEvent(clickEvent);
    return { prompt };
};

window.actions.selectTranscription = (challenge) => {
    const { choices, correctIndex } = challenge;
    document.querySelectorAll(window.keys.CHALLENGE_JUDGE_TEXT)[correctIndex].dispatchEvent(clickEvent);
    return { choices, correctIndex };
};

window.actions.tapCloze = (challenge) => {
    const { choices, correctIndices } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN);
    for (let i = 0; i < correctIndices.length; i++) {
        choices.forEach((value, j) => {
            if (correctIndices[i] == j) {
                for (let k = 0; k < tokens.length; k++) {
                    if (tokens[k].innerText == value) {
                        tokens[k].dispatchEvent(clickEvent);
                    }
                }
            }
        });
    }
    return { choices, correctIndices };
};

window.actions.tapClozeTable = (challenge) => {
    const { displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
    displayTokens.forEach((line) => {
        line.forEach((column) => {
            column.forEach((word) => {
                if (word.damageStart) {
                    tokens.forEach((token) => {
                        if (token.innerText == word.text.substring(word.damageStart, word.text.length)) {
                            token.dispatchEvent(clickEvent);
                        }
                    });
                }
            });
        });
    });
    return { displayTokens };
};

window.actions.tapComplete = (challenge) => {
    const { choices, correctIndices } = challenge;
    const tokens = document.querySelectorAll(window.keys.WORD_BANK.concat(' ', window.keys.CHALLENGE_TAP_TOKEN_TEXT));
    correctIndices.forEach((i) => {
        tokens[i].dispatchEvent(clickEvent);
    });
    return { choices, correctIndices };
};

window.actions.tapCompleteTable = (challenge) => {
    const { choices, displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.WORD_BANK.concat(' ', window.keys.CHALLENGE_TAP_TOKEN));
    displayTokens.forEach((line) => {
        line.forEach((column) => {
            if (column[0].isBlank == true) {
                tokens.forEach((e) => {
                    if (e.innerText == column[0].text) {
                        e.dispatchEvent(clickEvent);
                    }
                });
            }
        });
    });
    return { choices, displayTokens };
};

window.actions.translate = (challenge) => {
    const { correctTokens, correctSolutions } = challenge;
    if (correctTokens) {
        const tokens = document.querySelectorAll(window.keys.CHALLENGE_TAP_TOKEN_TEXT);
        let ignoreTokeIndexes = [];
        for (let correctTokenIndex in correctTokens) {
            for (let tokenIndex in tokens) {
                const token = tokens[tokenIndex];
                if (ignoreTokeIndexes.includes(tokenIndex)) continue;
                if (token.innerText === correctTokens[correctTokenIndex]) {
                    token.dispatchEvent(clickEvent);
                    ignoreTokeIndexes.push(tokenIndex);
                    break;
                }
            }
        }
    } else if (correctSolutions) {
        let textInputElement = document.querySelectorAll(
            window.keys.CHALLENGE_TRANSLATE_INPUT
        )[0];
        dynamicInput(textInputElement, correctSolutions[0]);
    }
    return { correctTokens };
};

window.actions.typeCloze = (challenge) => {
    const { displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TYPE_CLOZE.concat(' input'));
    let i = 0;
    displayTokens.forEach((word) => {
        if (word.damageStart) {
            dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
            i++;
        }
    });
    return { displayTokens };
};

window.actions.typeClozeTable = (challenge) => {
    const { displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.CHALLENGE_TYPE_CLOZE_TABLE.concat(' input'));
    let i = 0;
    displayTokens.forEach((line) => {
        line.forEach((column) => {
            column.forEach((word) => {
                if (word.damageStart) {
                    dynamicInput(tokens[i], word.text.substring(word.damageStart, word.text.length));
                    i++;
                }
            });
        });
    });
    return { displayTokens };
};

window.actions.typeCompleteTable = (challenge) => {
    const { displayTokens } = challenge;
    const tokens = document.querySelectorAll(window.keys.TYPE_COMPLETE_TABLE.concat(' input'));
    let index = 0;
    displayTokens.forEach((line) => {
        line.forEach((column) => {
            if (column[0].isBlank == true) {
                dynamicInput(tokens[index], column[0].text);
                index++;
            }
        });
    });
    return { displayTokens };
};
(function() {
    'use strict';

    let basePing = 100; // Basic Ping (ms)
    let baseFps = 60; // Basic FPS

    let ping = basePing; // Current ping value (ms)
    let fps = baseFps; // Current FPS value
    let sessionStartTime = Date.now(); // Session start time

    function isLocalStorageSupported() {
        try {
            const testKey = '__testKey';
            localStorage.setItem(testKey, testKey);
            localStorage.removeItem(testKey);
            return true;
        } catch (error) {
            return false;
        }
    }

    if (!isLocalStorageSupported()) {
        console.error('LocalStorage is not supported.');
        return;
    }

    const style = document.createElement('style');
    style.textContent = `
        :root {
            --text-color: black; /* Default text color */
            --background-color: white; /* Default background color */
        }

        @keyframes rainbow-border {
            0% { border-color: red; }
            14% { border-color: orange; }
            28% { border-color: yellow; }
            42% { border-color: green; }
            57% { border-color: blue; }
            71% { border-color: indigo; }
            85% { border-color: violet; }
            100% { border-color: red; }
        }

        #performanceMonitor {
            position: fixed;
            top: 10px;
            right: 10px;
            padding: 8px;
            border: 5px solid;
            border-radius: 8px;
            font-family: Arial, sans-serif;
            font-size: 14px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
            width: 200px;
            height: auto;
            text-align: left;
            overflow: hidden;
            cursor: pointer;
            z-index: 9999;
            transition: opacity 0.3s ease-in-out, width 0.3s ease-in-out, transform 0.3s ease-in-out;
            background-color: var(--background-color); /* Use the CSS variable for background color */
            background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAA...');
            background-size: 32px 32px;
            background-repeat: no-repeat;
            background-position: 10px center;
            animation: rainbow-border 3s linear infinite;
            color: var(--text-color); /* Use the CSS variable for text color */
        }
        #performanceMonitor.hidden {
            width: 80px;
            transform: scale(0.9);
        }
        #performanceContentWrapper {
            transition: opacity 0.3s ease-in-out;
        }
        #performanceContentWrapper.hidden {
            opacity: 0;
            height: 0;
            overflow: hidden;
        }
        #performanceMonitor button {
            display: block;
            margin-bottom: 5px;
            cursor: pointer;
            background-color: #444; /* Fixed button background color */
            color: white; /* Fixed button text color */
            border: none;
            border-radius: 4px;
            padding: 4px 8px;
            font-size: 12px;
            transition: background-color 0.3s, transform 0.3s;
        }
        #performanceMonitor button:hover {
            background-color: #666;
            transform: scale(1.05);
        }
        .modal {
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%) scale(0);
            background-color: rgba(255, 255, 255, 0.9); /* Default modal background */
            color: var(--text-color); /* Use the CSS variable for modal text color */
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            z-index: 10000;
            transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
            opacity: 0;
        }
        .modal.show {
            transform: translate(-50%, -50%) scale(1);
            opacity: 1;
        }
        .modal h3 {
            margin-bottom: 10px;
        }
        .modal label {
            display: block;
            margin-bottom: 5px;
        }
        .modal input[type="email"], .modal textarea {
            width: 100%;
            padding: 8px;
            margin-bottom: 10px;
            border: 1px solid #666;
            border-radius: 4px;
            background-color: #f9f9f9; /* Default input background color */
            color: var(--text-color); /* Use the CSS variable for input text color */
        }
        .modal textarea {
            height: 100px; /* Set height for feedback textarea */
            resize: vertical; /* Allow vertical resizing */
        }
        .modal button {
            margin-top: 10px;
            padding: 8px 16px;
            background-color: #1cb0f6;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        .modal button:hover {
            background-color: #0a7bb0;
        }
        #settingsPanel {
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%) scale(0);
            background-color: rgba(255, 255, 255, 0.9); /* Default settings panel background */
            color: var(--text-color); /* Use the CSS variable for settings panel text color */
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            z-index: 10001;
            transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
            opacity: 0;
        }
        #settingsPanel.show {
            transform: translate(-50%, -50%) scale(1);
            opacity: 1;
        }
        #settingsPanel h3 {
            margin-bottom: 10px;
        }
        #musicMenu {
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%) scale(0);
            background-color: rgba(255, 255, 255, 0.9);
            color: var(--text-color);
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            z-index: 10001;
            transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
            opacity: 0;
            max-height: 80%;
            overflow-y: auto;
        }
        #musicMenu.show {
            transform: translate(-50%, -50%) scale(1);
            opacity: 1;
        }
        #musicMenu button {
            display: block;
            margin-bottom: 5px;
            cursor: pointer;
            background-color: #444;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 4px 8px;
            font-size: 12px;
            transition: background-color 0.3s, transform 0.3s;
        }
        #musicMenu button:hover {
            background-color: #666;
            transform: scale(1.05);
        }
    `;
    document.head.appendChild(style);

    // Add audio element
    const audio = document.createElement('audio');
    audio.id = 'backgroundMusic';
    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
    audio.loop = true; // Lặp lại nhạc liên tục
    document.body.appendChild(audio);

    const container = document.createElement('div');
    container.id = 'performanceMonitor';
    container.title = 'Click to hide/show';
    document.body.appendChild(container);

    const contentWrapper = document.createElement('div');
    contentWrapper.id = 'performanceContentWrapper';
    container.appendChild(contentWrapper);

    const content = document.createElement('div');
    content.id = 'performanceContent';
    contentWrapper.appendChild(content);

    const toggleButton = document.createElement('button');
    toggleButton.textContent = 'Hide';
    toggleButton.addEventListener('mouseover', () => {
        toggleButton.style.backgroundColor = '#666';
    });
    toggleButton.addEventListener('mouseout', () => {
        toggleButton.style.backgroundColor = '#444';
    });
    toggleButton.addEventListener('click', () => {
        const isVisible = !contentWrapper.classList.contains('hidden');
        contentWrapper.classList.toggle('hidden', isVisible);
        toggleButton.textContent = isVisible ? 'Show' : 'Hide';

        const monitor = document.getElementById('performanceMonitor');
        monitor.classList.toggle('hidden', isVisible);
    });
    container.appendChild(toggleButton);

    const reloadButton = document.createElement('button');
    reloadButton.textContent = 'Reload';
    reloadButton.addEventListener('click', () => {
        location.reload();
    });
    contentWrapper.appendChild(reloadButton);

    const discordButton = document.createElement('button');
    discordButton.textContent = 'Discord';
    discordButton.addEventListener('click', () => {
        window.open('https://discord.gg/XSXPtD5hD4', '_blank');
    });
    contentWrapper.appendChild(discordButton);

    // New Choose Music Button
    const chooseMusicButton = document.createElement('button');
    chooseMusicButton.textContent = 'Choose Music';
    chooseMusicButton.addEventListener('click', () => {
        showMusicMenu();
    });
    contentWrapper.appendChild(chooseMusicButton);

    // Music Menu
    const musicMenu = document.createElement('div');
    musicMenu.id = 'musicMenu';
    musicMenu.innerHTML = `
        <h3>Select Music</h3>
        <button data-music-url="https://ia600605.us.archive.org/8/items/NeverGonnaGiveYouUp/jocofullinterview41.mp3">Secret :))</button>

                <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>
   <button data-music-url="https://ia601604.us.archive.org/29/items/gio-jank/GI%C3%93%20-%20JANK.mp3">Gió (Song by JanK)</button>
<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 nắng</button>
<button data-music-url="https://ia803408.us.archive.org/29/items/keo-bong-gon-xuan-ken/Keo-Bong-Gon-XuanKen.mp3">Kẹo Bông Gòn</button>
  <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>
 <button data-music-url="https://ia801709.us.archive.org/20/items/10-lies/06%20Runaway.mp3">Runaway</button>
         <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>
                  <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">Tuyệt Sắc</button>
                <button data-music-url="https://ia601409.us.archive.org/9/items/youtube-Ko63BameVgI/Ko63BameVgI.mp4">少女A</button>
                         <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">Muộn Rồi Mà Sao Còn</button>
                         <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>
                               <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>
                                                  <button data-music-url="https://ia800106.us.archive.org/30/items/LacTroiSonTungMTP/Lac-Troi-Son-Tung-M-TP.mp3">Lạc Trôi</button>
                                                  <button data-music-url="https://dn720301.ca.archive.org/0/items/vietnamese-communist-anthems-old-recordings/Vietnamese%20Communist%20Anthems%20%5BOld%20Recordings%5D.mp3">Quốc Ca Việt Nam</button>
                                                  <button data-music-url="https://ia600304.us.archive.org/16/items/soundcloud-295595865/Alan_Walker_-_Fade-295595865.mp3">Faded</button>
                                                  <button data-music-url="https://ia800909.us.archive.org/0/items/AlanWalkerAlone_201902/Alan_Walker_-_Alone.mp3">Alone by Alan Walker</button>
                                                  <button data-music-url="https://ia801503.us.archive.org/26/items/soundcloud-251045088/Janji_Heroes_Tonight_feat_Johnning_SNC-251045088.mp3">Heroes Tonight</button>
                                                  <button data-music-url="https://ia601403.us.archive.org/24/items/soundcloud-1013787601/1013787601.mp3">Royalty</button>
                                                  <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>
                                                  <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>
                                                  <button data-music-url="https://ia804705.us.archive.org/27/items/grimace-cg-5/GRIMACE%20-%20CG5.mp3">CG5 - Grimace</button>
                                                  <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>
                                                  <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>
              <button data-music-url="https://example.com/your-other-music.mp3">Stop Music</button>
        <button id="closeMusicMenu">Close</button
    `;
    document.body.appendChild(musicMenu);

    document.getElementById('closeMusicMenu').addEventListener('click', () => {
        musicMenu.classList.remove('show');
    });

    musicMenu.querySelectorAll('button[data-music-url]').forEach(button => {
        button.addEventListener('click', () => {
            const musicUrl = button.getAttribute('data-music-url');
            const audioElement = document.getElementById('backgroundMusic');
            audioElement.src = musicUrl;
            audioElement.play();
            chooseMusicButton.textContent = 'Choose Music';
            musicMenu.classList.remove('show');
        });
    });

    const feedbackButton = document.createElement('button');
    feedbackButton.textContent = 'Feedback';
    feedbackButton.addEventListener('click', () => {
        showFeedbackModal();
    });
    contentWrapper.appendChild(feedbackButton);

    const settingsButton = document.createElement('button');
    settingsButton.textContent = 'Settings';
    settingsButton.addEventListener('click', () => {
        showSettingsPanel();
    });
    contentWrapper.appendChild(settingsButton);

    async function measurePing(url) {
        try {
            const start = performance.now();
            const response = await fetch(url, { method: 'HEAD' });
            await response;
            const end = performance.now();
            const pingValue = Math.round(end - start) + ' ms';
            updateDisplay(pingValue);
        } catch (error) {
            console.error('Ping Error:', error);
            updateDisplay('Error');
        }
    }

    let lastFrameTime = performance.now();
    let frameCount = 0;

    function measureFPS() {
        const now = performance.now();
        const delta = now - lastFrameTime;
        frameCount++;

        if (delta >= 1000) {
            const fpsValue = Math.round((frameCount * 1000) / delta);
            updateDisplay(null, fpsValue);
            frameCount = 0;
            lastFrameTime = now;
        }

        requestAnimationFrame(measureFPS);
    }

    function updateDisplay(pingValue, fpsValue) {
        if (pingValue !== undefined) {
            ping = pingValue;
        }
        if (fpsValue !== undefined) {
            fps = fpsValue;
        }

        const elapsedTime = formatSessionTime(Date.now() - sessionStartTime);

        const display = document.getElementById('performanceContent');
        display.innerHTML = `
            <div><strong>Ping:</strong> ${ping}</div>
            <div><strong>FPS:</strong> ${fps}</div>
            <div><strong>Session Time:</strong> ${elapsedTime}</div>
        `;
    }

    function formatSessionTime(milliseconds) {
        let seconds = Math.floor(milliseconds / 1000);
        const hours = Math.floor(seconds / 3600);
        seconds %= 3600;
        const minutes = Math.floor(seconds / 60);
        seconds %= 60;
        return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
    }

    const feedbackModal = document.createElement('div');
    feedbackModal.className = 'modal';
    feedbackModal.innerHTML = `
        <h3>Send Feedback</h3>
        <div>
            <label for="feedbackMessage">Your Report:</label>
            <textarea id="feedbackMessage"></textarea>
        </div>
        <button id="sendFeedback" style="margin-top: 10px;">Submit</button>
        <button id="cancelFeedback" style="margin-top: 10px;">Cancel</button>
    `;
    document.body.appendChild(feedbackModal);

    const settingsPanel = document.createElement('div');
    settingsPanel.id = 'settingsPanel';
    settingsPanel.innerHTML = `
        <h3>Settings</h3>
        <div>
            <label for="fontSize">Font Size:</label>
            <input type="number" id="fontSize" value="14" min="10" max="30" />
        </div>
        <div>
            <label for="backgroundColor">Background Color:</label>
            <input type="color" id="backgroundColor" value="#ffffff" />
        </div>
        <div>
            <label for="transparentBackground">Transparent Background:</label>
            <input type="checkbox" id="transparentBackground" />
        </div>
        <button id="applySettings" style="margin-top: 10px;">Apply</button>
        <button id="resetSettings" style="margin-top: 10px;">Reset to Default</button>
        <button id="cancelSettings" style="margin-top: 10px;">Cancel</button>
    `;
    document.body.appendChild(settingsPanel);

    function hideAllPanels() {
        feedbackModal.classList.remove('show');
        settingsPanel.classList.remove('show');
        musicMenu.classList.remove('show');
        document.getElementById('performanceContentWrapper').classList.remove('hidden');
    }

    document.getElementById('sendFeedback').addEventListener('click', () => {
        const feedback = document.getElementById('feedbackMessage').value;
        if (feedback) {
            // Normally, you would send the feedback to your backend here.
            console.log('Feedback:', feedback);
            alert('Feedback sent!');
            feedbackModal.classList.remove('show');
        } else {
            alert('Please enter your feedback.');
        }
    });

    document.getElementById('cancelFeedback').addEventListener('click', () => {
        feedbackModal.classList.remove('show');
    });

    document.getElementById('applySettings').addEventListener('click', () => {
        const fontSize = document.getElementById('fontSize').value + 'px';
        const backgroundColor = document.getElementById('backgroundColor').value;
        const isTransparent = document.getElementById('transparentBackground').checked;

        const performanceMonitor = document.getElementById('performanceMonitor');
        performanceMonitor.style.fontSize = fontSize;
        performanceMonitor.style.backgroundColor = isTransparent ? 'rgba(255, 255, 255, 0)' : backgroundColor;

        // Update text color based on background color
        const textColor = getContrastColor(backgroundColor);
        document.documentElement.style.setProperty('--text-color', textColor);

        alert('Settings applied.');
    });

    document.getElementById('resetSettings').addEventListener('click', () => {
        document.getElementById('fontSize').value = '14';
        document.getElementById('backgroundColor').value = '#ffffff';
        document.getElementById('transparentBackground').checked = false;

        const performanceMonitor = document.getElementById('performanceMonitor');
        performanceMonitor.style.fontSize = '14px';
        performanceMonitor.style.backgroundColor = 'white';

        // Reset text color to default
        document.documentElement.style.setProperty('--text-color', 'black');

        alert('Settings reset to default.');
    });

    document.getElementById('cancelSettings').addEventListener('click', () => {
        settingsPanel.classList.remove('show');
    });

    function showFeedbackModal() {
        hideAllPanels();
        feedbackModal.classList.add('show');
    }

    function showSettingsPanel() {
        hideAllPanels();
        settingsPanel.classList.add('show');
    }

    function showMusicMenu() {
        hideAllPanels();
        musicMenu.classList.add('show');
    }

    function getContrastColor(hex) {
        // Calculate luminance and return black or white based on contrast
        const r = parseInt(hex.substring(1, 3), 16);
        const g = parseInt(hex.substring(3, 5), 16);
        const b = parseInt(hex.substring(5, 7), 16);

        const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
        return luminance > 128 ? 'black' : 'white';
    }

    measureFPS();

    setInterval(() => {
        measurePing('https://www.google.com');
    }, 30000);

})();