Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Streaming live translate
- // @namespace youtube.com
- // @version 0.8
- // @author u/BakuhatsuK
- // @description Get streaming translation comments easily. Based on extension made by u/konokalahola
- // @include https://*.youtube.com/watch*
- // @run-at document-start
- // ==/UserScript==
- (function () {
- 'use strict';
- const MSG_REGEX = /^\[[\s\S]*en[\s\S]*\].*/i;
- const DEBUG = false;
- const sleep = ms => new Promise(res => setTimeout(res, ms));
- const whenAvailable = async (selector, wnd = window) => {
- while (true) {
- const elem = wnd.document.querySelector(selector);
- if (!elem) {
- if (DEBUG) console.warn('whenAvailable: Could not find selector', selector);
- await sleep(1000);
- } else {
- if (DEBUG) console.log('whenAvailable: Found selector', selector);
- return elem;
- }
- }
- };
- let ytApp;
- async function main() {
- if (DEBUG) console.log('main: Running on', window.location.href);
- ytApp = await whenAvailable('ytd-app');
- const appObserver = new MutationObserver(onPageChange);
- appObserver.observe(ytApp, {attributes: true, attributeFilter: ['is-watch-page']});
- // Trigger initial setup as if page had just changed
- onPageChange();
- }
- let chatObserver;
- async function onPageChange() {
- if (DEBUG) console.log('onPageChange: Init');
- const changedToWatchPage = ytApp.hasAttribute('is-watch-page');
- if (changedToWatchPage) {
- const chatFrame = await whenAvailable('#chatframe');
- const chat = await whenAvailable('#item-offset', chatFrame.contentWindow);
- if (DEBUG) console.log('onPageChange: Setting chat observer');
- chatObserver = new MutationObserver(onChatChange);
- chatObserver.observe(chat, {childList: true, subtree: true});
- if (DEBUG) console.log('onPageChange: Setting up translation container');
- resetContainer();
- setupContainer();
- } else {
- if (DEBUG) console.log('onPageChange: Disconnecting chat observer');
- if (chatObserver) {
- chatObserver.disconnect();
- }
- if (DEBUG) console.log('onPageChange: Removing translation container');
- resetContainer();
- }
- }
- function onChatChange(mutations) {
- for (const mutation of mutations) {
- if (mutation.type !== 'childList') continue;
- const chatElems = [...mutation.addedNodes]
- .filter(node => node.nodeType === Node.ELEMENT_NODE)
- .filter(elem => elem.classList.contains('yt-live-chat-item-list-renderer'));
- if (chatElems.length === 0) continue;
- if (DEBUG) console.log('onChatChange: New messages ', chatElems.length);
- for (const chatElem of chatElems) {
- onMessage(chatElem);
- }
- }
- }
- function onMessage(chatElem) {
- const msgElem = chatElem.querySelector('#message');
- if (!msgElem) {
- if (DEBUG) console.warn('onMessage: Could not find message within chatElem', chatElem);
- return;
- }
- const msgText = msgElem.textContent;
- const isAMatch = MSG_REGEX.test(msgText)
- if (!isAMatch) return;
- const author = chatElem.querySelector('#author-name');
- const authorText = author ? author.textContent : '???';
- if (DEBUG) console.log('onMessage: Matched text', msgText);
- updateContainer(msgText, authorText);
- }
- function updateContainer(msg, author) {
- const container = document.getElementById("translate_container");
- if (!container) {
- if (DEBUG) console.warn('updateContainer: Wasn\'t able to show message beacuse container is not ready');
- return;
- }
- const position = container.scrollHeight - container.offsetHeight - 5;
- container.insertAdjacentHTML('beforeend', `
- <div style="margin-top: 15px;">
- <b>${author}:</b> ${msg}
- </div>
- `);
- if (container.scrollTop >= position) {
- container.scrollTo(0, container.scrollHeight);
- }
- }
- function resetContainer() {
- const button = document.getElementById("translate_live_button");
- if (button) button.remove();
- const container = document.getElementById("translate_container");
- if (container) container.remove();
- }
- async function setupContainer() {
- const upnext = await whenAvailable("#upnext");
- upnext.insertAdjacentHTML('beforeend', '<svg id="translate_live_button" viewBox="0 0 20 20" width="20" height="20" class="adjustments w-6 h-6" style="vertical-align: middle; margin-left: 7px;"><path fill-rule="evenodd" d="M7 2a1 1 0 011 1v1h3a1 1 0 110 2H9.578a18.87 18.87 0 01-1.724 4.78c.29.354.596.696.914 1.026a1 1 0 11-1.44 1.389c-.188-.196-.373-.396-.554-.6a19.098 19.098 0 01-3.107 3.567 1 1 0 01-1.334-1.49 17.087 17.087 0 003.13-3.733 18.992 18.992 0 01-1.487-2.494 1 1 0 111.79-.89c.234.47.489.928.764 1.372.417-.934.752-1.913.997-2.927H3a1 1 0 110-2h3V3a1 1 0 011-1zm6 6a1 1 0 01.894.553l2.991 5.982a.869.869 0 01.02.037l.99 1.98a1 1 0 11-1.79.895L15.383 16h-4.764l-.724 1.447a1 1 0 11-1.788-.894l.99-1.98.019-.038 2.99-5.982A1 1 0 0113 8zm-1.382 6h2.764L13 11.236 11.618 14z" clip-rule="evenodd"></path></svg>');
- document.getElementById("translate_live_button").style.fill = "gray";
- document.getElementById("upnext").style.display = "flex";
- document.getElementById("translate_live_button").style.display = "block";
- document.getElementById("translate_live_button").onclick = function() {
- if (document.getElementById("translate_live_button").style.fill === "gray") {
- let divTemp = document.getElementById("translate_container");
- document.getElementById("translate_live_button").style.fill = "#c00";
- document.getElementById("info-contents").style.display = "none";
- divTemp.style.display = "block";
- divTemp.scrollTo(0, divTemp.scrollHeight);
- }
- else {
- document.getElementById("translate_live_button").style.fill = "gray";
- document.getElementById("info-contents").style.display = "block";
- document.getElementById("translate_container").style.display = "none";
- }
- };
- document.getElementById("info-contents").insertAdjacentHTML('afterend', '<div id="translate_container" style="display: none; font-size: 13px; width: 100%; height: 120px; background-color: white; overflow: hidden; overflow-y: scroll; padding-bottom: 15px; margin-top: 5px; padding-left: 10px; padding-right: 10px;"></div>');
- if (DEBUG) console.log("setupContainer: End setup");
- }
- if (document.readyState == "complete" || document.readyState == "loaded" || document.readyState == "interactive") {
- main();
- } else {
- document.addEventListener("DOMContentLoaded", main);
- }
- })();
Add Comment
Please, Sign In to add comment