您需要先安装一个扩展,例如 篡改猴、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);
- }
- })();