您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
YouTube Live のチャットでチャンネルの所有者・モデレータの発言を目立たせ、自動で上部に固定する
// ==UserScript== // @name YouTube Live: Highlight Moderator Comments // @namespace https://twitter.com/aryn_ra // @version 1.0.1 // @description YouTube Live のチャットでチャンネルの所有者・モデレータの発言を目立たせ、自動で上部に固定する // @author Aryn // @match https://www.youtube.com/live_chat?* // @match https://www.youtube.com/live_chat_replay?* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // ==/UserScript== /* jshint esversion: 6 */ (function() { 'use strict'; const HIGHLIGHT_BACKGROUND_COLOR_LIGHT = 'lavender'; const HIGHLIGHT_BACKGROUND_COLOR_DARK = 'black'; const PIN_LIMIT = 5; const BANNER_OFFSET = 66; const css = ` html { /* light theme */ --highlight-background-color: ${HIGHLIGHT_BACKGROUND_COLOR_LIGHT}; } html[dark] { /* dark theme */ --highlight-background-color: ${HIGHLIGHT_BACKGROUND_COLOR_DARK}; } #item-offset { overflow: visible !important; } #items { transform: none !important; } yt-live-chat-text-message-renderer[author-type=owner].hmc-highlight, yt-live-chat-text-message-renderer[author-type=moderator].hmc-highlight { position: sticky; z-index: 1; transition: top ease-in-out .1s; background-color: var(--highlight-background-color); } #live-chat-banner { z-index: 2; } #hmc-config { position: absolute; right: 84px; width: 24px; height: 24px; margin: 8px; cursor: pointer; user-select: none; opacity: .8; } #hmc-config:hover { opacity: 1; } #hmc-config-popover { position: absolute; top: 48px; left: 0; display: none; width: 400px; height: 400px; padding: 8px; color: var(--yt-spec-text-primary); background-color: var(--yt-spec-general-background-b); } #hmc-config-popover.hmc-show { display: block; } #hmc-config-popover h1 { font-size: 18px; font-weight: normal; line-height: 18px; margin-bottom: 8px; } #hmc-config-popover p { font-size: 12px; line-height: 12px; width: 384px; } #hmc-config-popover textarea { font-size: 15px; box-sizing: border-box; width: 384px; height: 322px; margin: 8px 0; color: var(--ytd-searchbox-text-color); border: 1px solid var(--ytd-searchbox-legacy-border-color); background-color: var(--ytd-searchbox-background); } #hmc-config-popover input[type='button'], #hmc-config-popover input[type='reset'] { font-size: 12px; box-sizing: border-box; height: 24px; } `; const exclusionList = GM_getValue('exclusion-list', []); const itemsObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { function isModerator(node) { const authorType = node.getAttribute('author-type'); return authorType === 'owner' || authorType === 'moderator'; } function isExcluded(node) { const channelName = node.querySelector('#author-name').textContent.trim(); return exclusionList.includes(channelName) } function isHighlightTarget(node) { return isModerator(node) && !isExcluded(node); } const nodes = [...mutation.target.children].filter(isHighlightTarget); for (const node of nodes) { node.classList.add('hmc-highlight'); } const unpinNodes = nodes.slice(0, -PIN_LIMIT); for (const node of unpinNodes) { node.style.transition = ''; node.style.top = ''; } const pinNodes = nodes.slice(-PIN_LIMIT); const existsActiveBanner = document.getElementById('live-chat-banner').hasAttribute('has-active-banner'); let offset = existsActiveBanner ? BANNER_OFFSET : 0; for (const node of pinNodes) { node.style.top = `${offset}px`; offset += node.clientHeight; } }); }); const items = document.querySelector('#item-offset #items'); itemsObserver.observe(items, { childList: true }); const itemListObserver = new MutationObserver(() => { const items = document.querySelector('#item-offset #items'); itemsObserver.observe(items, { childList: true }); }); const itemList = document.querySelector('#item-list'); itemListObserver.observe(itemList, { childList: true }); const configButton = document.createElement('div'); configButton.id = 'hmc-config'; configButton.textContent = '🔧'; configButton.addEventListener('click', () => { document.querySelector('#hmc-config-popover').classList.toggle('hmc-show'); }); const chatHeader = document.querySelector('yt-live-chat-header-renderer'); chatHeader.appendChild(configButton); const configPopover = document.createElement('div'); configPopover.id = 'hmc-config-popover'; configPopover.innerHTML = ` <h1>強調除外設定</h1> <p>1行に1つずつ、除外したいチャンネル名 (表示ユーザー名) を入力</p> <form name="exclusion-config"> <div><textarea id="hmc-exclusion-text">${exclusionList.join('\n')}</textarea></div> <div><input type="reset" value="キャンセル" id="hmc-cancel" /> <input type="button" value="保存" id="hmc-save" /></div> </form> `; document.body.appendChild(configPopover); const save = document.querySelector('#hmc-save'); save.addEventListener('click', () => { const exclusionText = document.querySelector('#hmc-exclusion-text'); GM_setValue('exclusion-list', exclusionText.value.trim().split('\n').map((s) => s.trim())); location.reload(); }); const cancel = document.querySelector('#hmc-cancel'); cancel.addEventListener('click', () => { document.querySelector('#hmc-config-popover').classList.toggle('hmc-show'); }); if (typeof GM_addStyle !== 'undefined') { GM_addStyle(css); } else { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } })();