MjKey / Перевод чата Twitch

// ==UserScript==
// @name        Перевод чата Twitch
// @version     0.2
// @description Перевод сообщений в чате с любого языка на русский!
// @author     MjKey
// @match       *://*.twitch.tv/*
// @icon        https://www.google.com/s2/favicons?sz=64&domain=twitch.tv
// @copyright   2024, MjKey | MjKey.ru
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_registerMenuCommand
// @updateURL   https://openuserjs.org/meta/MjKey/Перевод_чата_Twitch.meta.js
// @downloadURL https://openuserjs.org/install/MjKey/Перевод_чата_Twitch.user.js
// @require     https://openuserjs.org/src/libs/sizzle/GM_config.js
// @license     MIT
// @run-at      document-end
// ==/UserScript==

// ==OpenUserJS==
// @author MjKey
// ==/OpenUserJS==

/* global GM_config */

(function() {
  'use strict';
 console.log("1")
  GM_config.init({
    id: 'ttconf',
    title: GM_info.script.name + ' • Настройки',
    fields: {
      AUTOTRANSLATE: {
        label: 'Кнопка перевода',
        type: 'checkbox',
        default: true,
        title: 'ВКЛ/ВЫКЛ кнопку'
      },
      FROMLANG: {
        label: 'На какой язык переводить (ru,nl,fr и т.д.)',
        type: 'text',
        default: "ru",
        title: 'В РАЗРАБОТКЕ'
      }
    }
  })

  GM_registerMenuCommand('Настройки', () => {
    GM_config.open('ttconf')
  })
  GM_registerMenuCommand('Поддержать автора', () => {
    window.open("https://mjkey.ru/#donate", '_blank');
  })


  function httpPost(theUrl, theData, callback) {
    console.log("HTTPPOST")
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open("POST", theUrl, true); // false for synchronous request
    xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xmlHttp.send(theData);
    xmlHttp.onreadystatechange = function() {
      if (this.readyState != 4) return;
      callback(xmlHttp.responseText);
    }
  }

  function isProbablyEnglish(text) {
    if (!text || text.length === 0 || text.startsWith('!')) return false;
    // Consider using a library for language detection
    return !/[А-я]/.test(text);
  }

  function TranslateTo(text, to, callback) {
    console.log(`Translating text: ${text} (to: ${to})`); // Add logging for translation request
    httpPost('https://translate.googleapis.com/translate_a/single',
      'client=gtx&sl=auto&tl='+to+'&dt=t&q='+encodeURI(text),
      (x) => {
        try {
          var jsos = JSON.parse(x);
          callback({ text: jsos[0][0][0], from: jsos[2] });
          console.log(`Translation successful: ${jsos[0][0][0]} (from: ${jsos[2]})`); // Add logging for successful translation
        } catch (error) {
          console.error('Error translating text:', error); // Add logging for translation error
          callback({ text: text, from: 'auto' });
        }
      },
      true);
  }

  var ChatLanguage = "en";
  const chatObserver = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      if (mutation.type == 'childList') {
        if (mutation.target.matches('.seventv-chat-list') || mutation.target.matches('div.chat-scrollable-area__message-container')) {
          mutation.addedNodes.forEach(n => {
                var textns = n.querySelectorAll('.text-fragment')
                var textn = textns[0];
                var textns7 = n.querySelectorAll('.text-token')
                var textn7 = textns7[0];
            setTimeout(() => {
              if (textn) {
                var text = '';
                textns.forEach(element => { text += element.innerText; });
                if (isProbablyEnglish(text.trim()) && text.length > 1 && !text.startsWith('!') && GM_config.get("AUTOTRANSLATE")) {
                  setTimeout(() => {
                    var xnewSpan = document.createElement('span');
                    xnewSpan.classList.add("chat-line__message--deleted");
                    xnewSpan.classList.add("twitchtoolbox-translate-button");
                    xnewSpan.style.cursor = 'pointer';
                    xnewSpan.innerHTML = '<div class="tw-inline tw-relative tw-tooltip-wrapper" style="float:right;"><div class="tw-align-center tw-inline-block">&#xA0;⌈🌐⌋</div></div>';
                    xnewSpan.title = text;
                    xnewSpan.onclick = function() {
                      var translatedText = '';
                      var isChanged = false;
                      textns.forEach(element => {
                        element.alt = element.innerText;
                        TranslateTo(element.innerText, GM_config.get("FROMLANG"), (x) => {
                          translatedText += element.innerText = x.text || element.innerText;
                        });
                      });
                      if (isChanged) {
                        // Handle successful translation (e.g., update title, show feedback)
                        xnewSpan.title = translatedText;
                        console.log(`Message translation successful: ${translatedText}`); // Add logging for successful message translation
                      }
                    }
                    textn.parentNode.prepend(xnewSpan);
                  }, 0);
                }
              }
                if (textn7) {
                    var text7 = '';
                    textns7.forEach(element => { text7 += element.innerText; });
                    if (isProbablyEnglish(text7.trim()) && text7.length > 1 && !text7.startsWith('!') && GM_config.get("AUTOTRANSLATE")) {
                        setTimeout(() => {
                            var xnewSpan = document.createElement('span');
                            xnewSpan.classList.add("chat-line__message--deleted");
                            xnewSpan.classList.add("twitchtoolbox-translate-button");
                            xnewSpan.style.cursor = 'pointer';
                            xnewSpan.innerHTML = '<div class="tw-inline tw-relative tw-tooltip-wrapper" style="float:right;"><div class="tw-align-center tw-inline-block">&#xA0;⌈🌐⌋</div></div>';
                            xnewSpan.title = text7;
                            xnewSpan.onclick = function() {
                                var translatedText7 = '';
                                var isChanged = false;
                                textns7.forEach(element => {
                                    element.alt = element.innerText;
                                    TranslateTo(element.innerText, GM_config.get("FROMLANG"), (x) => {
                                        translatedText7 += element.innerText = x.text || element.innerText;
                                    });
                                });
                                if (isChanged) {
                                    // Handle successful translation (e.g., update title, show feedback)
                                    xnewSpan.title = translatedText7;
                                    console.log(`Message translation successful: ${translatedText7}`); // Add logging for successful message translation
                                }
                            }
                            textn7.parentNode.prepend(xnewSpan);
                        }, 0);
                    }
                }
            });
          });
        }
      }
    });
  });

  chatObserver.observe(document.documentElement, {
    attributes: true,
    childList: true,
    subtree: true
  })

  // Thanks ScriptedEngineer (aka. Siptrixed) <3
})();