Skip to content

Instantly share code, notes, and snippets.

@lumynou5
Last active July 2, 2025 14:57
Show Gist options
  • Save lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d to your computer and use it in GitHub Desktop.
Save lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d to your computer and use it in GitHub Desktop.
Make YouTube display the names of commenters instead of their handles.
// ==UserScript==
// @name YouTube Commenter Names
// @version 1.7.5
// @description Make YouTube display the names of commenters instead of their handles.
// @author Lumynous
// @license MIT
// @match https://www.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
// @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 TRUE_FN = () => true;
const elmObserver = new MutationObserver(elmObserverCallback);
elmObserver.observe(document, {childList: true, subtree: true});
const callbacks = new Set();
function elmObserverCallback(mutations) {
for (const {callback, selector} of callbacks) {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE)
continue;
if (node.matches(selector)) {
callback(node);
}
for (const x of node.querySelectorAll(selector)) {
callback(x);
}
}
}
}
}
function elmCallback(observer, action, filter, elm) {
if (filter(elm)) {
action(elm);
observer.observe(elm, {attributes: true});
}
}
return (selector, action, filter) => {
filter ??= TRUE_FN;
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
action(mutation.target);
}
});
const callback = elmCallback.bind(null, observer, action, filter);
for (const elm of document.querySelectorAll(selector)) {
callback(elm);
}
callbacks.add({callback, selector});
};
})();
async function fetchInternalApi(endpoint, body) {
const response = await fetch(
`https://www.youtube.com/youtubei/v1/${endpoint}?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8`,
{
method: 'POST',
body: JSON.stringify({
context: {client: {clientName: 'WEB', clientVersion: '2.20240411.01.00'}},
...body,
}),
}
);
return await response.json();
}
function cacheFunctionDecorator(f) {
const cache = new Map();
return (arg) => {
let res = cache.get(arg);
if (res === undefined) {
res = f(arg);
cache.set(arg, res);
}
return res;
};
}
const getChannelId = cacheFunctionDecorator(async (url) => {
let json = await fetchInternalApi('navigation/resolve_url', {url});
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(node, text) {
if (node.firstChild.textContent === text)
return;
node.firstChild.textContent = text;
}
if (location.pathname === '/live_chat') {
watchElm('yt-live-chat-author-chip:not([hidden])', (elm) => {
getChannelName(elm.__dataHost.__data.data.authorExternalChannelId)
.then((name) => replaceText(elm.querySelector('#author-name'), name));
});
} else if (location.hostname === 'www.youtube.com') {
// Mentions in titles.
watchElm('#title.ytd-watch-metadata a.yt-simple-endpoint', (elm) => {
getChannelName(elm.data.browseEndpoint.browseId)
.then((name) => replaceText(elm, name));
}, (elm) => elm.pathname[1] === '@' /* Skip hashtags. */);
// Commenters.
watchElm('#author-text.ytd-comment-view-model' +
':not(ytd-browse *, ytd-popup-container #expander *)', (elm) => {
getChannelName(elm.data.browseEndpoint.browseId)
.then((name) => replaceText(elm.firstElementChild, name));
});
watchElm('#name.ytd-author-comment-badge-renderer', (elm) => {
getChannelName(elm.data.browseEndpoint.browseId)
.then((name) => replaceText(elm.querySelector('#text'), name));
});
// Commenters in posts and in notification previews.
watchElm('ytd-browse #author-text.ytd-comment-view-model, ' +
'ytd-popup-container #expander #author-text.ytd-comment-view-model', (elm) => {
getChannelId(elm.href)
.then(getChannelName)
.then((name) => replaceText(elm.firstElementChild, name));
});
// Mentions in comments.
watchElm('#content-text.ytd-comment-view-model a', (elm) => {
getChannelName(elm.href.slice(elm.href.lastIndexOf('/') + 1))
.then((name) => replaceText(elm, `\xA0${name}\xA0`));
}, (elm) => elm.textContent.trim()[0] === '@' /* Skip anchors such as timestamps. */);
} else {
// TODO: After changing the filters of comments, some commenters' names might be IDs
// if they're the same one, because their attributes wasn't changed. A solution is
// to watch their character data, but it causes a lock with Edge's translation
// feature.
watchElm('#name.ytcp-comment', (elm) => {
getChannelId(elm.href)
.then(getChannelName)
.then((name) => replaceText(elm.firstElementChild, name));
});
watchElm('#badge-name.ytcp-author-comment-badge', (elm) => {
getChannelId(elm.href)
.then(getChannelName)
.then((name) => replaceText(elm.firstElementChild, name));
});
}
// ==UserScript==
// @name YouTube Commenter Names
// @version 1.7.5
// @description Make YouTube display the names of commenters instead of their handles.
// @author Lumynous
// @license MIT
// @match https://www.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
// @downloadURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/youtube-commenter-names.user.js
// @updateURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/~meta
// ==/UserScript==
@lumynou5
Copy link
Author

lumynou5 commented Jun 29, 2024

@mrbenho Fixed in 1.6.3.
It seems that Edge changes the textContent of text nodes to translate pages, and causes the mutation observers in the userscript triggered and triggered. This behavior is different from Chrome.
I decided to observe characterData mutations in 1.6.x because attributes sometimes doesn't work, and currently there are indeed some names cannot be replaced after re-sorting comments. I'll try if we can fix this and make the script still work on Edge.

update (2024-07-09):
The current client version is 2.20240704.03.00. In this version, attributes seems no longer having bug when re-sorting comments.

update (2024-12-14):
The re-sorting comments bug is actually due to the :not([hidden]). When encounters a comment from channel owner, its #author-text is skipped because it's hidden, and thereby it's not observed, then after re-sorting, it stays at the same position but for a different comment, the href is changed but not observed.

@DarkblooM-IO
Copy link

Thank you for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment