Automatically translates messages in channels/private messages into the selected language in Discord Web.
当前为 
// ==UserScript==
// @name         DiscordAutoTranslator
// @namespace    http://tampermonkey.net/
// @version      1.17
// @description  Automatically translates messages in channels/private messages into the selected language in Discord Web.
// @match        *://discord.com/*
// @author       Timka251 & eretly
// @grant        GM_xmlhttpRequest
// @icon         https://i.pinimg.com/236x/68/95/31/689531dc04ba222ab7af0fa34dc63644.jpg
// @run-at       document-end
// @license      BSD-3-Clause
// ==/UserScript==
/*
 * Copyright 2024 eretly
 * Licensed under the BSD 3-Clause License.
 */
(function () {
    'use strict';
    const languageSelector = document.createElement('div');
    languageSelector.style.position = 'fixed';
    languageSelector.style.top = '15px';
    languageSelector.style.right = '15px';
    languageSelector.style.backgroundColor = '#2f3136';
    languageSelector.style.padding = '16px';
    languageSelector.style.zIndex = '9999';
    languageSelector.style.border = '1px solid #4f545c';
    languageSelector.style.borderRadius = '8px';
    languageSelector.style.width = '250px';
    languageSelector.style.display = 'none';
    const toggleButton = document.createElement('button');
    toggleButton.style.width = '100%';
    toggleButton.style.marginTop = '10px';
    toggleButton.style.padding = '8px 12px';
    toggleButton.style.backgroundColor = '#7289da';
    toggleButton.style.color = 'white';
    toggleButton.style.border = 'none';
    toggleButton.style.borderRadius = '4px';
    toggleButton.style.cursor = 'pointer';
    toggleButton.textContent = 'Enable Translator';
    const languages = {
        'af': 'Afrikaans', 'sq': 'Albanian', 'am': 'Amharic', 'ar': 'Arabic', 'hy': 'Armenian',
        'as': 'Assamese', 'ay': 'Aymara', 'az': 'Azerbaijani', 'bm': 'Bambara', 'eu': 'Basque',
        'be': 'Belarusian', 'bn': 'Bengali', 'bho': 'Bhojpuri', 'bs': 'Bosnian', 'bg': 'Bulgarian',
        'ca': 'Catalan', 'ceb': 'Cebuano', 'zh-CN': 'Chinese (Simplified)', 'zh-TW': 'Chinese (Traditional)',
        'co': 'Corsican', 'hr': 'Croatian', 'cs': 'Czech', 'da': 'Danish', 'dv': 'Dhivehi',
        'doi': 'Dogri', 'nl': 'Dutch', 'en': 'English', 'eo': 'Esperanto', 'et': 'Estonian',
        'ee': 'Ewe', 'fil': 'Filipino (Tagalog)', 'fi': 'Finnish', 'fr': 'French', 'fy': 'Frisian',
        'gl': 'Galician', 'ka': 'Georgian', 'de': 'German', 'el': 'Greek', 'gn': 'Guarani',
        'gu': 'Gujarati', 'ht': 'Haitian Creole', 'ha': 'Hausa', 'haw': 'Hawaiian', 'he': 'Hebrew',
        'hi': 'Hindi', 'hmn': 'Hmong', 'hu': 'Hungarian', 'is': 'Icelandic', 'ig': 'Igbo',
        'ilo': 'Ilocano', 'id': 'Indonesian', 'ga': 'Irish', 'it': 'Italian', 'ja': 'Japanese',
        'jv': 'Javanese', 'kn': 'Kannada', 'kk': 'Kazakh', 'km': 'Khmer', 'rw': 'Kinyarwanda',
        'gom': 'Konkani', 'ko': 'Korean', 'kri': 'Krio', 'ku': 'Kurdish', 'ckb': 'Kurdish (Sorani)',
        'ky': 'Kyrgyz', 'lo': 'Lao', 'la': 'Latin', 'lv': 'Latvian', 'ln': 'Lingala',
        'lt': 'Lithuanian', 'lg': 'Luganda', 'lb': 'Luxembourgish', 'mk': 'Macedonian', 'mai': 'Maithili',
        'mg': 'Malagasy', 'ms': 'Malay', 'ml': 'Malayalam', 'mt': 'Maltese', 'mi': 'Maori',
        'mr': 'Marathi', 'mni-Mtei': 'Meiteilon (Manipuri)', 'lus': 'Mizo', 'mn': 'Mongolian',
        'my': 'Myanmar (Burmese)', 'ne': 'Nepali', 'no': 'Norwegian', 'ny': 'Nyanja (Chichewa)',
        'or': 'Odia (Oriya)', 'om': 'Oromo', 'ps': 'Pashto', 'fa': 'Persian', 'pl': 'Polish',
        'pt': 'Portuguese (Portugal, Brazil)', 'pa': 'Punjabi', 'qu': 'Quechua', 'ro': 'Romanian',
        'ru': 'Russian', 'sm': 'Samoan', 'sa': 'Sanskrit', 'gd': 'Scots Gaelic', 'nso': 'Sepedi',
        'sr': 'Serbian', 'st': 'Sesotho', 'sn': 'Shona', 'sd': 'Sindhi', 'si': 'Sinhala (Sinhalese)',
        'sk': 'Slovak', 'sl': 'Slovenian', 'so': 'Somali', 'es': 'Spanish', 'su': 'Sundanese',
        'sw': 'Swahili', 'sv': 'Swedish', 'tl': 'Tagalog (Filipino)', 'tg': 'Tajik', 'ta': 'Tamil',
        'tt': 'Tatar', 'te': 'Telugu', 'th': 'Thai', 'ti': 'Tigrinya', 'ts': 'Tsonga',
        'tr': 'Turkish', 'tk': 'Turkmen', 'ak': 'Twi (Akan)', 'uk': 'Ukrainian', 'ur': 'Urdu',
        'ug': 'Uyghur', 'uz': 'Uzbek', 'vi': 'Vietnamese', 'cy': 'Welsh', 'xh': 'Xhosa',
        'yi': 'Yiddish', 'yo': 'Yoruba', 'zu': 'Zulu'
    };
    function createCustomSelect(defaultText, label) {
        const customSelect = document.createElement('div');
        customSelect.className = 'custom-select';
        customSelect.style.marginBottom = '10px';
        const selectButton = document.createElement('button');
        selectButton.className = 'select-button';
        selectButton.style.width = '100%';
        selectButton.style.padding = '8px 12px';
        selectButton.style.color = 'white';
        selectButton.style.backgroundColor = '#2f3136';
        selectButton.style.border = '1px solid #4f545c';
        selectButton.style.borderRadius = '4px';
        selectButton.style.cursor = 'pointer';
        selectButton.style.textAlign = 'left';
        selectButton.textContent = defaultText;
        const chevronDown = document.createElement('span');
        chevronDown.textContent = '▼';
        chevronDown.style.float = 'right';
        selectButton.appendChild(chevronDown);
        const selectOptions = document.createElement('div');
        selectOptions.className = 'select-options';
        selectOptions.style.display = 'none';
        selectOptions.style.position = 'absolute';
        selectOptions.style.backgroundColor = '#2f3136';
        selectOptions.style.border = '1px solid #4f545c';
        selectOptions.style.borderRadius = '4px';
        selectOptions.style.maxHeight = '200px';
        selectOptions.style.overflowY = 'auto';
        selectOptions.style.width = '100%';
        selectOptions.style.zIndex = '1000';
        Object.entries(languages).forEach(([code, name]) => {
            const option = document.createElement('div');
            option.className = 'select-option';
            option.textContent = name;
            option.dataset.code = code;
            option.style.padding = '8px 12px';
            option.style.cursor = 'pointer';
            option.style.color = 'white';
            option.addEventListener('mouseover', () => {
                option.style.backgroundColor = '#7289da';
            });
            option.addEventListener('mouseout', () => {
                option.style.backgroundColor = '';
            });
            option.addEventListener('click', () => {
                selectButton.textContent = name;
                selectButton.dataset.code = code;
                selectButton.appendChild(chevronDown);
                selectOptions.style.display = 'none';
                customSelect.dispatchEvent(new Event('change'));
            });
            selectOptions.appendChild(option);
        });
        const labelElement = document.createElement('div');
        labelElement.textContent = label;
        labelElement.style.marginTop = '4px';
        labelElement.style.fontSize = '12px';
        labelElement.style.color = '#b9bbbe';
        selectButton.addEventListener('click', () => {
            selectOptions.style.display = selectOptions.style.display === 'none' ? 'block' : 'none';
        });
        document.addEventListener('click', (event) => {
            if (!customSelect.contains(event.target)) {
                selectOptions.style.display = 'none';
            }
        });
        customSelect.appendChild(selectButton);
        customSelect.appendChild(selectOptions);
        customSelect.appendChild(labelElement);
        return customSelect;
    }
    const sourceSelect = createCustomSelect('English', 'Source Language');
    const targetSelect = createCustomSelect('Russian', 'Target Language');
    languageSelector.appendChild(sourceSelect);
    languageSelector.appendChild(targetSelect);
    languageSelector.appendChild(toggleButton);
    document.body.appendChild(languageSelector);
    const savedSourceLang = localStorage.getItem('sourceLang') || 'en';
    const savedTargetLang = localStorage.getItem('targetLang') || 'ru';
    let isTranslatorActive = localStorage.getItem('isTranslatorActive') === 'true';
    sourceSelect.querySelector('.select-button').textContent = languages[savedSourceLang];
    sourceSelect.querySelector('.select-button').dataset.code = savedSourceLang;
    targetSelect.querySelector('.select-button').textContent = languages[savedTargetLang];
    targetSelect.querySelector('.select-button').dataset.code = savedTargetLang;
    let sourceLang = savedSourceLang;
    let targetLang = savedTargetLang;
    let activeRequests = [];
    if (isTranslatorActive) {
        toggleButton.textContent = 'Disable Translator';
        translateAllMessages();
    }
    function updateLanguages() {
        const sourceButton = sourceSelect.querySelector('.select-button');
        const targetButton = targetSelect.querySelector('.select-button');
        sourceLang = sourceButton.dataset.code;
        targetLang = targetButton.dataset.code;
        localStorage.setItem('sourceLang', sourceLang);
        localStorage.setItem('targetLang', targetLang);
        if (isTranslatorActive) {
            translateAllMessages();
        }
    }
    sourceSelect.addEventListener('change', updateLanguages);
    targetSelect.addEventListener('change', updateLanguages);
    function updateTranslatorState() {
        isTranslatorActive = !isTranslatorActive;
        localStorage.setItem('isTranslatorActive', isTranslatorActive);
        toggleButton.textContent = isTranslatorActive ? 'Disable Translator' : 'Enable Translator';
        if (isTranslatorActive) {
            translateAllMessages();
        } else {
            resetTranslations();
            cancelActiveRequests();
        }
    }
    toggleButton.addEventListener('click', updateTranslatorState);
    function translateText(text, callback) {
        const url = `https://translate.google.com/m?hl=${targetLang}&sl=${sourceLang}&tl=${targetLang}&ie=UTF-8&prev=_m&q=${encodeURIComponent(text)}`;
        const request = GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function (response) {
                if (response.status === 200) {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, "text/html");
                    const translatedTextElement = doc.querySelector('.result-container');
                    if (translatedTextElement) {
                        callback(translatedTextElement.textContent.trim());
                    } else {
                        console.error("Translation failed");
                    }
                } else {
                    console.error("Error when receiving transfer, status: " + response.status);
                }
                activeRequests = activeRequests.filter(req => req !== request);
            },
            onerror: function () {
                console.error("Network error during transfer request");
                activeRequests = activeRequests.filter(req => req !== request);
            }
        });
        activeRequests.push(request);
    }
    function cancelActiveRequests() {
        activeRequests.forEach(request => {
            if (request && request.abort) {
                request.abort();
            }
        });
        activeRequests = [];
    }
    function annotateMessage(div) {
        const originalText = div.textContent.trim();
        const container = document.createElement('div');
        container.style.position = 'relative';
        const translatedDiv = document.createElement('div');
        translatedDiv.classList.add('translated-message');
        translatedDiv.style.color = 'rgb(135, 155, 164)';
        translatedDiv.style.marginTop = '0px';
        translatedDiv.style.paddingLeft = '0px';
        translateText(originalText, function (translatedText) {
            translatedDiv.textContent = translatedText;
            container.appendChild(translatedDiv);
            div.parentNode.insertBefore(container, div.nextSibling);
        });
    }
    function checkNewDiv() {
        const divs = document.querySelectorAll('div[id^="message-content-"]');
        divs.forEach(div => {
            if (!div.dataset.processed) {
                const text = div.textContent;
                const lang = detectLanguage(text);
                if (lang === sourceLang && isTranslatorActive) {
                    annotateMessage(div);
                }
                div.dataset.processed = 'true';
            }
        });
    }
    function detectLanguage(text) {
        return text.match(/[a-zA-Z]/) ? 'en' : 'ru';
    }
    function resetTranslations() {
        const translatedMessages = document.querySelectorAll('.translated-message');
        translatedMessages.forEach(msg => msg.remove());
    }
    function translateAllMessages() {
        resetTranslations();
        const divs = document.querySelectorAll('div[id^="message-content-"]');
        divs.forEach(div => {
            const text = div.textContent;
            const lang = detectLanguage(text);
            if (lang === sourceLang && isTranslatorActive) {
                annotateMessage(div);
            }
        });
    }
    document.addEventListener('keydown', (event) => {
        if (event.altKey && (event.key === 't' || event.key === 'е')) { // Alt + T or Alt + Е
            event.preventDefault();
            languageSelector.style.display = languageSelector.style.display === 'none' ? 'block' : 'none';
        }
    });
    setInterval(checkNewDiv, 1000);
})();