Make YouTube display the names of commenters instead of their handles.
August 18, 2026 ยท View on GitHub
// ==UserScript== // @name YouTube Commenter Names // @version 1.13.0 // @description Make YouTube display the names of commenters instead of their handles. // @author Lumynous // @license MIT // @match https://www.youtube.com/* // @match https://music.youtube.com/* // @match https://studio.youtube.com/* // @exclude https://www.youtube.com/persist_identity // @exclude https://studio.youtube.com/persist_identity // @exclude https://studio.youtube.com/ytscframe // @exclude https://www.youtube.com/embed/* // @grant none // @downloadURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/youtube-commenter-names.user.js // @updateURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/~meta // ==/UserScript==
"use strict";
const watchElm = (function () { const elmObserver = new MutationObserver(elmObserverCallback); elmObserver.observe(document, {childList: true, subtree: true}); const callbacks = []; // Array is faster since we don't remove.
function elmObserverCallback(mutations) {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE)
continue;
for (const {selector, callback} of callbacks) {
if (node.matches(selector))
callback(node);
for (const elm of node.querySelectorAll(selector))
callback(elm);
}
}
}
}
function elmCallback(observer, action, elm) {
observer.observe(elm, {attributeFilter: ["id", "href", "whole-message-clickable"]});
action(elm);
}
return (selector, action) => {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations)
action(mutation.target);
});
const callback = elmCallback.bind(null, observer, action);
for (const elm of document.querySelectorAll(selector))
callback(elm);
callbacks.push({selector, callback});
};
})();
async function fetchInternalApi(endpoint, body) {
const response = await fetch(
https://www.youtube.com/youtubei/v1/${endpoint}?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false,
{
method: "POST",
body: JSON.stringify({
context: {client: {clientName: "WEB", clientVersion: "2.20240411.01.00"}},
...body,
}),
},
);
return await response.json();
}
function cacheFunctionDecorator(fn) { const cache = new Map(); const decoratedFn = (arg) => { let res = cache.get(arg); if (res === void 0) { res = fn(arg); cache.set(arg, res); } return res; }; decoratedFn.cache = cache; return decoratedFn; }
const getChannelId = cacheFunctionDecorator(async (handle) => {
let json = await fetchInternalApi("navigation/resolve_url", {
url: https://www.youtube.com/${handle},
});
if (!json.endpoint.browseEndpoint) {
// Workaround: Some channels such as @rayduenglish behave strange. Normally GETing
// channel pages result 303 and redirect to /rayduenglish for example; the internal
// API responses similarly, the workaround is to resolve twice. However, some are
// impossible to resolve correctly; for example, requesting /@Konata redirected to
// /user/Konata, and /user/Konata leads 404. This is probably a bug of YouTube.
json = await fetchInternalApi("navigation/resolve_url", json.endpoint.urlEndpoint);
}
return json.endpoint.browseEndpoint.browseId;
});
const getChannelName = cacheFunctionDecorator(async (id) => { const json = await fetchInternalApi("browse", {browseId: id}); return json.metadata.channelMetadataRenderer.title; });
function replaceText(elm, text) { elm.firstChild.textContent = text; }
switch (true) { case location.pathname === "/live_chat": watchElm("yt-live-chat-participant-renderer", async (elm) => { let name; if (elm.data.authorBadges?.at(-1).liveChatAuthorBadgeRenderer.icon?.iconType === "OWNER") name = top.__lmn?.ownerName; else name = await getChannelName(elm.data.authorExternalChannelId); name && replaceText(elm.querySelector("#author-name"), name); }); watchElm("yt-live-chat-banner-redirect-renderer", async (elm) => { const handle = elm.data.bannerMessage.runs.find((x) => x.bold).text; const name = await getChannelName(await getChannelId(handle)); replaceText(elm.querySelector("#banner-text .bold"), name); }); // fallthrough
case location.pathname === "/live_chat_replay":
watchElm("ytd-sponsorships-live-chat-gift-redemption-banner-renderer", async (elm) => {
let idx = elm.data.headerText.runs.findIndex((x) => x.text[0] === "@");
let name = await getChannelName(await getChannelId(elm.data.headerText.runs[idx].text));
replaceText(elm.querySelector(`#header-text span:nth-child(${idx + 1})`), name);
idx = elm.data.messageText.runs.findIndex((x) => x.text[0] === "@");
if ((name = top.__lmn?.ownerName))
replaceText(elm.querySelector(`#message-text span:nth-child(${idx + 1})`), name);
});
watchElm("yt-live-chat-ticker-paid-message-item-renderer", async (elm) => {
const name = await getChannelName(elm.data.authorExternalChannelId);
replaceText(elm.querySelector("span"), name);
});
watchElm(`yt-live-chat-text-message-renderer,
yt-live-chat-paid-message-renderer,
yt-live-chat-paid-sticker-renderer,
yt-live-chat-membership-item-renderer,
ytd-sponsorships-live-chat-gift-purchase-announcement-renderer,
ytd-sponsorships-live-chat-gift-redemption-announcement-renderer`, async (elm) => {
let name = await getChannelName(elm.data.authorExternalChannelId);
replaceText(elm.querySelector("#author-name"), name);
// If the message is a reply.
if (elm.data.beforeContentButtons?.at(-1).buttonViewModel.iconName !== "MESSAGE")
return;
name = await getChannelName(await getChannelId(elm.data.beforeContentButtons.at(-1).buttonViewModel.title));
replaceText(elm.querySelector("#before-content-buttons > :last-child .ytSpecButtonShapeNextButtonTextContent"), name);
});
watchElm("yt-gift-message-view-model", async (elm) => {
const name = await getChannelName(await getChannelId(elm.data.authorName.content));
replaceText(elm.querySelector("#author-name-v2").firstElementChild.firstElementChild, name);
});
// Get handle only.
watchElm("yt-live-chat-paid-message-renderer", (elm) => {
const handle = elm.data.authorName.simpleText;
if (!getChannelId.cache.has(handle))
getChannelId.cache.set(handle, Promise.resolve(elm.data.authorExternalChannelId));
});
watchElm("ytd-sponsorships-live-chat-gift-purchase-announcement-renderer", (elm) => {
const handle = elm.data.header.liveChatSponsorshipsHeaderRenderer.authorName.simpleText;
if (!getChannelId.cache.has(handle))
getChannelId.cache.set(handle, Promise.resolve(elm.data.authorExternalChannelId));
});
// Replace gifter name only.
watchElm("ytd-sponsorships-live-chat-gift-redemption-announcement-renderer", async (elm) => {
const gifter = elm.data.message.runs.find((x) => x.bold).text;
const name = await getChannelName(await getChannelId(gifter));
replaceText(elm.querySelector("#message .bold"), name);
});
break;
case location.hostname === "www.youtube.com":
window.__lmn = {};
// Get video owner names without requesting. Video owners likely have comments on their videos.
document.addEventListener("yt-page-data-updated", (ev) => {
if (ev.detail.pageType !== "watch")
return;
const videoOwner = ev.target.get("data").response.contents.twoColumnWatchNextResults
.results.results.contents.find((x) => x.videoSecondaryInfoRenderer)
.videoSecondaryInfoRenderer.owner.videoOwnerRenderer;
if (videoOwner.title) {
const id = videoOwner.title.runs[0].navigationEndpoint.browseEndpoint.browseId;
const name = videoOwner.title.runs[0].text;
if (!getChannelName.cache.has(id))
getChannelName.cache.set(id, Promise.resolve(name));
window.__lmn.ownerName = name;
} else {
// Collaborated videos.
videoOwner.navigationEndpoint.showDialogCommand.panelLoadingStrategy.inlineContent
.dialogViewModel.customContent.listViewModel.listItems
.forEach(({listItemViewModel: {title}}) => {
const id = title.commandRuns[0].onTap.innertubeCommand.browseEndpoint.browseId;
if (!getChannelName.cache.has(id))
getChannelName.cache.set(id, Promise.resolve(title.content));
});
}
});
// Mentions in titles.
watchElm("#title.ytd-watch-metadata a.yt-simple-endpoint", async (elm) => {
if (elm.pathname[1] !== "@")
return;
const name = await getChannelName(elm.data.browseEndpoint.browseId);
replaceText(elm, name);
});
// fallthrough
case location.hostname === "music.youtube.com":
// Commenters.
watchElm("#author-text.ytd-comment-view-model", async (elm) => {
const name = await getChannelName(elm.data.browseEndpoint.browseId);
replaceText(elm.firstElementChild, name);
});
watchElm("#name.ytd-author-comment-badge-renderer", async (elm) => {
const name = await getChannelName(elm.data.browseEndpoint.browseId);
replaceText(elm.querySelector("#text"), name);
});
// Mentions in comments.
watchElm("#content-text.ytd-comment-view-model a", async (elm) => {
// Skip non-mentions. URLs with protocol begin with `http` only.
// Channels begin with `/channel/`.
if (elm.attributes.href.value[1] !== "c")
return;
const name = await getChannelName(elm.href.slice(elm.href.lastIndexOf("/") + 1));
replaceText(elm, `\xA0${name}\xA0`);
});
break;
default: {
const observer = new IntersectionObserver(async (entries) => {
for (const {isIntersecting, target} of entries) {
if (!isIntersecting)
continue;
const name = await getChannelName(await getChannelId(target.data.authorText.simpleText));
replaceText(target.querySelector(".author-text"), name);
}
});
watchElm("ytcp-comment", (elm) => observer.observe(elm));
watchElm("ytcp-author-comment-badge", async (elm) => {
const name = await getChannelName(await getChannelId(elm.data.authorText.simpleText));
replaceText(elm.firstElementChild.firstElementChild, name);
});
}
}