- /*
-
- MIT License
-
- Copyright 2023 CY Fung
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
-
- */
- // ==UserScript==
- // @name Restore YouTube Username from Handle to Custom
- // @namespace http://tampermonkey.net/
- // @version 0.1.17
- // @license MIT License
- // @description To restore YouTube Username to the traditional custom name
- // @description:ja YouTubeのユーザー名を伝統的なカスタム名に復元するために。
-
- // @author CY Fung
- // @match https://www.youtube.com/*
- // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
- // @icon https://github.com/cyfung1031/userscript-supports/raw/main/icons/general-icon.png
- // @supportURL https://github.com/cyfung1031/userscript-supports
- // @run-at document-start
- // @grant none
- // @unwrap
- // @allFrames
- // @inject-into page
- // ==/UserScript==
-
- /* jshint esversion:8 */
-
- (function () {
- 'use strict';
-
- const cfg = {};
- class Mutex {
-
- constructor() {
- this.p = Promise.resolve()
- }
-
- lockWith(f) {
- this.p = this.p.then(() => new Promise(f)).catch(console.warn)
- }
-
- }
- const mutex = new Mutex();
-
- const displayNameCacheStore = new Map();
-
- const promisesStore = {};
-
- function createNetworkPromise(channelId) {
-
- return new Promise(networkResolve => {
-
-
- mutex.lockWith(lockResolve => {
-
-
-
- //INNERTUBE_API_KEY = ytcfg.data_.INNERTUBE_API_KEY
-
-
- fetch(new window.Request(`/youtubei/v1/browse?key=${cfg.INNERTUBE_API_KEY}&prettyPrint=false`, {
- "method": "POST",
- "mode": "same-origin",
- "credentials": "same-origin",
-
- // (-- reference: https://javascript.info/fetch-api
- referrerPolicy: "no-referrer",
- cache: "default",
- redirect: "error",
- integrity: "",
- keepalive: false,
- signal: undefined,
- window: window,
- // --)
-
- "headers": {
- "Content-Type": "application/json",
- "Accept-Encoding": "gzip, deflate, br"
- },
- "body": JSON.stringify({
- "context": {
- "client": {
- "clientName": "MWEB",
- "clientVersion": `${cfg.INNERTUBE_CLIENT_VERSION || '2.20230614.01.00'}`,
- "originalUrl": `https://m.youtube.com/channel/${channelId}`,
- "playerType": "UNIPLAYER",
- "platform": "MOBILE",
- "clientFormFactor": "SMALL_FORM_FACTOR",
- "acceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
- "mainAppWebInfo": {
- "graftUrl": `/channel/${channelId}`,
- "webDisplayMode": "WEB_DISPLAY_MODE_BROWSER",
- "isWebNativeShareAvailable": true
- }
- },
- "user": {
- "lockedSafetyMode": false
- },
- "request": {
- "useSsl": true,
- "internalExperimentFlags": [],
- "consistencyTokenJars": []
- }
- },
- "browseId": `${channelId}`
- })
- })).then(res => {
- lockResolve();
- return res.json();
- }).then(res => {
- networkResolve(res);
- }).catch(e => {
- lockResolve();
- console.warn(e);
- })
-
-
-
- });
-
- })
-
- }
-
- const queueMicrotask_ = typeof queueMicrotask === 'function' ? queueMicrotask : requestAnimationFrame;
-
- function getDisplayName(channelId) {
-
- return new Promise(resolve => {
-
- let cachedResult = displayNameCacheStore.get(channelId);
- if (cachedResult) {
- resolve(cachedResult);
- return;
- }
-
- if (!promisesStore[channelId]) promisesStore[channelId] = createNetworkPromise(channelId);
-
- promisesStore[channelId].then(res => {
-
- queueMicrotask_(() => {
- promisesStore[channelId] = null;
- delete promisesStore[channelId];
- });
-
- const { title, externalId, ownerUrls, channelUrl, vanityChannelUrl } = res.metadata.channelMetadataRenderer;
-
- const displayNameRes = { title, externalId, ownerUrls, channelUrl, vanityChannelUrl };
- displayNameCacheStore.set(channelId, displayNameRes);
-
- resolve(displayNameRes);
-
- }).catch(console.warn);
-
- }).catch(console.warn);
- }
-
- const dataChangedFuncStore = new WeakMap();
-
-
- const dataChangeFuncProducer = (dataChanged) => {
-
- return function () {
- let p = this.querySelector('#author-text[href^="/channel/"], #name[href^="/channel/"]');
- if (p && (this.data || 0).jkrgx !== 1) {
- p.classList.remove('jkrgx');
- }
- return dataChanged.apply(this, arguments)
- }
-
-
- }
-
- const domCheck = async (anchor) => {
-
- try {
-
- let channelHref = anchor.getAttribute('href');
- if (!channelHref) return;
- let parentNode = anchor.parentNode;
- while (parentNode instanceof Node) {
- if (typeof parentNode.is === 'string' && typeof parentNode.dataChanged === 'function') break;
- parentNode = parentNode.parentNode
- }
- if (parentNode instanceof Node && typeof parentNode.is === 'string' && typeof parentNode.dataChanged === 'function') { } else return;
- let authorText = (parentNode.data || 0).authorText;
- const currentDisplayed = (authorText||0).simpleText;
- if (typeof currentDisplayed !== 'string') return;
- if (!/^\s*@[a-zA-Z0-9_\-.]{3,30}\s*$/.test(currentDisplayed)) return;
- /* https://support.google.com/youtube/answer/11585688?hl=en&co=GENIE.Platform%3DAndroid
-
- Handle naming guidelines
-
- Is between 3-30 characters
- Is made up of alphanumeric characters (A–Z, a–z, 0–9)
- Your handle can also include: underscores (_), hyphens (-), dots (.)
- Is not URL-like or phone number-like
- Is not already being used
- Follows YouTube's Community Guidelines
-
- // auto handle - without dot (.)
-
- */
-
- let m = /\/channel\/([^/?#]+)/.exec(channelHref);
- if (!m || !m[1]) return;
-
- let oldDataChanged = parentNode.dataChanged;
- if (typeof oldDataChanged === 'function' && !oldDataChanged.jkrgx) {
- let newDataChanged = dataChangedFuncStore.get(oldDataChanged)
- if (!newDataChanged) {
- newDataChanged = dataChangeFuncProducer(oldDataChanged);
- newDataChanged.jkrgx = 1;
- dataChangedFuncStore.set(oldDataChanged, newDataChanged);
- }
- parentNode.dataChanged = newDataChanged;
- }
-
- const fetchResult = await getDisplayName(m[1]);
-
-
- const { title, externalId, ownerUrls, channelUrl, vanityChannelUrl } = fetchResult;
-
- if (anchor.getAttribute('href') !== `/channel/${externalId}`) return;
- const parentNodeData = parentNode.data
- if (parentNode.isAttached === true && parentNode.isConnected === true && typeof parentNodeData === 'object' && parentNodeData) {
-
-
- if (authorText.simpleText !== currentDisplayed) return;
- let currentDisplayTrimmed = currentDisplayed.trim();
- let match = false;
- if ((vanityChannelUrl || '').endsWith(`/${currentDisplayTrimmed}`)) {
- match = true;
- } else if ((ownerUrls || 0).length >= 1) {
- for (const ownerUrl of ownerUrls) {
- if ((ownerUrl || '').endsWith(`/${currentDisplayTrimmed}`)) {
- match = true;
- break;
- }
- }
- }
- let cSimpleText = ((parentNodeData.authorText || 0).simpleText || '');
- if (match && currentDisplayed !== title && cSimpleText === currentDisplayed) {
- let md = Object.assign({}, parentNodeData);
- let setBadge = false;
- if (((((md.authorCommentBadge || 0).authorCommentBadgeRenderer || 0).authorText || 0).simpleText || '').trim() === cSimpleText.trim()) {
- setBadge = true;
- }
- // parentNode.data = Object.assign({}, { jkrgx: 1 });
- md.authorText = Object.assign({}, md.authorText, { simpleText: title });
- if (setBadge) {
- md.authorCommentBadge = Object.assign({}, md.authorCommentBadge);
- md.authorCommentBadge.authorCommentBadgeRenderer = Object.assign({}, md.authorCommentBadge.authorCommentBadgeRenderer);
- md.authorCommentBadge.authorCommentBadgeRenderer.authorText = Object.assign({}, md.authorCommentBadge.authorCommentBadgeRenderer.authorText, { simpleText: title });
-
- }
- parentNode.data = Object.assign({}, md, { jkrgx: 1 });
- }
-
- }
- } catch (e) {
- console.warn(e);
- }
-
-
- }
-
- const domChecker = () => {
-
- for (const anchor of document.querySelectorAll('#author-text[href^="/channel/"]:not(.jkrgx), #name[href^="/channel/"]:not(.jkrgx)')) {
- anchor.classList.add('jkrgx');
- domCheck(anchor);
- }
-
- };
-
-
- /** @type {MutationObserver | null} */
- let domObserver = null;
-
- document.addEventListener('yt-page-data-fetched', function (evt) {
-
- const cfgData = (((window || 0).ytcfg || 0).data_ || 0);
- for (const key of ['INNERTUBE_API_KEY', 'INNERTUBE_CLIENT_VERSION']) {
- cfg[key] = cfgData[key];
- }
-
- if (!cfg['INNERTUBE_API_KEY']) return;
-
- if (!domObserver) {
- domObserver = new MutationObserver(domChecker);
- } else {
- domObserver.takeRecords();
- domObserver.disconnect();
- }
-
- domObserver.observe(evt.target || document.body, { childList: true, subtree: true });
- domChecker();
-
- });
-
-
- })();