- /*
-
- MIT License
-
- Copyright 2024 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 YouTube: Add Channel Name to Shorts Thumbnail
- // @namespace UserScript
- // @match https://www.youtube.com/*
- // @grant none
- // @version 0.2.3
- // @license MIT License
- // @author CY Fung
- // @description To add channel name to Shorts thumbnail
- // @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@8fac46500c5a916e6ed21149f6c25f8d1c56a6a3/library/ytZara.js
- // @run-at document-start
- // ==/UserScript==
-
-
-
- !window.TTP && (()=>{
- // credit to Benjamin Philipp
- // original source: https://greasyfork.org/en/scripts/433051-trusted-types-helper
-
- // --------------------------------------------------- Trusted Types Helper ---------------------------------------------------
-
- const overwrite_default = false; // If a default policy already exists, it might be best not to overwrite it, but to try and set a custom policy and use it to manually generate trusted types. Try at your own risk
- const prefix = `TTP`;
- var passThroughFunc = function(string, sink){
- return string; // Anything passing through this function will be returned without change
- }
- var TTPName = "passthrough";
- var TTP_default, TTP = {createHTML: passThroughFunc, createScript: passThroughFunc, createScriptURL: passThroughFunc}; // We can use TTP.createHTML for all our assignments even if we don't need or even have Trusted Types; this should make fallbacks and polyfills easy
- var needsTrustedHTML = false;
- function doit(){
- try{
- if(typeof window.isSecureContext !== 'undefined' && window.isSecureContext){
- if (window.trustedTypes && window.trustedTypes.createPolicy){
- needsTrustedHTML = true;
- if(trustedTypes.defaultPolicy){
- log("TT Default Policy exists");
- if(overwrite_default)
- TTP = window.trustedTypes.createPolicy("default", TTP);
- else
- TTP = window.trustedTypes.createPolicy(TTPName, TTP); // Is the default policy permissive enough? If it already exists, best not to overwrite it
- TTP_default = trustedTypes.defaultPolicy;
-
- log("Created custom passthrough policy, in case the default policy is too restrictive: Use Policy '" + TTPName + "' in var 'TTP':", TTP);
- }
- else{
- TTP_default = TTP = window.trustedTypes.createPolicy("default", TTP);
- }
- log("Trusted-Type Policies: TTP:", TTP, "TTP_default:", TTP_default);
- }
- }
- }catch(e){
- log(e);
- }
- }
-
- function log(...args){
- if("undefined" != typeof(prefix) && !!prefix)
- args = [prefix + ":", ...args];
- if("undefined" != typeof(debugging) && !!debugging)
- args = [...args, new Error().stack.replace(/^\s*(Error|Stack trace):?\n/gi, "").replace(/^([^\n]*\n)/, "\n")];
- console.log(...args);
- }
-
- doit();
-
- // --------------------------------------------------- Trusted Types Helper ---------------------------------------------------
-
- window.TTP = TTP;
-
- })();
-
- function createHTML(s) {
- if (typeof TTP !== 'undefined' && typeof TTP.createHTML === 'function') return TTP.createHTML(s);
- return s;
- }
-
- let trustHTMLErr = null;
- try {
- document.createElement('div').innerHTML = createHTML('1');
- } catch (e) {
- trustHTMLErr = e;
- }
-
- if (trustHTMLErr) {
- console.log(`trustHTMLErr`, trustHTMLErr);
- trustHTMLErr(); // exit userscript
- }
-
-
- // -----------------------------------------------------------------------------------------------------------------------------
-
-
-
- ((__CONTEXT__) => {
-
- const { Promise, fetch } = __CONTEXT__;
-
- class Mutex {
-
- constructor() {
- this.p = Promise.resolve()
- }
-
- /**
- * @param {(lockResolve: () => void)} f
- */
- lockWith(f) {
- this.p = this.p.then(() => new Promise(f).catch(console.warn))
- }
-
- }
-
-
- const addCSSProcess = () => {
-
- if (document.querySelector('style#ePCWh')) return;
-
-
- let style = document.createElement('style')
- style.id = 'ePCWh'
- style.textContent = `
-
- #metadata-line[ePCWh]::before {
- width: 100%;
- content: attr(ePCWh);
- display: block;
- max-width: 100%;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- }
-
-
- [ePCWu]::before {
- width: 100%;
- content: attr(ePCWu);
- display: block;
- max-width: 100%;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- }
-
- `;
- document.head.appendChild(style);
-
- }
-
- const { requestVideoInfoAsync } = (() => {
-
- const mutex = new Mutex();
-
- const resolvedValues = new Map();
-
- const createPromise = (videoId) => {
-
- return new Promise(resolve => {
-
- mutex.lockWith(lockResolve => {
-
- fetch(`/watch?v=${videoId}`, {
-
- "method": "GET",
- "mode": "same-origin",
- "credentials": "omit",
- referrerPolicy: "no-referrer",
- cache: "default",
- redirect: "error", // there shall be no redirection in this API request
- integrity: "",
- keepalive: false,
-
- "headers": {
- "Cache-Control": "public, max-age=900, stale-while-revalidate=1800",
- // refer "Cache-Control Use Case Examples" in https://www.koyeb.com/blog/using-cache-control-and-cdns-to-improve-performance-and-reduce-latency
- // seems YouTube RSS Feeds server insists its own Cache-Control.
-
- // "Content-Type": "text/xml; charset=UTF-8",
- "Accept-Encoding": "gzip, deflate, br", // YouTube Response - gzip
- // X-Youtube-Bootstrap-Logged-In: false,
- // X-Youtube-Client-Name: 1, // INNERTUBE_CONTEXT_CLIENT_NAME
- // X-Youtube-Client-Version: "2.20230622.06.00" // INNERTUBE_CONTEXT_CLIENT_VERSION
-
- "Accept": "text/html",
- "Pragma": ""
- }
-
- }).then(res => {
- lockResolve();
- return res.text();
- }).then(resText => {
-
- let wIdx2 = resText.indexOf('itemprop="author"');
- let wIdx1 = wIdx2 > 0 ? resText.lastIndexOf('<span', wIdx2) : -1;
- let wIdx3 = wIdx1 > 0 ? resText.indexOf('<\/span>', wIdx2) : -1;
- if (wIdx3 > 0) {
-
- let mText = resText.substring(wIdx1, wIdx3 + '<\/span>'.length);
- let template = document.createElement('template');
- template.innerHTML = createHTML(mText);
- let span = template.content.firstElementChild;
- if (span && span.nodeName === "SPAN") {
- let name = span.querySelector('link[itemprop="name"]')
- if (name) {
- name = name.getAttribute('content');
- if (name) {
- return name;
- }
- }
- }
- template.innerHTML = createHTML('');
- }
-
- return '';
-
- }).then(resolve);
-
- })
-
- });
-
- };
-
- const requestVideoInfoAsync = (videoId) => {
-
- let promise = resolvedValues.get(videoId);
- if (!promise) {
- promise = createPromise(videoId);
- resolvedValues.set(videoId, promise);
- }
- return promise;
- }
-
- return { requestVideoInfoAsync };
-
- })();
-
-
- const firstKey = (obj) => {
- for (const key in obj) {
- if (obj.hasOwnProperty(key)) return key;
- }
- return null;
- }
-
- const __addChannelName582__ = function (){
-
-
- let useOldModel = true;
- const cnt = this;
- const hostElement = this.hostElement || this;
- let nameToAdd = '';
- const data = cnt.data;
-
- const details = data && data.videoType && data.videoId ? ((this.$ || 0).details || 0) : null;
- const content = data && data.videoType && data.videoId ? null : ((this.$ || 0).content || 0);
-
- if (data && data.videoType === "REEL_VIDEO_TYPE_VIDEO" && typeof data.videoId === 'string') {
- const videoId = data.videoId;
- if (data.rsVideoId === videoId && typeof data.rsChannelName === 'string') {
- nameToAdd = data.rsChannelName;
-
- let ml = details ? HTMLElement.prototype.querySelector.call(details, '#details #metadata-line') : HTMLElement.prototype.querySelector.call(hostElement, 'ytd-reel-item-renderer #details #metadata-line');
- if (ml) {
- ml.setAttribute('ePCWh', nameToAdd);
- }
-
- } else {
- requestVideoInfoAsync(videoId).then(name => {
- cnt.data = Object.assign({}, cnt.data, { rsVideoId: videoId, rsChannelName: name });
- });
- }
-
-
-
- } else if (data && data.content && content instanceof Element){
-
-
-
- useOldModel = false;
- const mFirstKey = firstKey(data.content);
- const mFirstObj = data.content[mFirstKey];
-
- const rsVideoId = mFirstObj.rsVideoId;
- let videoId = '';
-
- if(mFirstObj.overlayMetadata && typeof mFirstObj.entityId == 'string' && (mFirstObj.inlinePlayerData||0).onVisible){
- const watchEndpoint = ((mFirstObj.inlinePlayerData||0).onVisible.innertubeCommand||0).watchEndpoint || 0;
- if(watchEndpoint && typeof (watchEndpoint.videoId||0) === 'string'){
- videoId = watchEndpoint.videoId;
- }
- }
-
-
-
- if(!videoId && mFirstObj.overlayMetadata && typeof mFirstObj.entityId == 'string' && (mFirstObj.onTap||0).innertubeCommand){
- const reelWatchEndpoint = ((mFirstObj.onTap||0).innertubeCommand||0).reelWatchEndpoint || 0;
- if(reelWatchEndpoint && typeof (reelWatchEndpoint.videoId||0) === 'string'){
- videoId = reelWatchEndpoint.videoId;
- }
- }
-
- if (videoId !== rsVideoId && videoId) {
-
- requestVideoInfoAsync(videoId).then(name => {
-
- const newFirstObject = Object.assign({}, cnt.data.content[mFirstKey], { rsVideoId: videoId, rsChannelName: name });
- const newContent = Object.assign({}, cnt.data.content, { [mFirstKey]: newFirstObject });
- cnt.data = Object.assign({}, cnt.data, { content: newContent });
- });
-
- } else if(rsVideoId === videoId && rsVideoId){
- nameToAdd = mFirstObj.rsChannelName;
- const subhead = HTMLElement.prototype.querySelector.call(content, '.ShortsLockupViewModelHostMetadataSubhead, .ShortsLockupViewModelHostOutsideMetadataSubhead');
- subhead.setAttribute('ePCWu', nameToAdd);
- }
-
- }
-
- if (!nameToAdd) {
- if (useOldModel) {
- const ml = details ? HTMLElement.prototype.querySelector.call(details, '#details #metadata-line') : HTMLElement.prototype.querySelector.call(hostElement, 'ytd-reel-item-renderer #details #metadata-line');
- if (ml) {
- ml.removeAttribute('ePCWh');
- }
- } else if (content) {
- const subhead = HTMLElement.prototype.querySelector.call(content, '[ePCWu]');
- if (subhead) {
- subhead.removeAttribute('ePCWu');
- }
- }
- }
-
- }
-
- ytZara.ytProtoAsync("ytd-rich-grid-slim-media").then((cProto) => {
- Promise.resolve().then(addCSSProcess);
- console.log(4717);
- cProto.__addChannelName582__ = __addChannelName582__;
- const onDataChanged = cProto.onDataChanged;
- cProto.onDataChanged = function () {
- const cnt = this;
- console.log(4719, 1);
- Promise.resolve().then(()=>{
- this.__addChannelName582__();
- }).catch(console.warn);
- return onDataChanged ? onDataChanged.apply(cnt, arguments) : void 0;
- };
-
- });
-
-
- const onVisiblePn = (cProto) => {
- Promise.resolve().then(addCSSProcess);
- console.log(4718);
- cProto.__addChannelName582__ = __addChannelName582__;
- const onVisible = cProto.onVisible;
- cProto.onVisible = function () {
- const cnt = this;
- console.log(4719, 2);
- Promise.resolve().then(()=>{
- this.__addChannelName582__();
- }).catch(console.warn);
- return onVisible ? onVisible.apply(cnt, arguments) : void 0;
- };
-
- };
-
- ytZara.ytProtoAsync("ytd-reel-item-renderer").then(onVisiblePn);
- ytZara.ytProtoAsync("ytd-rich-item-renderer").then(onVisiblePn);
-
-
- })({ Promise, fetch });