YouTube Live Chat Channel Name Restorer

YouTubeのライブチャットでチャンネル名を表示します。メンバーは元の色(緑など)、一般は明るいグレーで表示されます。IDと名前が重複する場合も表示します。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         YouTube Live Chat Channel Name Restorer
// @namespace    http://tampermonkey.net/
// @version      1.4
// @license MIT
// @description  YouTubeのライブチャットでチャンネル名を表示します。メンバーは元の色(緑など)、一般は明るいグレーで表示されます。IDと名前が重複する場合も表示します。
// @author       めらのあ(𝕏@meranoaaa)
// @match        https://www.youtube.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==
(function () {
    'use strict';
    // --- CONFIGURATION (Stable High Speed Mode) ---
    // 3万人規模の配信(秒間10コメント以上)に対応しつつ、ブロックリスクを抑えた設定です。
    const MAX_CONCURRENT_REQUESTS = 6; // 同時通信数
    const REQUEST_DELAY_MS = 100;      // 待機時間: 0.1秒 (秒間10回ペース)
    // --- STATE ---
    const CACHE = new Map();
    const PENDING = new Map();
    const QUEUE = [];
    let activeRequests = 0;
    // --- UTILS ---
    const getHandleText = (el) => {
        const text = el.textContent || el.innerText || '';
        if (text.startsWith('@') && text.length > 1) return text.trim();
        const chip = el.closest('yt-live-chat-author-chip') || el.querySelector('yt-live-chat-author-chip');
        if (chip) {
            const span = chip.querySelector('#author-name') || chip.querySelector('span');
            if (span && span.textContent?.trim().startsWith('@')) return span.textContent.trim();
        }
        return null;
    };
    // --- QUEUE PROCESSOR ---
    const processQueue = async () => {
        if (activeRequests >= MAX_CONCURRENT_REQUESTS || QUEUE.length === 0) return;
        const handle = QUEUE.shift();
        activeRequests++;
        try {
            const cleanHandle = handle.slice(1);
            const url = `https://www.youtube.com/@${cleanHandle}/about`;
            const response = await fetch(url);
            const html = await response.text();
            const match = html.match(/<meta property="og:title" content="([^"]+)">/);
            let realName = match ? match[1] : '';
            realName = realName.replace(' - YouTube', '').trim();
            if (!realName) realName = null;
            if (realName) {
                CACHE.set(cleanHandle, realName);
            }
            const callbacks = PENDING.get(cleanHandle) || [];
            callbacks.forEach(cb => cb(realName));
        } catch (e) {
            console.warn(`[NameRestorer] Failed to fetch ${handle}`, e);
        } finally {
            PENDING.delete(handle.slice(1));
            activeRequests--;
            if (QUEUE.length > 0) {
                setTimeout(processQueue, REQUEST_DELAY_MS);
            }
        }
    };
    const enqueueRequest = (handle, callback) => {
        const cleanHandle = handle.slice(1);
        if (CACHE.has(cleanHandle)) {
            callback(CACHE.get(cleanHandle));
            return;
        }
        if (PENDING.has(cleanHandle)) {
            PENDING.get(cleanHandle).push(callback);
            return;
        }
        PENDING.set(cleanHandle, [callback]);
        QUEUE.push(handle);
        processQueue();
    };
    // --- DOM PROCESSOR ---
    const processChip = (chip) => {
        if (chip.dataset.processed) return;
        if (chip.querySelector('.restored-channel-name')) {
            chip.dataset.processed = 'true';
            return;
        }
        const handle = getHandleText(chip);
        if (!handle) return;
        chip.dataset.processed = 'true';
        enqueueRequest(handle, (realName) => {
            if (!realName) return;
            if (chip.querySelector('.restored-channel-name')) return;
            const authorNameNode = chip.querySelector('#author-name') || chip.querySelector('span');
            if (!authorNameNode) return;
            // --- COLOR LOGIC ---
            const originalColor = window.getComputedStyle(authorNameNode).color;
            const authorType = chip.getAttribute('author-type') || '';
            const isSpecial = ['member', 'moderator', 'owner'].includes(authorType);
            const hasBadge = chip.querySelector('yt-live-chat-author-badge-renderer[type="member"]');
            // Default: #dddddd (Very Light Gray)
            let targetColor = '#dddddd';
            if (isSpecial || hasBadge) {
                targetColor = originalColor;
            }
            const nameSpan = document.createElement('span');
            nameSpan.className = 'restored-channel-name';
            nameSpan.textContent = realName + ' ';
            // --- VISUAL ADJUSTMENT ---
            nameSpan.style.cssText = `
                font-weight: 700 !important;
                margin-right: 4px;
                color: ${targetColor} !important;
            `;
            authorNameNode.style.fontSize = '0.9em';
            chip.insertBefore(nameSpan, chip.firstChild);
            chip.title = `${handle} (${realName})`;
        });
    };
    // --- OBSERVER ---
    const observer = new MutationObserver(mutations => {
        for (const mutation of mutations) {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType !== 1) return;
                    if (node.tagName === 'YT-LIVE-CHAT-AUTHOR-CHIP') {
                        processChip(node);
                    } else if (node.getElementsByTagName) {
                        const chips = node.getElementsByTagName('yt-live-chat-author-chip');
                        for (let i = 0; i < chips.length; i++) {
                            processChip(chips[i]);
                        }
                    }
                });
            }
        }
    });
    const startObserver = () => {
        const chatContainer = document.querySelector('yt-live-chat-renderer') ||
            document.querySelector('yt-live-chat-app') ||
            document.body;
        if (chatContainer) {
            observer.observe(chatContainer, { childList: true, subtree: true });
            document.querySelectorAll('yt-live-chat-author-chip').forEach(processChip);
            console.log('YouTube Chat Name Restorer (Release v1.4 Stable) Started');
        } else {
            setTimeout(startObserver, 1000);
        }
    };
    if (document.readyState === 'complete') {
        startObserver();
    } else {
        window.addEventListener('load', startObserver);
    }
})();