Кнопка "Перевести" исчезает после одного использования в скрипте для YouTube Studio

Я создаю скрипт для YouTube Studio, который добавляет кнопку "Перевести", переводящую название и описание видео с помощью Google Translate. После первого использования кнопка исчезает, и ее нужно обновить страницу, чтобы она снова появилась.

Желаемое поведение:

Кнопка "Перевести" должна оставаться на странице и быть доступной для повторного использования без необходимости перезагрузки страницы.

Проблема:

Кнопка исчезает после первого использования, а также не появляется снова при возвращении на страницу или после каких-либо других действий.

Шаги для воспроизведения:

  1. Откройте страницу с YouTube Studio (например, страницу перевода видео).
  2. Скрипт добавляет кнопку "Перевести".
  3. После нажатия кнопка исчезает и больше не появляется.

Мой код:

!(function () {
  "use strict"
  const e = {
    абхазский: "ab",
    азербайджанский: "az",
    аймара: "ay",
    акан: "ak",
    аккадский: "akk",
    албанский: "sq",
    амхарский: "am",
    английский: "en",
    арабский: "ar",
    арамейский: "arc",
    армянский: "hy",
    ассамский: "as",
    афарский: "aa",
    африкаанс: "af",
    бамбара: "bm",
    баскский: "eu",
    башкирский: "ba",
    белорусский: "be",
    бенгальский: "bn",
    бирманский: "my",
    бислама: "bi",
    бодо: "brx",
    болгарский: "bg",
    боснийский: "bs",
    бретонский: "br",
    бходжпури: "bho",
    валлийский: "cy",
    венгерский: "hu",
    венда: "ve",
    воламо: "wal",
    волапюк: "vo",
    волоф: "wo",
    вьетнамский: "vi",
    гавайский: "haw",
    гаитянский: "ht",
    галисийский: "gl",
    ганда: "lg",
    гренландский: "kl",
    греческий: "el",
    грузинский: "ka",
    гуарани: "gn",
    гуджарати: "gu",
    гусии: "guz",
    гэльский: "gd",
    датский: "da",
    "дзонг-кэ": "dz",
    догри: "doi",
    западнофризский: "fy",
    зулу: "zu",
    иврит: "he",
    игбо: "ig",
    идиш: "yi",
    индонезийский: "id",
    интерлингва: "ia",
    интерлингве: "ie",
    инуктитут: "iu",
    инупиак: "ik",
    ирландский: "ga",
    исландский: "is",
    испанский: "es",
    итальянский: "it",
    йоруба: "yo",
    казахский: "kk",
    календжин: "kln",
    камба: "kam",
    каннада: "kn",
    кантонский: "yue",
    каталанский: "ca",
    кашмири: "ks",
    кечуа: "qu",
    кикуйю: "ki",
    киньяруанда: "rw",
    киргизский: "ky",
    китайский: "zh",
    клингонский: "tlh",
    конкани: "kok",
    коптский: "cop",
    корейский: "ko",
    корсиканский: "co",
    коса: "xh",
    кри: "cr",
    курдский: "ku",
    кхмерский: "km",
    ладино: "lad",
    лаосский: "lo",
    латинский: "la",
    латышский: "lv",
    лингала: "ln",
    литовский: "lt",
    "луба-катанга": "lu",
    луо: "luo",
    лухья: "luy",
    люксембургский: "lb",
    майтхили: "mai",
    македонский: "mk",
    малагасийский: "mg",
    малайский: "ms",
    малаялам: "ml",
    мальтийский: "mt",
    манипурский: "mni",
    маори: "mi",
    маратхи: "mr",
    масаи: "mas",
    меру: "mer",
    мизо: "lus",
    миньнань: "nan",
    михе: "mxp",
    монгольский: "mn",
    навахо: "nv",
    науру: "na",
    немецкий: "de",
    непальский: "ne",
    "нигерийско-креольский": "pcm",
    нидерландский: "nl",
    норвежский: "no",
    окситанский: "oc",
    ория: "or",
    оромо: "om",
    панджаби: "pa",
    папьяменто: "pap",
    персидский: "fa",
    польский: "pl",
    португальский: "pt",
    пушту: "ps",
    романшский: "rm",
    румынский: "ro",
    рунди: "rn",
    русский: "ru",
    самоанский: "sm",
    санго: "sg",
    санскрит: "sa",
    сантали: "sat",
    сардинский: "sc",
    свази: "ss",
    "северный ндебеле": "nd",
    "северный сото": "nso",
    сербский: "sr",
    сингальский: "si",
    синдхи: "sd",
    сицилийский: "scn",
    словацкий: "sk",
    словенский: "sl",
    сомали: "so",
    суахили: "sw",
    сунданский: "su",
    тагалог: "tl",
    таджикский: "tg",
    тайский: "th",
    тамильский: "ta",
    татарский: "tt",
    тви: "tw",
    телугу: "te",
    тибетский: "bo",
    тигринья: "ti",
    "ток-писин": "tpi",
    токипона: "tok",
    тонганский: "to",
    тсвана: "tn",
    тсонга: "ts",
    турецкий: "tr",
    туркменский: "tk",
    узбекский: "uz",
    уйгурский: "ug",
    украинский: "uk",
    урду: "ur",
    фарерский: "fo",
    фиджи: "fj",
    филиппинский: "fil",
    финский: "fi",
    французский: "fr",
    фулах: "ff",
    хакка: "hak",
    харианви: "bgc",
    хауса: "ha",
    хинди: "hi",
    хиримоту: "ho",
    хорватский: "hr",
    чероки: "chr",
    чешский: "cs",
    чоктавский: "cho",
    шведский: "sv",
    шердукпен: "sdp",
    шона: "sn",
    эве: "ee",
    эсперанто: "eo",
    эстонский: "et",
    "южный ндебеле": "nr",
    "южный сото": "st",
    яванский: "jv",
    японский: "ja",
    ase: "ase",
    vro: "vro",
  }
  function c(t) {
    return e[t.toLowerCase()] || null
  }
  function u(t, e, o, n) {
    console.log(`Запрос на перевод с языка: ${e} на язык: ${o}`)
    t = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${e}&tl=${o}&dt=t&q=${encodeURIComponent(
      t
    )}`
    fetch(t)
      .then((t) => t.json())
      .then((t) => {
        var e
        t && t[0] && 0 < t[0].length
          ? ((e = t[0].map((t) => t[0]).join("")),
            console.log(`Переведенный текст: ${e}`),
            n(e))
          : (console.error(
              "Ошибка: перевод не был выполнен должным образом.",
              t
            ),
            alert(
              "Не удалось перевести текст. Проверьте исходный и целевой язык."
            ))
      })
      .catch((t) => {
        console.error("Ошибка при переводе:", t),
          alert("Ошибка при переводе. Попробуйте снова.")
      })
  }
  function s() {
    console.log("Кнопка 'Перевести' нажата!")
    const t = document.querySelector('textarea[aria-label="Название"]'),
      e = document.querySelector('textarea[aria-label="Описание"]'),
      o = document.querySelector('textarea[aria-label="Название*"]'),
      n = document.querySelectorAll('textarea[aria-label="Описание"]')[1]
    if (t && e && o && n) {
      var a = t.value.trim()
      const r = e.value.trim()
      if (a && r) {
        var l = (function () {
          const t = document.querySelector(
              ".metadata-editor-original .language-header"
            ),
            e = document.querySelector(
              ".metadata-editor-translated .language-header"
            )
          if (!t || !e)
            return (
              alert(
                "Не удалось найти исходный или целевой язык! Перевод невозможен."
              ),
              null
            )
          var o = t.textContent.trim().toLowerCase(),
            n = e.textContent.trim().toLowerCase(),
            a = c(o),
            l = c(n)
          return a && l
            ? (console.log("Исходный язык:", o, "Код:", a),
              console.log("Целевой язык:", n, "Код:", l),
              { originalLangCode: a, targetLangCode: l })
            : (alert(
                "Не удалось определить коды исходного или целевого языка! Перевод невозможен."
              ),
              null)
        })()
        if (l) {
          const { originalLangCode: s, targetLangCode: i } = l
          console.log("Исходный язык (код):", s, "Целевой язык (код):", i),
            u(a, s, i, (t) => {
              console.log(`Вставка переведённого названия: ${t}`),
                (o.value = t),
                o.dispatchEvent(new Event("input", { bubbles: !0 })),
                console.log("Название переведено:", t),
                u(r, s, i, (t) => {
                  console.log(`Вставка переведённого описания: ${t}`),
                    (n.value = t),
                    n.dispatchEvent(new Event("input", { bubbles: !0 })),
                    console.log("Описание переведено:", t),
                    (function () {
                      const e = new Event("input", { bubbles: !0 })
                      document
                        .querySelectorAll(
                          'textarea[aria-label="Название"], textarea[aria-label="Описание"]'
                        )
                        .forEach((t) => {
                          t.dispatchEvent(e)
                        }),
                        console.log("Состояние страницы обновлено.")
                    })(),
                    setTimeout(() => {
                      !(function () {
                        const t = document.querySelector(
                          "ytcp-button#publish-button"
                        )
                        if (t) {
                          const e = t.querySelector(
                            "button.ytcp-button-shape-impl"
                          )
                          e &&
                            (e.classList.remove(
                              "ytcp-button-shape-impl--disabled"
                            ),
                            e.removeAttribute("disabled"),
                            t.setAttribute("aria-disabled", "false"),
                            console.log('Кнопка "Опубликовать" активирована.'))
                        }
                      })()
                    }, 2e3)
                })
            })
        }
      } else alert("Пожалуйста, введите текст для перевода.")
    } else alert("Не удалось найти блоки для перевода.")
  }
  window.addEventListener("load", function () {
    const o = new MutationObserver((t) => {
      for (var e of t)
        if (0 < e.addedNodes.length && document.querySelector("#footer-row")) {
          !(function () {
            const t = document.querySelector("#footer-row")
            if (t) {
              const e = document.querySelector("#translate-button")
              e && e.remove()
              const o = document.createElement("div")
              ;(o.id = "translate-button"),
                o.setAttribute("label", "Перевести"),
                o.setAttribute("type", "filled-primary"),
                o.classList.add("translate-button-id"),
                o.setAttribute("role", "button")
              const n = document.createElement("span")
              n.classList.add("translate-button-style")
              const a = document.createElement("button")
              a.classList.add("translate-button-style-button"),
                a.setAttribute("aria-label", "Перевести")
              const l = document.createElement("div")
              l.classList.add("translate-button-class"),
                (l.textContent = "Перевести"),
                a.appendChild(l),
                n.appendChild(a),
                o.appendChild(n),
                t.insertAdjacentElement("beforeend", o)
              const r = document.createElement("style")
              ;(r.textContent = `
        .translate-button-class {
            text-overflow: ellipsis;
            overflow: hidden;
        }

        button.translate-button-style-button {
            padding: 0 16px;
            height: 36px;
            font-size: 14px;
            line-height: 36px;
            border-radius: 18px;
            position: relative;
            margin: 0;
            white-space: nowrap;
            min-width: 0;
            text-transform: none;
            font-family: "Roboto", "Arial", sans-serif;
            font-weight: 500;
            cursor: pointer;
            outline-width: 0;
            box-sizing: border-box;
            background: #373b37;
            color: white;
            flex: 1;
            flex-basis: 0.000000001px;
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
        }

        #translate-button.translate-button-id {
            float: right;
            margin: 1px 8px 1px 1px;
            max-height: max-content;
        }
        `),
                document.head.appendChild(r),
                o.addEventListener("click", s),
                console.log('Кнопка "Перевести" добавлена.')
            } else console.log('Кнопки "Опубликовать" и "Отмена" не найдены.')
          })(),
            o.disconnect()
          break
        }
    })
    o.observe(document.body, { childList: !0, subtree: !0 })
  })
})()


Что я пробовал:

  1. Я пробовал использовать MutationObserver для добавления кнопки при изменении DOM, но кнопка все равно исчезает после одного использования.
  2. Пробовал использовать setInterval, но это привело к перегрузке страницы и замедлению работы.

Вопрос:

Как я могу исправить проблему с исчезновением кнопки после первого использования и сделать её доступной для повторного использования без перезагрузки страницы?

Фото С Кнопкой Перевести введите сюда описание изображения

Фото без Кнопки Перевести введите сюда описание изображения


Ответы (1 шт):

Автор решения: Samir Mamedov

Задал вопрос о помощи в исправлении этого скрипта. Прошло несколько дней, но никто не ответил или не заметил!

Однако, самостоятельно разобрался и с помощью ChatGPT4 решил проблему! Теперь этот скрипт работает на 100%!

Любой желающий, у кого есть YouTube канал, может использовать этот скрипт. Для этого достаточно установить его через ViolentMonkey в своём браузере. Этот скрипт переводит название и описание видео с исходного языка на целевой, используя бесплатный Google Переводчик.

Важно помнить, что этот способ бесплатен, но у Google Переводчика есть ограничения на количество запросов. Если вы превысите лимит, вашу IP-адрес могут временно заблокировать!

Чтобы избежать блокировки, можно зарегистрироваться в Google API и получить свой API-ключ. Затем скрипт можно будет настроить для отправки запросов через API-ключ.

Вот полный код!

// ==UserScript==
// @name         100% YouTube Translate Button
// @namespace    Violentmonkey Scripts
// @version      10.0
// @description  Automatically translates metadata in YouTube Studio
// @author       Samir Mammadov
// @match        https://studio.youtube.com/*
// @grant        none
// ==/UserScript==

!(function () {
  "use strict";

  // Перехват Fetch для подавления ошибок в консоли
  const originalFetch = window.fetch;
  window.fetch = function (...args) {
    return originalFetch.apply(this, args).catch((err) => {
      // Можно обработать ошибки, если нужно
    });
  };

  // Отключаем глобальные логи (если требуется)
  console.log = function () {}; // Полностью отключаем console.log (НЕ рекомендуется в других сценариях)

  // Маппинг языков и их кодов для перевода
  const e = {
    абхазский: "ab",
    азербайджанский: "az",
    албанский: "sq",
    английский: "en",
    арабский: "ar",
    белорусский: "be",
    бенгальский: "bn",
    болгарский: "bg",
    боснийский: "bs",
    венгерский: "hu",
    вьетнамский: "vi",
    греческий: "el",
    грузинский: "ka",
    датский: "da",
    иврит: "he",
    индонезийский: "id",
    ирландский: "ga",
    исландский: "is",
    испанский: "es",
    итальянский: "it",
    казахский: "kk",
    киргизский: "ky",
    китайский: "zh",
    корейский: "ko",
    курдский: "ku",
    лаосский: "lo",
    латинский: "la",
    латышский: "lv",
    литовский: "lt",
    люксембургский: "lb",
    македонский: "mk",
    малагасийский: "mg",
    малайский: "ms",
    малаялам: "ml",
    мальтийский: "mt",
    монгольский: "mn",
    немецкий: "de",
    непальский: "ne",
    нидерландский: "nl",
    норвежский: "no",
    панджаби: "pa",
    персидский: "fa",
    польский: "pl",
    португальский: "pt",
    румынский: "ro",
    русский: "ru",
    сербский: "sr",
    словацкий: "sk",
    словенский: "sl",
    сомали: "so",
    таджикский: "tg",
    тайский: "th",
    тамильский: "ta",
    татарский: "tt",
    турецкий: "tr",
    туркменский: "tk",
    узбекский: "uz",
    уйгурский: "ug",
    украинский: "uk",
    урду: "ur",
    филиппинский: "fil",
    финский: "fi",
    французский: "fr",
    хинди: "hi",
    хорватский: "hr",
    чешский: "cs",
    шведский: "sv",
    эстонский: "et",
    японский: "ja",
  };

  // Функция для получения кода языка на основе его названия
  function c(t) {
    return e[t.toLowerCase()] || null;
  }

  // Функция для отправки запроса на перевод текста
  function u(t, e, o, n) {
    t = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${e}&tl=${o}&dt=t&q=${encodeURIComponent(
      t
    )}`;
    fetch(t)
      .then((t) => t.json())
      .then((t) => {
        var e;
        t && t[0] && 0 < t[0].length
          ? ((e = t[0].map((t) => t[0]).join("")), n(e))
          : alert("Не удалось перевести текст. Проверьте исходный и целевой язык.");
      })
      .catch(() => {
        alert("Ошибка при переводе. Попробуйте снова.");
      });
  }

  // Основная функция обработки клика на кнопку "Перевести"
  function s(event) {
    const modal = event.target.closest('tp-yt-paper-dialog'); // Определяем текущее окно

    // Получаем нужные текстовые поля из окна
    const t = modal.querySelector('textarea[aria-label="Название"]'),
      e = modal.querySelector('textarea[aria-label="Описание"]'),
      o = modal.querySelector('textarea[aria-label="Название*"]'),
      n = modal.querySelectorAll('textarea[aria-label="Описание"]')[1];

    if (t && e && o && n) {
      var a = t.value.trim();
      const r = e.value.trim();
      if (a && r) {
        // Определяем исходный и целевой язык
        var l = (function () {
          const t = modal.querySelector(".metadata-editor-original .language-header"),
            e = modal.querySelector(".metadata-editor-translated .language-header");
          if (!t || !e)
            return (
              alert("Не удалось найти исходный или целевой язык! Перевод невозможен."),
              null
            );
          var o = t.textContent.trim().toLowerCase(),
            n = e.textContent.trim().toLowerCase(),
            a = c(o),
            l = c(n);
          return a && l
            ? { originalLangCode: a, targetLangCode: l }
            : (alert("Не удалось определить коды исходного или целевого языка! Перевод невозможен."),
              null);
        })();

        if (l) {
          const { originalLangCode: s, targetLangCode: i } = l;

          // Выполняем перевод для названия и описания
          u(a, s, i, (t) => {
            o.value = t;
            o.dispatchEvent(new Event("input", { bubbles: !0 }));
            u(r, s, i, (t) => {
              n.value = t;
              n.dispatchEvent(new Event("input", { bubbles: !0 }));

              // Обновляем состояние страницы после перевода
              (function () {
                const e = new Event("input", { bubbles: !0 });
                modal
                  .querySelectorAll('textarea[aria-label="Название"], textarea[aria-label="Описание"]')
                  .forEach((t) => {
                    t.dispatchEvent(e);
                  });
              })();

              // Активируем кнопку "Опубликовать"
              setTimeout(() => {
                !(function () {
                  const t = modal.querySelector("ytcp-button#publish-button");
                  if (t) {
                    const e = t.querySelector("button.ytcp-button-shape-impl");
                    e &&
                      (e.classList.remove("ytcp-button-shape-impl--disabled"),
                      e.removeAttribute("disabled"),
                      t.setAttribute("aria-disabled", "false"));
                  }
                })();
              }, 2e3);
            });
          });
        }
      } else alert("Пожалуйста, введите текст для перевода.");
    } else alert("Не удалось найти блоки для перевода.");
  }

  // Функция для добавления кнопки перевода в модальное окно
  function addTranslateButton(modal) {
    const footerRow = modal.querySelector('#footer-row');
    if (!footerRow || modal.querySelector('#translate-button')) {
      return;
    }

    // Создаем кнопку перевода и добавляем её в модальное окно
    const translateButtonDiv = document.createElement('div');
    translateButtonDiv.id = 'translate-button';
    translateButtonDiv.setAttribute('label', 'Перевести');
    translateButtonDiv.classList.add('translate-button-id');
    translateButtonDiv.setAttribute('role', 'button');

    const span = document.createElement('span');
    span.classList.add('translate-button-style');

    const button = document.createElement('button');
    button.classList.add('translate-button-style-button');
    button.setAttribute('aria-label', 'Перевести');

    const div = document.createElement('div');
    div.classList.add('translate-button-class');
    div.textContent = 'Перевести';

    button.appendChild(div);
    span.appendChild(button);
    translateButtonDiv.appendChild(span);
    footerRow.insertAdjacentElement('beforeend', translateButtonDiv);

    // Добавляем обработчик события клика на кнопку
    button.addEventListener('click', s);
  }

  // Функция для отслеживания появления новых модальных окон и добавления кнопки перевода
  function observeModals() {
    const modalContainers = document.querySelectorAll('tp-yt-paper-dialog');
    modalContainers.forEach((modal) => {
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.target.style.display !== 'none') {
            addTranslateButton(mutation.target); // Добавляем кнопку, когда модальное окно становится видимым
          }
        });
      });
      observer.observe(modal, { attributes: true, attributeFilter: ['style'] });
    });
  }

  // Наблюдатель за изменениями в DOM для поиска новых модальных окон
  const globalObserver = new MutationObserver(() => {
    observeModals();
  });

  globalObserver.observe(document.body, { childList: true, subtree: true });

  // Запускаем наблюдение за модальными окнами при загрузке страницы
  window.addEventListener('load', () => {
    observeModals();
  });

  // Первоначальная настройка кнопки перевода на основной странице
  window.addEventListener("load", function () {
    const o = new MutationObserver((t) => {
      for (var e of t)
        if (0 < e.addedNodes.length && document.querySelector("#footer-row")) {
          !(function () {
            const t = document.querySelector("#footer-row");
            if (t) {
              const e = document.querySelector("#translate-button");
              e && e.remove();
              const o = document.createElement("div");
              (o.id = "translate-button"),
                o.setAttribute("label", "Перевести"),
                o.setAttribute("type", "filled-primary"),
                o.classList.add("translate-button-id"),
                o.setAttribute("role", "button");
              const n = document.createElement("span");
              n.classList.add("translate-button-style");
              const a = document.createElement("button");
              a.classList.add("translate-button-style-button"),
                a.setAttribute("aria-label", "Перевести");
              const l = document.createElement("div");
              l.classList.add("translate-button-class"),
                (l.textContent = "Перевести"),
                a.appendChild(l),
                n.appendChild(a),
                o.appendChild(n),
                t.insertAdjacentElement("beforeend", o);
              const r = document.createElement("style");
              (r.textContent = `
        .translate-button-class {
            text-overflow: ellipsis;
            overflow: hidden;
        }

        button.translate-button-style-button {
            padding: 0 16px;
            height: 36px;
            font-size: 14px;
            line-height: 36px;
            border-radius: 18px;
            position: relative;
            margin: 0;
            white-space: nowrap;
            min-width: 0;
            text-transform: none;
            font-family: "Roboto", "Arial", sans-serif;
            font-weight: 500;
            cursor: pointer;
            outline-width: 0;
            box-sizing: border-box;
            background: #373b37;
            color: white;
            flex: 1;
            flex-basis: 0.000000001px;
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
        }

        #translate-button.translate-button-id {
            float: right;
            margin: 1px 8px 1px 1px;
            max-height: max-content;
        }
        `),
                document.head.appendChild(r),
                o.addEventListener("click", s);
            }
          })(),
            o.disconnect();
          break;
        }
    });
    o.observe(document.body, { childList: !0, subtree: !0 });
  });
})();

→ Ссылка