Skip to content

Instantly share code, notes, and snippets.

@htsign
Last active April 12, 2024 14:07
Show Gist options
  • Save htsign/14484659fa9224853835aeeae0cdbe10 to your computer and use it in GitHub Desktop.
Save htsign/14484659fa9224853835aeeae0cdbe10 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name YouTube Comment Username Reveals
// @description add user name for comment
// @namespace https://htsign.hateblo.jp
// @version 0.3.5
// @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;
/**
* @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) {
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