您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
add user name for comment
- // ==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/*
- // @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 });
- }
- }