Last active
April 12, 2024 14:07
-
-
Save htsign/14484659fa9224853835aeeae0cdbe10 to your computer and use it in GitHub Desktop.
This file contains 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.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