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