- /*
-
- 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 YouTube: Add Channel Name to Shorts Thumbnail
- // @namespace UserScript
- // @match https://www.youtube.com/*
- // @grant none
- // @version 0.1.7
- // @license MIT License
- // @author CY Fung
- // @description To add channel name to Shorts thumbnail
- // @require https://gf.qytechs.cn/scripts/465819-api-for-customelements-in-youtube/code/API%20for%20CustomElements%20in%20YouTube.js?version=1215161
- // @run-at document-start
- // ==/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))
- }
-
- }
-
-
- customYtElements.whenRegistered('ytd-rich-grid-slim-media', (proto) => {
-
- const mutex = new Mutex();
-
- const resolvedValues = new Map();
-
- Promise.resolve().then(() => {
-
- 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;
- }
- `;
- document.head.appendChild(style);
-
- });
-
- 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 = 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 = '';
- }
-
- return '';
-
- }).then(resolve);
-
- })
-
- });
-
- };
-
- proto.onDataChanged = ((onDataChanged) => {
-
- return function () {
- let nameToAdd = '';
- const data = this.data;
- 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;
- } else {
- let promise = resolvedValues.get(videoId);
- if (!promise) {
- promise = createPromise(videoId);
- resolvedValues.set(videoId, promise);
- }
- promise.then(name => {
- this.data = Object.assign({}, this.data, { rsVideoId: videoId, rsChannelName: name });
- });
- }
- }
-
- let ml = HTMLElement.prototype.querySelector.call(this, 'ytd-rich-grid-slim-media #details #metadata-line');
- if (ml) {
- if (nameToAdd) {
- ml.setAttribute('ePCWh', nameToAdd);
- } else if (ml.hasAttribute('ePCWh')) {
- ml.removeAttribute('ePCWh');
- }
- }
-
- return onDataChanged.apply(this, arguments);
- };
-
- })(proto.onDataChanged)
-
- });
-
- })({ Promise, fetch });