Last active
February 21, 2025 08:01
-
-
Save htsign/14484659fa9224853835aeeae0cdbe10 to your computer and use it in GitHub Desktop.
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 Comment Username Reveals | |
// @description add user name for comment | |
// @namespace https://htsign.hateblo.jp | |
// @version 0.3.7 | |
// @author htsign | |
// @match https://www.youtube.com/* | |
// @updateURL https://gist.github.com/htsign/14484659fa9224853835aeeae0cdbe10/raw/YouTube-Comment-Username-Reveals.user.js | |
// @downloadURL https://gist.github.com/htsign/14484659fa9224853835aeeae0cdbe10/raw/YouTube-Comment-Username-Reveals.user.js | |
// @grant none | |
// ==/UserScript== | |
{ | |
'use strict'; | |
/** @type {Map<string, string | null>} */ | |
const nameMap = new Map(); | |
const pageManager = document.getElementById('page-manager'); | |
if (pageManager != null) { | |
/** | |
* @param {Node} node | |
* @returns {node is HTMLElement} | |
*/ | |
const isHTMLElement = node => node instanceof HTMLElement; | |
/** | |
* | |
* @param {HTMLElement} element | |
* @param {Name} name | |
* @returns {element is HTMLElement & { is: Name }} | |
* @template {string} Name | |
*/ | |
const is = (element, name) => 'is' in element && element.is === name; | |
const decode = (() => { | |
/** | |
* @type {[string, string][]} | |
*/ | |
const ENTITIES = [ | |
['amp', '&'], | |
['apos', '\''], | |
['quot', '"'], | |
['nbsp', ' '], | |
['lt', '<'], | |
['gt', '>'], | |
['#39', '\''], | |
]; | |
/** | |
* @param {string} s | |
* @returns {string} | |
*/ | |
return s => ENTITIES.reduce((acc, [entity, sym]) => acc.replaceAll(`&${entity};`, sym), s); | |
})(); | |
/** | |
* @param {HTMLAnchorElement} anchor | |
* @param {string} name | |
*/ | |
const appendName = (anchor, name) => { | |
// <span style="margin-left: 4px;" data-name="$name">( $name )</span> | |
const span = anchor.querySelector(`span[data-name="${name}"]`) ?? Object.assign( | |
document.createElement('span'), | |
{ textContent: `( ${name} )`, style: 'margin-left: 4px' }, | |
); | |
Object.assign(span.dataset, { name }); | |
// remove other names if exists | |
for (const el of anchor.querySelectorAll(`span[data-name]:not([data-name="${name}"])`)) { | |
el.remove(); | |
} | |
// append them name | |
(anchor.querySelector('ytd-channel-name') ?? anchor).append(span); | |
}; | |
const pageManagerObserver = new MutationObserver(records => { | |
const addedElements = records.flatMap(r => [...r.addedNodes]).filter(isHTMLElement); | |
for (const el of addedElements) { | |
const commentsWrapper = el.querySelector('#columns #primary-inner #below ytd-comments'); | |
if (commentsWrapper != null) { | |
const contentsObserver = new MutationObserver(records => { | |
const addedElements = records.flatMap(r => [...r.addedNodes]).filter(isHTMLElement); | |
for (const el of addedElements.filter(el => is(el, 'ytd-comment-view-model'))) { | |
for (const author of el.querySelectorAll('#author-text, #name')) { | |
const channelName = author.textContent.trim(); | |
if (channelName == null) { | |
console.warn('Username Reveals [name not found]:', author); | |
continue; | |
} | |
// append user name from map if nameMap has | |
if (nameMap.has(channelName)) { | |
const f = () => { | |
// break if record is removed | |
if (!nameMap.has(channelName)) return; | |
const name = nameMap.get(channelName); | |
if (name == null) { | |
return requestIdleCallback(f); | |
} | |
appendName(author, name); | |
}; | |
f(); | |
continue; | |
} | |
// reserve a record key for supress unnecessary request | |
nameMap.set(channelName, null); | |
fetch(author.href).then(async response => { | |
const text = await response.text(); | |
const [name] = text.match(/(?<=\<title\>).+?(?= - YouTube)/) ?? []; | |
if (name != null) { | |
const _name = decode(name); | |
appendName(author, _name); | |
nameMap.set(channelName, _name); | |
} | |
}, error => { | |
console.warn('Username Reveals [error]:', error); | |
nameMap.delete(channelName); | |
}); | |
} | |
} | |
}); | |
contentsObserver.observe(commentsWrapper, { childList: true, subtree: true }); | |
} | |
} | |
}); | |
pageManagerObserver.observe(pageManager, { childList: true }); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment