Last active
          December 12, 2020 20:23 
        
      - 
      
- 
        Save kebien6020/e18b489e40cd3767b1badcbb4d8b431c to your computer and use it in GitHub Desktop. 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | // ==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 | |
| // @updateURL https://gist.github.com/kebien6020/e18b489e40cd3767b1badcbb4d8b431c/raw/live-tranlation.user.js | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const MSG_REGEX = /\[eng?\].*/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); | |
| } | |
| })(); | 
Hey, wanted to let you know that I forked this script to add a few features and needed fixes here
Changelog (can also be found in the source):
- fix: script stops working because #chatframe url changes (which in turn changes #item-offset)
- match 'en:', 'eng:' too
- option to match generic 'any:', useful for collaborations
- add 'collaboration' button which switches the generic 'any:' matching
- option to always show moderators (default: on)
- option to always show Yagoo (tanigox, default: on)
- copy the actual comment instead of only the text (e.g. moderators will have blue names and emoji will appear)
 author colors won't work for super chats but they will show the donation amount
- fix auto-scrolling not always working
- filter chat mutations via tagName instead of classList (filter out 'engagement messages', slow mode notices and placeholders)
- make updateContainer async (await the container) to ensure it will add early messages
- slightly adjusted margins
- adjust log levels, always log errors
- add license notice of used icons
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment
  
            
It's supposed to auto-scroll to the bottom, just like the regular chat.