DiscordAutoTranslator

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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