Кнопка "Перевести" исчезает после одного использования в скрипте для YouTube Studio
Я создаю скрипт для YouTube Studio, который добавляет кнопку "Перевести", переводящую название и описание видео с помощью Google Translate. После первого использования кнопка исчезает, и ее нужно обновить страницу, чтобы она снова появилась.
Желаемое поведение:
Кнопка "Перевести" должна оставаться на странице и быть доступной для повторного использования без необходимости перезагрузки страницы.
Проблема:
Кнопка исчезает после первого использования, а также не появляется снова при возвращении на страницу или после каких-либо других действий.
Шаги для воспроизведения:
- Откройте страницу с YouTube Studio (например, страницу перевода видео).
- Скрипт добавляет кнопку "Перевести".
- После нажатия кнопка исчезает и больше не появляется.
Мой код:
!(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 })
})
})()
Что я пробовал:
- Я пробовал использовать
MutationObserver
для добавления кнопки при измененииDOM
, но кнопка все равно исчезает после одного использования. - Пробовал использовать
setInterval,
но это привело к перегрузке страницы и замедлению работы.
Вопрос:
Как я могу исправить проблему с исчезновением кнопки после первого использования и сделать её доступной для повторного использования без перезагрузки страницы?
Ответы (1 шт):
Задал вопрос о помощи в исправлении этого скрипта. Прошло несколько дней, но никто не ответил или не заметил!
Однако, самостоятельно разобрался и с помощью 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 });
});
})();