Last active
December 6, 2025 17:20
-
-
Save lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d to your computer and use it in GitHub Desktop.
Make YouTube display the names of commenters instead of their handles.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name YouTube Commenter Names | |
| // @version 1.10.6 | |
| // @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 | |
| // @exclude https://www.youtube.com/embed/* | |
| // @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) { | |
| action(elm); | |
| observer.observe(elm, {attributes: true}); | |
| } | |
| 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 === undefined) { | |
| res = fn(arg); | |
| cache.set(arg, res); | |
| } | |
| return res; | |
| }; | |
| decoratedFn.cache = cache; | |
| return decoratedFn; | |
| } | |
| 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(elm, text) { | |
| elm.firstChild.textContent = text; | |
| } | |
| switch (true) { | |
| case location.pathname === '/live_chat': | |
| watchElm('yt-live-chat-participant-renderer', async (elm) => { | |
| if (elm.data.authorBadges?.at(-1).liveChatAuthorBadgeRenderer.icon?.iconType === 'OWNER') | |
| return; | |
| const name = await getChannelName(elm.data.authorExternalChannelId); | |
| 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(`https://www.youtube.com/${handle}`)); | |
| replaceText(elm.querySelector('#banner-text .bold'), name); | |
| }); | |
| case location.pathname === '/live_chat_replay': | |
| 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) => { | |
| const name = await getChannelName(elm.data.authorExternalChannelId); | |
| replaceText(elm.querySelector('#author-name'), name); | |
| }); | |
| // Get gifter ID only. | |
| watchElm('ytd-sponsorships-live-chat-gift-purchase-announcement-renderer', (elm) => { | |
| const url = `https://www.youtube.com/${ | |
| elm.data.header.liveChatSponsorshipsHeaderRenderer.authorName.simpleText | |
| }`; | |
| if (!getChannelId.cache.has(url)) | |
| getChannelId.cache.set(url, 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(`https://www.youtube.com/${gifter}`)); | |
| replaceText(elm.querySelector('#message .bold'), name); | |
| }); | |
| break; | |
| case location.hostname === 'www.youtube.com': | |
| // 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; | |
| if (!getChannelName.cache.has(id)) | |
| getChannelName.cache.set(id, Promise.resolve(videoOwner.title.runs[0].text)); | |
| } 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); | |
| }); | |
| // 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: | |
| // 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, #badge-name.ytcp-author-comment-badge', async (elm) => { | |
| const name = await getChannelName(await getChannelId(elm.href)); | |
| replaceText(elm.firstElementChild, name); | |
| }); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name YouTube Commenter Names | |
| // @version 1.10.6 | |
| // @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 | |
| // @exclude https://www.youtube.com/embed/* | |
| // @downloadURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/youtube-commenter-names.user.js | |
| // @updateURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/~meta | |
| // ==/UserScript== |
Author
Thank you for this!
Does not work with Qutebrowser.
Author
@Frestein Can you describe the issue you encountered?
I tried to run the code and didn't see any problems.
qutebrowser v3.5.1
Git commit:
Backend: QtWebEngine 6.9.2
based on Chromium 130.0.6723.192
with security patches up to 139.0.7258.67 (plus any distribution patches)
(source: api)
Qt: 6.9.2
CPython: 3.13.7
PyQt: 6.9.1
Qt wrapper info:
PyQt6: success
PyQt5: not imported
-> selected: PyQt6 (via autoselect)
colorama: no
jinja2: 3.1.6
pygments: 2.19.2
yaml: 6.0.3
adblock: no
objc: no
PyQt6.QtWebEngineCore: 6.9.0
PyQt6.sip: 6.11.1
pdf.js: no
sqlite: 3.50.4
QtNetwork SSL: OpenSSL 3.5.3 16 Sep 2025
Style: QFusionStyle
Platform plugin: wayland
OpenGL: AMD, 4.6 (Compatibility Profile) Mesa 25.2.3-arch1.2
Platform: Linux-6.16.8-arch3-1-x86_64-with-glibc2.42, 64bit
Linux distribution: Arch Linux (arch)
Frozen: False
Imported from /usr/lib/python3.13/site-packages/qutebrowser
Using Python from /usr/bin/python3
Qt library executable path: /usr/lib/qt6, data path: /usr/share/qt6
Author
bruh why did youtube change the live chat back and then now revert the reversion?
Author
Each .then is replaced by await now because the former mutates an internal handler list of the promise it calls on, and as a result memory usage grows.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@mrbenho Fixed in 1.6.3.
It seems that Edge changes the
textContentof 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
characterDatamutations in 1.6.x becauseattributessometimes 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,attributesseems 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-textis 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, thehrefis changed but not observed.