YouTube Live Chat Channel Name Restorer

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==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);
    }
})();