DiscordAutoTranslator

Автоматический перевод сообщений в каналах/личных сообщениях на выбранный язык в Discord Web.

// ==UserScript==
// @name         DiscordAutoTranslator
// @namespace    http://tampermonkey.net/
// @version      1.20
// @description  Автоматический перевод сообщений в каналах/личных сообщениях на выбранный язык в 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 = '12px';
    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 = {
        'auto': 'Auto-Detect', 'en': 'English', 'ru': 'Russian',
    };

    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 = '94%';
        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 detectedLang = detectLanguage(text);

        // Если язык текста совпадает с целевым языком, отменяем перевод
        if (detectedLang === targetLang) {
            callback(text); // Возвращаем оригинальный текст без перевода
            return;
        }

        const url = `https://translate.google.com/m?hl=${targetLang}&sl=${detectedLang}&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 detectedLang = detectLanguage(originalText);

        if (/^[\s\W]+$/.test(originalText)) {
            return;
        }

        if (detectedLang === targetLang) {
            return;
        }

        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;
                let lang = sourceLang === 'auto' ? detectLanguage(text) : sourceLang; // Check for auto-detect

                if (lang && isTranslatorActive) {
                    annotateMessage(div, lang);
                }

                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);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址