// ==UserScript==
// @name YouTube +
// @name:en YouTube +
// @namespace by
// @version 1.9
// @author diorhc
// @description Вкладки для информации, комментариев, видео, плейлиста и скачивание видео и другие функции ↴
// @description:en Tabview YouTube and Download and others features ↴
// @match https://*.youtube.com/*
// @match *://myactivity.google.com/*
// @include *://www.youtube.com/feed/history/*
// @include https://www.youtube.com
// @include *://*.youtube.com/**
// @exclude *://accounts.youtube.com/*
// @exclude *://www.youtube.com/live_chat_replay*
// @exclude *://www.youtube.com/persist_identity*
// @exclude /^https?://\w+\.youtube\.com\/live_chat.*$/
// @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @license MIT
// @grant GM_xmlhttpRequest
// ==/UserScript==
if (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy === null) {
let s = s => s;
trustedTypes.createPolicy('default', { createHTML: s, createScriptURL: s, createScript: s });
}
const defaultPolicy = (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy) || { createHTML: s => s };
function createHTML(s) {
return defaultPolicy.createHTML(s);
}
let trustHTMLErr = null;
try {
document.createElement('div').innerHTML = createHTML('1');
} catch (e) {
trustHTMLErr = e;
}
if (trustHTMLErr) {
console.log(`trustHTMLErr`, trustHTMLErr);
trustHTMLErr(); // exit userscript
}
// -----------------------------------------------------------------------------------------------------------------------------
const VAL_ROUNDED_A1 = 12;
const executionScript = (communicationKey) => {
const DEBUG_5084 = false;
if (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy === null) {
let s = s => s;
trustedTypes.createPolicy('default', { createHTML: s, createScriptURL: s, createScript: s });
}
const defaultPolicy = (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy) || { createHTML: s => s };
function createHTML(s) {
return defaultPolicy.createHTML(s);
}
let trustHTMLErr = null;
try {
document.createElement('div').innerHTML = createHTML('1');
} catch (e) {
trustHTMLErr = e;
}
if (trustHTMLErr) {
console.log(`trustHTMLErr`, trustHTMLErr);
trustHTMLErr(); // exit userscript
}
try {
let executionFinished = 0;
if (typeof CustomElementRegistry === 'undefined') return;
if (CustomElementRegistry.prototype.define000) return;
if (typeof CustomElementRegistry.prototype.define !== 'function') return;
/** @type {HTMLElement} */
const HTMLElement_ = HTMLElement.prototype.constructor;
/**
* @param {Element} elm
* @param {string} selector
* @returns {Element | null}
* */
const qsOne = (elm, selector) => {
return HTMLElement_.prototype.querySelector.call(elm, selector);
}
/**
* @param {Element} elm
* @param {string} selector
* @returns {NodeListOf<Element>}
* */
const qsAll = (elm, selector) => {
return HTMLElement_.prototype.querySelectorAll.call(elm, selector);
}
const pdsBaseDF = Object.getOwnPropertyDescriptors(DocumentFragment.prototype);
Object.defineProperties(DocumentFragment.prototype, {
replaceChildren000: pdsBaseDF.replaceChildren,
});
const pdsBaseNode = Object.getOwnPropertyDescriptors(Node.prototype);
// console.log(pdsBaseElement.setAttribute, pdsBaseElement.getAttribute)
Object.defineProperties(Node.prototype, {
appendChild000: pdsBaseNode.appendChild,
insertBefore000: pdsBaseNode.insertBefore
});
// class BaseElement extends Element{
// }
const pdsBaseElement = Object.getOwnPropertyDescriptors(Element.prototype);
// console.log(pdsBaseElement.setAttribute, pdsBaseElement.getAttribute)
Object.defineProperties(Element.prototype, {
setAttribute000: pdsBaseElement.setAttribute,
getAttribute000: pdsBaseElement.getAttribute,
hasAttribute000: pdsBaseElement.hasAttribute,
removeAttribute000: pdsBaseElement.removeAttribute,
querySelector000: pdsBaseElement.querySelector,
replaceChildren000: pdsBaseElement.replaceChildren
});
Element.prototype.setAttribute111 = function (p, v) {
v = `${v}`;
if (this.getAttribute000(p) === v) return;
this.setAttribute000(p, v)
}
Element.prototype.incAttribute111 = function (p) {
let v = +this.getAttribute000(p) || 0;
v = v > 1e9 ? v + 1 : 9;
this.setAttribute000(p, `${v}`);
return v;
}
Element.prototype.assignChildren111 = function (previousSiblings, node, nextSiblings) {
// assume all previousSiblings, node, and nextSiblings are on the page
// -> only remove triggering is needed
let nodeList = [];
for (let t = this.firstChild; t instanceof Node; t = t.nextSibling) {
if (t === node) continue;
nodeList.push(t);
}
inPageRearrange = true;
if (node.parentNode === this) {
let fm = new DocumentFragment();
if (nodeList.length > 0) {
fm.replaceChildren000(...nodeList);
// nodeList.length = 0;
}
// nodeList = null;
if (previousSiblings && previousSiblings.length > 0) {
fm.replaceChildren000(...previousSiblings);
this.insertBefore000(fm, node);
}
if (nextSiblings && nextSiblings.length > 0) {
fm.replaceChildren000(...nextSiblings);
this.appendChild000(fm);
}
fm.replaceChildren000();
fm = null;
} else {
if (!previousSiblings) previousSiblings = [];
if (!nextSiblings) nextSiblings = [];
this.replaceChildren000(...previousSiblings, node, ...nextSiblings);
}
inPageRearrange = false;
if (nodeList.length > 0) {
for (const t of nodeList) {
if (t instanceof Element && t.isConnected === false) t.remove(); // remove triggering
}
}
nodeList.length = 0;
nodeList = null;
}
// ==============================================================================================================================================================================================================================================================================
const DISABLE_FLAGS_SHADYDOM_FREE = true;
/**
*
* Minified Code from https://greasyfork.org/en/scripts/475632-ytconfighacks/code (ytConfigHacks)
* Date: 2024.04.17
* Minifier: https://www.toptal.com/developers/javascript-minifier
*
*/
(() => {
let e = "undefined" != typeof unsafeWindow ? unsafeWindow : this instanceof Window ?
this : window; if (!e._ytConfigHacks) {
let t = 4; class n extends Set {
add(e) {
if (t <= 0) return console.warn(
"yt.config_ is already applied on the page."); "function" == typeof e && super.add(e)
}
} let a = (async () => { })()
.constructor, i = e._ytConfigHacks = new n, l = () => { let t = e.ytcsi.originalYtcsi; t && (e.ytcsi = t, l = null) },
c = null, o = () => {
if (t >= 1) {
let n = (e.yt || 0).config_ || (e.ytcfg || 0).data_ || 0; if ("string" == typeof n.
INNERTUBE_API_KEY && "object" == typeof n.EXPERIMENT_FLAGS) for (let a of (--t <= 0 && l && l(), c = !0, i)) a(n)
}
}, f = 1,
d = t => {
if (t = t || e.ytcsi) return e.ytcsi = new Proxy(t, { get: (e, t, n) => "originalYtcsi" === t ? e : (o(), c && --f <= 0 && l && l(), e[t]) })
, !0
}; d() || Object.defineProperty(e, "ytcsi", {
get() { }, set: t => (t && (delete e.ytcsi, d(t)), !0), enumerable: !1, configurable: !0
}); let { addEventListener: s, removeEventListener: y } = Document.prototype; function r(t) {
o(),
t && e.removeEventListener("DOMContentLoaded", r, !1)
} new a(e => {
if ("undefined" != typeof AbortSignal) s.call(document,
"yt-page-data-fetched", e, { once: !0 }), s.call(document, "yt-navigate-finish", e, { once: !0 }), s.call(document, "spfdone", e,
{ once: !0 }); else {
let t = () => {
e(), y.call(document, "yt-page-data-fetched", t, !1), y.call(document, "yt-navigate-finish", t, !1),
y.call(document, "spfdone", t, !1)
}; s.call(document, "yt-page-data-fetched", t, !1), s.call(document, "yt-navigate-finish", t, !1),
s.call(document, "spfdone", t, !1)
}
}).then(o), new a(e => {
if ("undefined" != typeof AbortSignal) s.call(document, "yt-action", e,
{ once: !0, capture: !0 }); else { let t = () => { e(), y.call(document, "yt-action", t, !0) }; s.call(document, "yt-action", t, !0) }
}).then(o),
a.resolve().then(() => { "loading" !== document.readyState ? r() : e.addEventListener("DOMContentLoaded", r, !1) })
}
})();
let configOnce = false;
window._ytConfigHacks.add((config_) => {
if (configOnce) return;
configOnce = true;
const EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS || 0;
const EXPERIMENTS_FORCED_FLAGS = config_.EXPERIMENTS_FORCED_FLAGS || 0;
for (const flags of [EXPERIMENT_FLAGS, EXPERIMENTS_FORCED_FLAGS]) {
if (flags) {
// flags.kevlar_watch_metadata_refresh_no_old_secondary_data = false;
// flags.live_chat_overflow_hide_chat = false;
flags.web_watch_chat_hide_button_killswitch = false;
flags.web_watch_theater_chat = false; // for re-openable chat (ytd-watch-flexy's liveChatCollapsed is always undefined)
flags.suppress_error_204_logging = true;
flags.kevlar_watch_grid = false; // A/B testing for watch grid
if (DISABLE_FLAGS_SHADYDOM_FREE) {
flags.enable_shadydom_free_scoped_node_methods = false;
flags.enable_shadydom_free_scoped_query_methods = false;
flags.enable_shadydom_free_scoped_readonly_properties_batch_one = false;
flags.enable_shadydom_free_parent_node = false;
flags.enable_shadydom_free_children = false;
flags.enable_shadydom_free_last_child = false;
}
}
}
});
// ==============================================================================================================================================================================================================================================================================
/* globals WeakRef:false */
/** @type {(o: Object | null) => WeakRef | null} */
const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
/** @type {(wr: Object | null) => Object | null} */
const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
/** @type {globalThis.PromiseConstructor} */
const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
const delayPn = delay => new Promise((fn => setTimeout(fn, delay)));
const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
const setTimeout_ = setTimeout.bind(window);
const PromiseExternal = ((resolve_, reject_) => {
const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
return class PromiseExternal extends Promise {
constructor(cb = h) {
super(cb);
if (cb === h) {
/** @type {(value: any) => void} */
this.resolve = resolve_;
/** @type {(reason?: any) => void} */
this.reject = reject_;
}
}
};
})();
// ------------------------------------------------------------------------ nextBrowserTick ------------------------------------------------------------------------
var nextBrowserTick = void 0 !== nextBrowserTick && nextBrowserTick.version >= 2 ? nextBrowserTick : (() => {
"use strict"; const e = "undefined" != typeof self ? self : "undefined" != typeof global ? global : this;
let t = !0; if (! function n(s) {
return s ? t = !1 : e.postMessage && !e.importScripts && e.addEventListener ? (e.addEventListener("message", n, !1), e.postMessage("$$$", "*"), e.removeEventListener("message", n, !1), t) : void 0
}()) return void console.warn("Your browser environment cannot use nextBrowserTick");
const n = (async () => { })().constructor; let s = null;
const o = new Map, { floor: r, random: i } = Math; let l;
do { l = `$$nextBrowserTick$$${(i() + 8).toString().slice(2)}$$` } while (l in e); const a = l,
c = a.length + 9;
e[a] = 1;
e.addEventListener("message", (e => {
if (0 !== o.size) {
const t = (e || 0).data; if ("string" == typeof t && t.length === c && e.source === (e.target || 1)) {
const e = o.get(t);
e && ("p" === t[0] && (s = null), o.delete(t), e())
}
}
}), !1); const d = (t = o) => {
if (t === o) {
if (s) return s; let t;
do { t = `p${a}${r(314159265359 * i() + 314159265359).toString(36)}` } while (o.has(t)); return s = new n((e => { o.set(t, e) })), e.postMessage(t, "*"), t = null, s
} {
let n;
do { n = `f${a}${r(314159265359 * i() + 314159265359).toString(36)}` } while (o.has(n));
o.set(n, t), e.postMessage(n, "*")
}
}; return d.version = 2, d
})();
// ------------------------------------------------------------------------ nextBrowserTick ------------------------------------------------------------------------
const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;
const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;
class Attributer {
constructor(list) {
this.list = list;
this.flag = 0;
}
makeString() {
let k = 1;
let s = '';
let i = 0;
while (this.flag >= k) {
if (this.flag & k) {
s += this.list[i];
}
i++;
k <<= 1;
}
return s;
}
}
const mLoaded = new Attributer('icp');
const wrSelfMap = new WeakMap();
/** @type {Object.<string, Element | null>} */
const elements = new Proxy({
related: null,
comments: null,
infoExpander: null,
}, {
get(target, prop) {
return kRef(target[prop]);
},
set(target, prop, value) {
if (value) {
let wr = wrSelfMap.get(value);
if (!wr) {
wr = mWeakRef(value);
wrSelfMap.set(value, wr);
}
target[prop] = wr;
} else {
target[prop] = null;
}
return true;
}
});
const getMainInfo = () => {
const infoExpander = elements.infoExpander;
if (!infoExpander) return null;
const mainInfo = infoExpander.matches('[tyt-main-info]') ? infoExpander : infoExpander.querySelector000('[tyt-main-info]');
return mainInfo || null;
}
const asyncWrap = (asyncFn) => {
return () => {
Promise.resolve().then(asyncFn);
};
}
let pageType = null;
let pageLang = 'en';
const langWords = {
'en': {
//'share':'Share',
'info': 'Info',
'videos': 'Videos',
'playlist': 'Playlist'
},
'jp': {
//'share':'共有',
'info': '情報',
'videos': '動画',
'playlist': '再生リスト'
},
'tw': {
//'share':'分享',
'info': '資訊',
'videos': '影片',
'playlist': '播放清單'
},
'cn': {
//'share':'分享',
'info': '资讯',
'videos': '视频',
'playlist': '播放列表'
},
'du': {
//'share':'Teilen',
'info': 'Info',
'videos': 'Videos',
'playlist': 'Playlist'
},
'fr': {
//'share':'Partager',
'info': 'Info',
'videos': 'Vidéos',
'playlist': 'Playlist'
},
'kr': {
//'share':'공유',
'info': '정보',
'videos': '동영상',
'playlist': '재생목록'
},
'ru': {
//'share':'Поделиться',
'info': 'Описание',
'videos': 'Видео',
'playlist': 'Плейлист'
}
};
const svgComments = `<path d="M80 27H12A12 12 90 0 0 0 39v42a12 12 90 0 0 12 12h12v20a2 2 90 0 0 3.4 2L47 93h33a12
12 90 0 0 12-12V39a12 12 90 0 0-12-12zM20 47h26a2 2 90 1 1 0 4H20a2 2 90 1 1 0-4zm52 28H20a2 2 90 1 1 0-4h52a2 2 90
1 1 0 4zm0-12H20a2 2 90 1 1 0-4h52a2 2 90 1 1 0 4zm36-58H40a12 12 90 0 0-12 12v6h52c9 0 16 7 16 16v42h0v4l7 7a2 2 90
0 0 3-1V71h2a12 12 90 0 0 12-12V17a12 12 90 0 0-12-12z"/>`.trim();
const svgVideos = `<path d="M89 10c0-4-3-7-7-7H7c-4 0-7 3-7 7v70c0 4 3 7 7 7h75c4 0 7-3 7-7V10zm-62 2h13v10H27V12zm-9
66H9V68h9v10zm0-56H9V12h9v10zm22 56H27V68h13v10zm-3-25V36c0-2 2-3 4-2l12 8c2 1 2 4 0 5l-12 8c-2 1-4 0-4-2zm25
25H49V68h13v10zm0-56H49V12h13v10zm18 56h-9V68h9v10zm0-56h-9V12h9v10z"/>`.trim();
const svgInfo = `<path d="M30 0C13.3 0 0 13.3 0 30s13.3 30 30 30 30-13.3 30-30S46.7 0 30 0zm6.2 46.6c-1.5.5-2.6
1-3.6 1.3a10.9 10.9 0 0 1-3.3.5c-1.7 0-3.3-.5-4.3-1.4a4.68 4.68 0 0 1-1.6-3.6c0-.4.2-1 .2-1.5a20.9 20.9 90 0 1
.3-2l2-6.8c.1-.7.3-1.3.4-1.9a8.2 8.2 90 0 0 .3-1.6c0-.8-.3-1.4-.7-1.8s-1-.5-2-.5a4.53 4.53 0 0 0-1.6.3c-.5.2-1
.2-1.3.4l.6-2.1c1.2-.5 2.4-1 3.5-1.3s2.3-.6 3.3-.6c1.9 0 3.3.6 4.3 1.3s1.5 2.1 1.5 3.5c0 .3 0 .9-.1 1.6a10.4 10.4
90 0 1-.4 2.2l-1.9 6.7c-.2.5-.2 1.1-.4 1.8s-.2 1.3-.2 1.6c0 .9.2 1.6.6 1.9s1.1.5 2.1.5a6.1 6.1 90 0 0 1.5-.3 9 9 90
0 0 1.4-.4l-.6 2.2zm-3.8-35.2a1 1 0 010 8.6 1 1 0 010-8.6z"/>`.trim();
const svgPlayList = `<path d="M0 3h12v2H0zm0 4h12v2H0zm0 4h8v2H0zm16 0V7h-2v4h-4v2h4v4h2v-4h4v-2z"/>`.trim();
const svgDiag1 = `<svg stroke="currentColor" fill="none"><path d="M8 2h2v2M7 5l3-3m-6 8H2V8m0 2l3-3"/></svg>`;
const svgDiag2 = `<svg stroke="currentColor" fill="none"><path d="M7 3v2h2M7 5l3-3M5 9V7H3m-1 3l3-3"/></svg>`;
const getGMT = () => {
let m = new Date('2023-01-01T00:00:00Z');
return m.getDate() === 1 ? `+${m.getHours()}` : `-${24 - m.getHours()}`;
};
function getWord(tag) {
return langWords[pageLang][tag] || langWords['en'][tag] || '';
}
const svgElm = (w, h, vw, vh, p, m) => `<svg${m ? ` class=${m}` : ''} width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`
let hiddenTabsByUserCSS = 0;
function getTabsHTML() {
const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}<span>${getWord('videos')}</span>`;
const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}<span>${getWord('info')}</span>`;
const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}<span>${getWord('playlist')}</span>`;
let str1 = `
<paper-ripple class="style-scope yt-icon-button">
<div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
<div id="waves" class="style-scope paper-ripple"></div>
</paper-ripple>
`;
let str_fbtns = `
<div class="font-size-right">
<div class="font-size-btn font-size-plus" tyt-di="8rdLQ">
<svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
<path d="M12 25H38M25 12V38"/>
</svg>
</div><div class="font-size-btn font-size-minus" tyt-di="8rdLQ">
<svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
<path d="M12 25h26"/>
</svg>
</div>
</div>
`.replace(/[\r\n]+/g, '');
const str_tabs = [
`<a id="tab-btn1" tyt-di="q9Kjc" tyt-tab-content="#tab-info" class="tab-btn${(hiddenTabsByUserCSS & 1) === 1 ? ' tab-btn-hidden' : ''}">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
`<a id="tab-btn3" tyt-di="q9Kjc" tyt-tab-content="#tab-comments" class="tab-btn${(hiddenTabsByUserCSS & 2) === 2 ? ' tab-btn-hidden' : ''}">${svgElm(16, 16, 120, 120, svgComments)}<span id="tyt-cm-count"></span>${str1}${str_fbtns}</a>`,
`<a id="tab-btn4" tyt-di="q9Kjc" tyt-tab-content="#tab-videos" class="tab-btn${(hiddenTabsByUserCSS & 4) === 4 ? ' tab-btn-hidden' : ''}">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
`<a id="tab-btn5" tyt-di="q9Kjc" tyt-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`
].join('');
let addHTML = `
<div id="right-tabs">
<tabview-view-pos-thead></tabview-view-pos-thead>
<header>
<div id="material-tabs">
${str_tabs}
</div>
</header>
<div class="tab-content">
<div id="tab-info" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
<div id="tab-comments" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
<div id="tab-videos" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
<div id="tab-list" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
</div>
</div>
`;
return addHTML;
}
function getLang() {
let lang = 'en';
let htmlLang = ((document || 0).documentElement || 0).lang || '';
switch (htmlLang) {
case 'en':
case 'en-GB':
lang = 'en';
break;
case 'de':
case 'de-DE':
lang = 'du';
break;
case 'fr':
case 'fr-CA':
case 'fr-FR':
lang = 'fr';
break;
case 'zh-Hant':
case 'zh-Hant-HK':
case 'zh-Hant-TW':
lang = 'tw';
break;
case 'zh-Hans':
case 'zh-Hans-CN':
lang = 'cn';
break;
case 'ja':
case 'ja-JP':
lang = 'jp';
break;
case 'ko':
case 'ko-KR':
lang = 'kr';
break;
case 'ru':
case 'ru-RU':
lang = 'ru';
break;
default:
lang = 'en';
}
return lang;
}
function getLangForPage() {
let lang = getLang();
if (langWords[lang]) pageLang = lang; else pageLang = 'en';
}
/** @type {Object.<string, number>} */
const _locks = {}
const lockGet = new Proxy(_locks,
{
get(target, prop) {
return (target[prop] || 0);
},
set(target, prop, val) {
return true;
}
}
);
const lockSet = new Proxy(_locks,
{
get(target, prop) {
if (target[prop] > 1e9) target[prop] = 9;
return (target[prop] = (target[prop] || 0) + 1);
},
set(target, prop, val) {
return true;
}
}
);
// note: xxxxxxxxxAsyncLock is not expected for calling multiple time in a short period.
// it is just to split the process into microTasks.
const videosElementProvidedPromise = new PromiseExternal();
const navigateFinishedPromise = new PromiseExternal();
let isRightTabsInserted = false;
const rightTabsProvidedPromise = new PromiseExternal();
const infoExpanderElementProvidedPromise = new PromiseExternal();
const cmAttr = document.createComment('1');
const cmAttrStack = [];
const cmAttrStackPush = (f) => {
cmAttrStack.push(f);
cmAttr.data = `${(cmAttr.data & 7) + 1}`;
}
const cmAttrObs = new MutationObserver(() => {
const l = cmAttrStack.length;
for (let i = 0; i < l; i++) {
cmAttrStack[i]();
}
});
cmAttrObs.observe(cmAttr, { characterData: true });
const funcCanCollapse = function (s) {
// if (!s) return;
const content = this.content || this.$.content;
this.canToggle = this.shouldUseNumberOfLines && (this.alwaysCollapsed || this.collapsed || (this.isToggled === false))
? this.alwaysToggleable || this.isToggled || (content && content.offsetHeight < content.scrollHeight)
: this.alwaysToggleable || this.isToggled || (content && content.scrollHeight > this.collapsedHeight)
};
const aoChatAttrChangeFn = async (lockId) => {
if (lockGet['aoChatAttrAsyncLock'] !== lockId) return;
const chatElm = elements.chat;
const ytdFlexyElm = elements.flexy;
// console.log(1882, chatElm, ytdFlexyElm)
if (chatElm && ytdFlexyElm) {
const isChatCollapsed = chatElm.hasAttribute000('collapsed');
if (isChatCollapsed) {
ytdFlexyElm.setAttribute111('tyt-chat-collapsed', '')
} else {
ytdFlexyElm.removeAttribute000('tyt-chat-collapsed')
}
ytdFlexyElm.setAttribute111('tyt-chat', isChatCollapsed ? '-' : '+');
}
};
// const aoInfoAttrChangeFn = async (lockId) => {
// if (lockGet['aoInfoAttrAsyncLock'] !== lockId) return;
// };
// const zoInfoAttrChangeFn = async (lockId) => {
// if (lockGet['zoInfoAttrAsyncLock'] !== lockId) return;
// };
const aoPlayListAttrChangeFn = async (lockId) => {
if (lockGet['aoPlayListAttrAsyncLock'] !== lockId) return;
const playlistElm = elements.playlist;
const ytdFlexyElm = elements.flexy;
// console.log(1882, chatElm, ytdFlexyElm)
if (playlistElm && ytdFlexyElm) {
if (playlistElm.hasAttribute000('collapsed')) {
ytdFlexyElm.removeAttribute000('tyt-playlist-expanded')
} else {
ytdFlexyElm.setAttribute111('tyt-playlist-expanded', '')
}
} else if (ytdFlexyElm) {
ytdFlexyElm.removeAttribute000('tyt-playlist-expanded')
}
};
const aoChat = new MutationObserver(() => {
Promise.resolve(lockSet['aoChatAttrAsyncLock']).then(aoChatAttrChangeFn).catch(console.warn);
});
// const aoInfo = new MutationObserver(()=>{
// Promise.resolve(lockSet['aoInfoAttrAsyncLock']).then(aoInfoAttrChangeFn).catch(console.warn);
// });
// const zoInfo = new MutationObserver(()=>{
// Promise.resolve(lockSet['zoInfoAttrAsyncLock']).then(zoInfoAttrChangeFn).catch(console.warn);
// });
const aoPlayList = new MutationObserver(() => {
Promise.resolve(lockSet['aoPlayListAttrAsyncLock']).then(aoPlayListAttrChangeFn).catch(console.warn);
});
const aoComment = new MutationObserver(async (mutations) => {
const commentsArea = elements.comments;
const ytdFlexyElm = elements.flexy;
//tyt-comments-video-id //tyt-comments-data-status // hidden
if (!commentsArea) return;
let bfHidden = false;
let bfCommentsVideoId = false;
let bfCommentDisabled = false;
for (const mutation of mutations) {
if (mutation.attributeName === 'hidden' && mutation.target === commentsArea) {
bfHidden = true;
} else if (mutation.attributeName === 'tyt-comments-video-id' && mutation.target === commentsArea) {
bfCommentsVideoId = true;
} else if (mutation.attributeName === 'tyt-comments-data-status' && mutation.target === commentsArea) {
bfCommentDisabled = true;
}
}
if (bfHidden) {
if (!commentsArea.hasAttribute000('hidden')) {
Promise.resolve(commentsArea).then(eventMap['settingCommentsVideoId']).catch(console.warn);
}
Promise.resolve(lockSet['removeKeepCommentsScrollerLock']).then(removeKeepCommentsScroller).catch(console.warn);
}
if ((bfHidden || bfCommentsVideoId || bfCommentDisabled) && ytdFlexyElm) {
const commentsDataStatus = +commentsArea.getAttribute000('tyt-comments-data-status');
if (commentsDataStatus === 2) {
ytdFlexyElm.setAttribute111('tyt-comment-disabled', '')
} else if (commentsDataStatus === 1) {
ytdFlexyElm.removeAttribute000('tyt-comment-disabled')
}
Promise.resolve(lockSet['checkCommentsShouldBeHiddenLock']).then(eventMap['checkCommentsShouldBeHidden']).catch(console.warn);
const lockId = lockSet['rightTabReadyLock01'];
await rightTabsProvidedPromise.then();
if (lockGet['rightTabReadyLock01'] !== lockId) return;
if (elements.comments !== commentsArea) return;
if (commentsArea.isConnected === false) return;
// console.log(7932, 'comments');
if (commentsArea.closest('#tab-comments')) {
const shouldTabVisible = !commentsArea.closest('[hidden]');
document.querySelector('[tyt-tab-content="#tab-comments"]').classList.toggle('tab-btn-hidden', !shouldTabVisible);
}
}
});
const ioComment = new IntersectionObserver((entries) => {
for (const entry of entries) {
const target = entry.target;
const cnt = insp(target);
if (entry.isIntersecting && target instanceof HTMLElement_ && typeof cnt.calculateCanCollapse === 'function') {
lockSet['removeKeepCommentsScrollerLock'];
cnt.calculateCanCollapse(true);
target.setAttribute111('io-intersected', '');
const ytdFlexyElm = elements.flexy;
if (ytdFlexyElm && !ytdFlexyElm.hasAttribute000('keep-comments-scroller')) {
ytdFlexyElm.setAttribute111('keep-comments-scroller', '');
}
} else if (target.hasAttribute000('io-intersected')) {
target.removeAttribute000('io-intersected');
}
}
}, {
threshold: [0],
rootMargin: "32px" // enlarging viewport for getting intersection earlier
});
let bFixForResizedTabLater = false;
let lastRoRightTabsWidth = 0;
const roRightTabs = new ResizeObserver((entries) => {
const entry = entries[entries.length - 1];
const width = Math.round(entry.borderBoxSize.inlineSize);
if (lastRoRightTabsWidth !== width) {
lastRoRightTabsWidth = width;
if ((tabAStatus & 2) === 2) {
bFixForResizedTabLater = false;
Promise.resolve(1).then(eventMap['fixForTabDisplay']);
} else {
bFixForResizedTabLater = true;
}
}
// console.log('resize')
});
const switchToTab = (activeLink) => {
if (typeof activeLink === 'string') {
activeLink = document.querySelector(`a[tyt-tab-content="${activeLink}"]`) || null;
}
const ytdFlexyElm = elements.flexy;
const links = document.querySelectorAll('#material-tabs a[tyt-tab-content]');
//console.log(701, activeLink)
for (const link of links) {
const content = document.querySelector(link.getAttribute000('tyt-tab-content'));
if (link && content) {
if (link !== activeLink) {
link.classList.remove("active");
content.classList.add("tab-content-hidden");
if (!content.hasAttribute000("tyt-hidden")) {
content.setAttribute111("tyt-hidden", ""); // for https://greasyfork.org/en/scripts/456108
}
} else {
link.classList.add("active");
if (content.hasAttribute000("tyt-hidden")) {
content.removeAttribute000("tyt-hidden"); // for https://greasyfork.org/en/scripts/456108
}
content.classList.remove("tab-content-hidden");
}
}
}
const switchingTo = activeLink ? activeLink.getAttribute000('tyt-tab-content') : '';
if (switchingTo) {
lastTab = lastPanel = switchingTo
}
if (ytdFlexyElm.getAttribute000('tyt-chat') === '') ytdFlexyElm.removeAttribute000('tyt-chat');
ytdFlexyElm.setAttribute111('tyt-tab', switchingTo);
if (switchingTo) {
bFixForResizedTabLater = false;
Promise.resolve(0).then(eventMap['fixForTabDisplay']);
}
}
let tabAStatus = 0;
const calculationFn = (r = 0, flag) => {
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) return r;
if (flag & 1) {
r |= 1;
if (!ytdFlexyElm.hasAttribute000('theater')) r -= 1;
}
if (flag & 2) {
r |= 2;
if (!ytdFlexyElm.getAttribute000('tyt-tab')) r -= 2;
}
if (flag & 4) {
r |= 4;
if (ytdFlexyElm.getAttribute000('tyt-chat') !== '-') r -= 4;
}
if (flag & 8) {
r |= 8;
if (ytdFlexyElm.getAttribute000('tyt-chat') !== '+') r -= 8;
}
if (flag & 16) {
r |= 16;
if (!ytdFlexyElm.hasAttribute000('is-two-columns_')) r -= 16;
}
if (flag & 32) {
r |= 32;
if (!ytdFlexyElm.hasAttribute000('tyt-egm-panel_')) r -= 32;
}
if (flag & 64) {
r |= 64;
if (!document.fullscreenElement) r -= 64;
}
if (flag & 128) {
r |= 128;
if (!ytdFlexyElm.hasAttribute000('tyt-playlist-expanded')) r -= 128;
}
return r;
}
function isTheater() {
const ytdFlexyElm = elements.flexy;
return (ytdFlexyElm && ytdFlexyElm.hasAttribute000('theater'))
}
function ytBtnSetTheater() {
if (!isTheater()) {
const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
if (sizeBtn) sizeBtn.click();
}
}
function ytBtnCancelTheater() {
if (isTheater()) {
const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
if (sizeBtn) sizeBtn.click();
}
}
function ytBtnExpandChat() {
let button = document.querySelector('ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button')
if (button) {
button =
button.querySelector000('div.yt-spec-touch-feedback-shape') ||
button.querySelector000('ytd-toggle-button-renderer');
if (button) button.click();
}
}
function ytBtnCollapseChat() {
let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button')
if (button) {
button =
button.querySelector000('div.yt-spec-touch-feedback-shape') ||
button.querySelector000('ytd-toggle-button-renderer');
if (button) button.click();
}
}
function ytBtnEgmPanelCore(arr) {
if (!arr) return
if (!('length' in arr)) arr = [arr]
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) return;
let actions = []
for (const entry of arr) {
if (!entry) continue;
let panelId = entry.panelId
let toHide = entry.toHide
let toShow = entry.toShow
if (toHide === true && !toShow) {
actions.push({
"changeEngagementPanelVisibilityAction": {
"targetId": panelId,
"visibility": "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"
}
})
} else if (toShow === true && !toHide) {
actions.push({
"showEngagementPanelEndpoint": {
"panelIdentifier": panelId
}
})
}
if (actions.length > 0) {
const cnt = insp(ytdFlexyElm);
cnt.resolveCommand(
{
"signalServiceEndpoint": {
"signal": "CLIENT_SIGNAL",
"actions": actions
}
},
{},
false);
}
actions = null;
}
}
/*
function ytBtnCloseEngagementPanel( s) {
//ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
let panelId = s.getAttribute('target-id')
scriptletDeferred.debounce(() => {
document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
detail: {
panelId,
toHide: true
}
}))
})
}
function ytBtnCloseEngagementPanels() {
if (isEngagementPanelExpanded()) {
for (const s of document.querySelectorAll(
`ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
)) {
if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
}
}
}
*/
function ytBtnCloseEngagementPanels() {
const actions = [];
for (const panelElm of document.querySelectorAll(
`ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
)) {
if (panelElm.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED" && !panelElm.closest('[hidden]')) {
actions.push({
panelId: panelElm.getAttribute000('target-id'),
toHide: true
});
}
}
ytBtnEgmPanelCore(actions);
}
function ytBtnOpenPlaylist() {
const cnt = insp(elements.playlist);
if (cnt && typeof cnt.collapsed === 'boolean') {
cnt.collapsed = false;
}
}
function ytBtnClosePlaylist() {
const cnt = insp(elements.playlist);
if (cnt && typeof cnt.collapsed === 'boolean') {
cnt.collapsed = true;
}
}
const updateChatLocation498 = function () {
/*
updateChatLocation: function() {
if (this.is !== "ytd-watch-grid" && y("web_watch_theater_chat")) {
var a = T(this.hostElement).querySelector("#chat-container")
, b = this.theater && (!this.fullscreen || y("web_watch_fullscreen_panels"));
this.watchWhileWindowSizeSufficient && this.liveChatPresentAndExpanded && b ? y("web_watch_theater_chat_beside_player") ? (b = T(this.hostElement).querySelector("#panels-full-bleed-container"),
(a == null ? void 0 : a.parentElement) !== b && b.append(a),
this.panelsBesidePlayer = !0) : y("web_watch_theater_fixed_chat") && (b = T(this.hostElement).querySelector("#columns"),
(a == null ? void 0 : a.parentElement) !== b && b.append(a),
this.fixedPanels = !0) : (y("web_watch_theater_chat_beside_player") ? this.panelsBesidePlayer = !1 : y("web_watch_theater_fixed_chat") && (this.fixedPanels = !1),
b = T(this.hostElement).querySelector("#playlist"),
a && b ? Fh(a, b) : Gm(new zk("Missing element when updating chat location",{
"chatContainer defined": !!a,
"playlist defined": !!b
})));
this.updatePageMediaQueries();
this.schedulePlayerSizeUpdate_()
}
},
*/
// console.log('updateChatLocation498')
if (this.is !== "ytd-watch-grid") {
this.updatePageMediaQueries();
this.schedulePlayerSizeUpdate_()
}
}
const mirrorNodeWS = new WeakMap();
/*
const infoFix = () => {
const infoExpander = elements.infoExpander;
const ytdFlexyElm = elements.flexy;
if (!infoExpander || !ytdFlexyElm) return;
console.log(386, infoExpander, infoExpander.matches('#tab-info > [class]'))
if (!infoExpander.matches('#tab-info > [class]')) return;
// const elms = [...document.querySelectorAll('ytd-watch-metadata.ytd-watch-flexy div[slot="extra-content"], ytd-watch-metadata.ytd-watch-flexy ytd-metadata-row-container-renderer')].filter(elm=>{
// if(elm.parentNode.closest('div[slot="extra-content"], ytd-metadata-row-container-renderer')) return false;
// return true;
// });
const requireElements = [...document.querySelectorAll('ytd-watch-metadata.ytd-watch-flexy div[slot="extra-content"] > *, ytd-watch-metadata.ytd-watch-flexy #extra-content > *')].filter(elm => {
return typeof elm.is == 'string'
}).map(elm => {
const is = elm.is;
while (elm instanceof HTMLElement_) {
const q = [...elm.querySelectorAll(is)].filter(e => insp(e).data);
if (q.length >= 1) return q[0];
elm = elm.parentNode;
}
}).filter(elm => !!elm && typeof elm.is === 'string');
console.log(requireElements)
const source = requireElements.map(entry=>({
data: insp(entry).data,
tag: insp(entry).is,
elm: entry
}))
if (!document.querySelector('noscript#aythl')) {
const noscript = document.createElement('noscript')
noscript.id = 'aythl';
ytdFlexyElm.insertBefore000(noscript, ytdFlexyElm.firstChild);
}
const noscript = document.querySelector('noscript#aythl');
const clones = new Set();
for (const {data, tag, elm} of source) {
// const cloneNode = document.createElement(tag);
let cloneNode = elm.cloneNode(true);
// noscript.appendChild(cloneNode);
// insp(cloneNode).data = null;
insp(cloneNode).data = data;
source.clone = cloneNode;
clones.add(cloneNode);
}
// const elms = [...document.querySelectorAll('ytd-watch-metadata.ytd-watch-flexy div[slot="extra-content"]')].filter(elm => {
// if (elm.parentNode.closest('div[slot="extra-content"], ytd-metadata-row-container-renderer')) return false;
// return true;
// });
// let arr = [];
// for(const elm of elms){
// if(elm.hasAttribute('slot')) arr.push(...elm.childNodes);
// else arr.push(elm);
// }
// arr = arr.filter(e=>e && e.nodeType === 1);
// console.log(386,arr)
// const clones = arr.map(e=>e.cloneNode(true));
// for(let node = infoExpander.nextSibling; node instanceof Node; node = node.nextSibling) node.remove();
// infoExpander.parentNode.assignChildren111(null, infoExpander, [...clones]);
let removal = [];
for(let node = infoExpander.nextSibling; node instanceof Node; node = node.nextSibling)removal.push(node);
for(const node of removal) node.remove();
for(const node of clones) infoExpander.parentNode.appendChild(node);
for (const {data, tag, elm, clone} of source) {
insp(clone).data = null;
insp(clone).data = data;
}
// console.log(infoExpander.parentNode.childNodes)
}
*/
const dummyNode = document.createElement('noscript');
// const __j4838__ = Symbol();
const __j4836__ = Symbol();
const __j5744__ = Symbol(); // original element
const __j5733__ = Symbol(); // __lastChanged__
const monitorDataChangedByDOMMutation = async function (mutations) {
const nodeWR = this;
const node = kRef(nodeWR);
if (!node) return;
const cnt = insp(node);
const __lastChanged__ = cnt[__j5733__];
const val = cnt.data ? (cnt.data[__j4836__] || 1) : 0;
if (__lastChanged__ !== val) {
cnt[__j5733__] = val > 0 ? (cnt.data[__j4836__] = Date.now()) : 0;
await Promise.resolve(); // required for making sufficient delay for data rendering
attributeInc(node, 'tyt-data-change-counter'); // next macro task
}
}
const moChangeReflection = function (mutations) {
const nodeWR = this;
const node = kRef(nodeWR);
if (!node) return;
const originElement = kRef(node[__j5744__] || null) || null;
if (!originElement) return;
const cnt = insp(node);
const oriCnt = insp(originElement);
if (mutations) {
let bfDataChangeCounter = false;
for (const mutation of mutations) {
if (mutation.attributeName === 'tyt-clone-refresh-count' && mutation.target === originElement) {
bfDataChangeCounter = true;
} else if (mutation.attributeName === 'tyt-data-change-counter' && mutation.target === originElement) {
bfDataChangeCounter = true;
}
}
if (bfDataChangeCounter && oriCnt.data) {
node.replaceWith(dummyNode);
cnt.data = Object.assign({}, oriCnt.data);
dummyNode.replaceWith(node);
}
}
}
/*
const moChangeReflection = async function (mutations) {
const nodeWR = this;
const node = kRef(nodeWR);
if (!node) return;
const originElement = kRef(node[__j5744__] || null) || null;
if (!originElement) return;
const cnt = insp(node);
const oriCnt = insp(originElement);
if(mutations){
let bfDataChangeCounter = false;
for (const mutation of mutations) {
if (mutation.attributeName === 'tyt-data-change-counter' && mutation.target === originElement) {
bfDataChangeCounter = true;
}
}
if(bfDataChangeCounter && oriCnt.data){
node.replaceWith(dummyNode);
cnt.data = Object.assign({}, oriCnt.data);
dummyNode.replaceWith(node);
}
}
// console.log(8348, originElement)
if (cnt.isAttached === false) {
// do nothing
// don't call infoFix() as it shall be only called in ytd-expander::attached and yt-navigate-finish
} else if (oriCnt.isAttached === false && cnt.isAttached === true) {
if (node.isConnected && node.parentNode instanceof HTMLElement_) {
node.parentNode.removeChild(node);
} else {
node.remove();
}
if (oriCnt.data !== null) {
cnt.data = null;
}
} else if (oriCnt.isAttached === true && cnt.isAttached === true) {
if (!oriCnt.data) {
if(cnt.data){
cnt.data = null;
}
} else if (!cnt.data || oriCnt.data[__j4838__] !== cnt.data[__j4838__]) {
oriCnt.data[__j4838__] = Date.now();
await Promise.resolve(); // required for making sufficient delay for data rendering
attributeInc(originElement, 'tyt-data-change-counter'); // next macro task
}
}
};
*/
const attributeInc = (elm, prop) => {
let v = (+elm.getAttribute000(prop) || 0) + 1;
if (v > 1e9) v = 9;
elm.setAttribute000(prop, v);
return v;
}
/**
* UC[-_a-zA-Z0-9+=.]{22}
* https://support.google.com/youtube/answer/6070344?hl=en
* The channel ID is the 24 character alphanumeric string that starts with 'UC' in the channel URL.
*/
const isChannelId = (x) => {
if (typeof x === 'string' && x.length === 24) {
return /UC[-_a-zA-Z0-9+=.]{22}/.test(x);
}
return false;
}
const infoFix = (lockId) => {
if (lockId !== null && lockGet['infoFixLock'] !== lockId) return;
// console.log('((infoFix))')
const infoExpander = elements.infoExpander;
const infoContainer = (infoExpander ? infoExpander.parentNode : null) || document.querySelector('#tab-info');
const ytdFlexyElm = elements.flexy;
if (!infoContainer || !ytdFlexyElm) return;
// console.log(386, infoExpander, infoExpander.matches('#tab-info > [class]'))
if (infoExpander) {
const match = infoExpander.matches('#tab-info > [class]') || infoExpander.matches('#tab-info > [tyt-main-info]');
if (!match) return;
}
// const elms = [...document.querySelectorAll('ytd-watch-metadata.ytd-watch-flexy div[slot="extra-content"], ytd-watch-metadata.ytd-watch-flexy ytd-metadata-row-container-renderer')].filter(elm=>{
// if(elm.parentNode.closest('div[slot="extra-content"], ytd-metadata-row-container-renderer')) return false;
// return true;
// });
const requireElements = [...document.querySelectorAll('ytd-watch-metadata.ytd-watch-flexy div[slot="extra-content"] > *, ytd-watch-metadata.ytd-watch-flexy #extra-content > *')].filter(elm => {
return typeof elm.is == 'string'
}).map(elm => {
const is = elm.is;
while (elm instanceof HTMLElement_) {
const q = [...elm.querySelectorAll(is)].filter(e => insp(e).data);
if (q.length >= 1) return q[0];
elm = elm.parentNode;
}
}).filter(elm => !!elm && typeof elm.is === 'string');
// console.log(9162, requireElements)
// if (!infoExpander && !requireElements.length) return;
const source = requireElements.map(entry => {
const inst = insp(entry);
return {
data: inst.data,
tag: inst.is,
elm: entry
};
});
let noscript_ = document.querySelector('noscript#aythl');
if (!noscript_) {
noscript_ = document.createElement('noscript')
noscript_.id = 'aythl';
inPageRearrange = true;
ytdFlexyElm.insertBefore000(noscript_, ytdFlexyElm.firstChild);
inPageRearrange = false;
}
const noscript = noscript_;
let requiredUpdate = false;
const mirrorElmSet = new Set();
const targetParent = infoContainer;
for (const { data, tag: tag, elm: s } of source) {
let mirrorNode = mirrorNodeWS.get(s)
mirrorNode = mirrorNode ? kRef(mirrorNode) : mirrorNode;
if (!mirrorNode) {
const cnt = insp(s);
const cProto = cnt.constructor.prototype;
const element = document.createElement(tag);
noscript.appendChild(element); // appendChild to trigger .attached()
mirrorNode = element
mirrorNode[__j5744__] = mWeakRef(s);
const nodeWR = mWeakRef(mirrorNode);
// if(!(insp(s)._dataChanged438)){
// insp(s)._dataChanged438 = async function(){
// await Promise.resolve(); // required for making sufficient delay for data rendering
// attributeInc(originElement, 'tyt-data-change-counter'); // next macro task
// moChangeReflection.call(nodeWR);
// }
// }
new MutationObserver(moChangeReflection.bind(nodeWR)).observe(s, { attributes: true, attributeFilter: ['tyt-clone-refresh-count', 'tyt-data-change-counter'] });
s.jy8432 = 1;
if (!(cProto instanceof Node) && !cProto._dataChanged496 && typeof cProto._createPropertyObserver === 'function') {
cProto._dataChanged496 = function () {
const cnt = this;
const node = cnt.hostElement || cnt;
if (node.jy8432) {
// console.log('hello _dataChanged496', this.is);
// await Promise.resolve(); // required for making sufficient delay for data rendering
attributeInc(node, 'tyt-data-change-counter'); // next macro task
}
}
cProto._createPropertyObserver('data', '_dataChanged496', undefined)
} else if (!(cProto instanceof Node) && !cProto._dataChanged496 && cProto.useSignals === true && insp(s).signalProxy) {
const dataSignal = cnt?.signalProxy?.signalCache?.data;
if (dataSignal && typeof dataSignal.setWithPath === 'function' && !dataSignal.setWithPath573 && !dataSignal.controller573) {
dataSignal.controller573 = mWeakRef(cnt);
dataSignal.setWithPath573 = dataSignal.setWithPath;
dataSignal.setWithPath = function () {
const cnt = (kRef(this.controller573 || null) || null);
cnt && typeof cnt._dataChanged496k === 'function' && Promise.resolve(cnt).then(cnt._dataChanged496k).catch(console.warn);
return this.setWithPath573(...arguments)
}
cProto._dataChanged496 = function () {
const cnt = this;
const node = cnt.hostElement || cnt;
if (node.jy8432) {
// console.log('hello _dataChanged496', this.is);
// await Promise.resolve(); // required for making sufficient delay for data rendering
attributeInc(node, 'tyt-data-change-counter'); // next macro task
}
}
cProto._dataChanged496k = ((cnt) => cnt._dataChanged496());
}
}
if (!cProto._dataChanged496) {
new MutationObserver(monitorDataChangedByDOMMutation.bind(mirrorNode[__j5744__])).observe(s, { attributes: true, childList: true, subtree: true });
}
// new MutationObserver(moChangeReflection.bind(nodeWR)).observe(s, {attributes: true, childList: true, subtree: true});
mirrorNodeWS.set(s, nodeWR);
requiredUpdate = true;
} else {
if (mirrorNode.parentNode !== targetParent) {
requiredUpdate = true;
}
}
if (!requiredUpdate) {
const cloneNodeCnt = insp(mirrorNode);
if (cloneNodeCnt.data !== data) {
// if(mirrorNode.parentNode !== noscript){
// noscript.appendChild(mirrorNode);
// }
// mirrorNode.replaceWith(dummyNode);
// cloneNodeCnt.data = data;
// dummyNode.replaceWith(mirrorNode);
requiredUpdate = true;
}
}
mirrorElmSet.add(mirrorNode);
source.mirrored = mirrorNode;
}
const mirroElmArr = [...mirrorElmSet];
mirrorElmSet.clear();
if (!requiredUpdate) {
let e = infoExpander ? -1 : 0;
// DOM Tree Check
for (let n = targetParent.firstChild; n instanceof Node; n = n.nextSibling) {
let target = e < 0 ? infoExpander : mirroElmArr[e];
e++;
if (n !== target) {
// target can be undefined if index overflow
requiredUpdate = true;
break;
}
}
if (!requiredUpdate && e !== mirroElmArr.length + 1) requiredUpdate = true;
}
if (requiredUpdate) {
if (infoExpander) {
targetParent.assignChildren111(null, infoExpander, mirroElmArr);
} else {
targetParent.replaceChildren000(...mirroElmArr);
}
for (const mirrorElm of mirroElmArr) {
// trigger data assignment and record refresh count by manual update
const j = attributeInc(mirrorElm, 'tyt-clone-refresh-count');
const oriElm = kRef(mirrorElm[__j5744__] || null) || null;
if (oriElm) {
oriElm.setAttribute111('tyt-clone-refresh-count', j)
}
}
}
mirroElmArr.length = 0;
source.length = 0;
}
const layoutFix = (lockId) => {
if (lockGet['layoutFixLock'] !== lockId) return;
// console.log('((layoutFix))')
const secondaryWrapper = document.querySelector('#secondary-inner.style-scope.ytd-watch-flexy > secondary-wrapper');
// console.log(3838, !!chatContainer, !!(secondaryWrapper && secondaryInner), secondaryInner?.firstChild, secondaryInner?.lastChild , secondaryWrapper?.parentNode === secondaryInner)
if (secondaryWrapper) {
const secondaryInner = secondaryWrapper.parentNode;
const chatContainer = document.querySelector('#columns.style-scope.ytd-watch-flexy [tyt-chat-container]');
if (secondaryInner.firstChild !== secondaryInner.lastChild || (chatContainer && !chatContainer.closest('secondary-wrapper'))) {
// console.log(38381)
let w = [];
let w2 = [];
for (let node = secondaryInner.firstChild; node instanceof Node; node = node.nextSibling) {
if (node === chatContainer && chatContainer) {
} else if (node === secondaryWrapper) {
for (let node2 = secondaryWrapper.firstChild; node2 instanceof Node; node2 = node2.nextSibling) {
if (node2 === chatContainer && chatContainer) {
} else {
if (node2.id === 'right-tabs' && chatContainer) {
w2.push(chatContainer);
}
w2.push(node2);
}
}
} else {
w.push(node);
}
}
// console.log('qww', w, w2)
inPageRearrange = true;
secondaryWrapper.replaceChildren000(...w, ...w2);
inPageRearrange = false;
const chatElm = elements.chat;
const chatCnt = insp(chatElm);
if (chatCnt && typeof chatCnt.urlChanged === 'function' && secondaryWrapper.contains(chatElm)) {
// setTimeout(() => chatCnt.urlChanged, 136);
if (typeof chatCnt.urlChangedAsync12 === 'function') {
console.log('elements.chat urlChangedAsync12', 61);
chatCnt.urlChanged();
} else {
console.log('elements.chat urlChangedAsync12', 62);
setTimeout(() => chatCnt.urlChanged(), 136);
}
}
}
}
}
let lastPanel = '';
let lastTab = '';
// let fixInitialTabState = 0;
const aoEgmPanels = new MutationObserver(() => {
// console.log(5094,3);
Promise.resolve(lockSet['updateEgmPanelsLock']).then(updateEgmPanels).catch(console.warn);
});
const removeKeepCommentsScroller = async (lockId) => {
if (lockGet['removeKeepCommentsScrollerLock'] !== lockId) return;
await Promise.resolve();
if (lockGet['removeKeepCommentsScrollerLock'] !== lockId) return;
const ytdFlexyFlm = elements.flexy;
if (ytdFlexyFlm) {
ytdFlexyFlm.removeAttribute000('keep-comments-scroller');
}
}
const updateEgmPanels = async (lockId) => {
if (lockId !== lockGet['updateEgmPanelsLock']) return;
await navigateFinishedPromise.then().catch(console.warn);
if (lockId !== lockGet['updateEgmPanelsLock']) return;
// console.log('updateEgmPanels::called');
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) return;
let newVisiblePanels = [];
let newHiddenPanels = [];
let allVisiblePanels = [];
for (const panelElm of document.querySelectorAll('[tyt-egm-panel][target-id][visibility]')) {
const visibility = panelElm.getAttribute000('visibility');
if (visibility === 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN' || panelElm.closest('[hidden]')) {
if (panelElm.hasAttribute000('tyt-visible-at')) {
panelElm.removeAttribute000('tyt-visible-at');
newHiddenPanels.push(panelElm);
}
} else if (visibility === 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED' && !panelElm.closest('[hidden]')) {
let visibleAt = panelElm.getAttribute000('tyt-visible-at');
if (!visibleAt) {
panelElm.setAttribute111('tyt-visible-at', Date.now());
newVisiblePanels.push(panelElm);
}
allVisiblePanels.push(panelElm);
}
}
if (newVisiblePanels.length >= 1 && allVisiblePanels.length >= 2) {
const targetVisible = newVisiblePanels[newVisiblePanels.length - 1];
const actions = [];
for (const panelElm of allVisiblePanels) {
if (panelElm === targetVisible) continue;
actions.push({
panelId: panelElm.getAttribute000('target-id'),
toHide: true
});
}
if (actions.length >= 1) {
ytBtnEgmPanelCore(actions);
}
}
if (allVisiblePanels.length >= 1) {
ytdFlexyElm.setAttribute111('tyt-egm-panel_', '');
} else {
ytdFlexyElm.removeAttribute000('tyt-egm-panel_');
}
newVisiblePanels.length = 0;
newVisiblePanels = null;
newHiddenPanels.length = 0;
newHiddenPanels = null;
allVisiblePanels.length = 0;
allVisiblePanels = null;
}
const checkElementExist = (css, exclude) => {
for (const p of document.querySelectorAll(css)) {
if (!p.closest(exclude)) return p;
}
return null;
}
let fixInitialTabStateK = 0;
const { handleNavigateFactory } = (() => {
let isLoadStartListened = false;
function findLcComment(lc) {
if (arguments.length === 1) {
let element = document.querySelector(`#tab-comments ytd-comments ytd-comment-renderer #header-author a[href*="lc=${lc}"]`);
if (element) {
let commentRendererElm = closestFromAnchor.call(element, 'ytd-comment-renderer');
if (commentRendererElm && lc) {
return {
lc,
commentRendererElm
}
}
}
} else if (arguments.length === 0) {
let element = document.querySelector(`#tab-comments ytd-comments ytd-comment-renderer > #linked-comment-badge span:not(:empty)`);
if (element) {
let commentRendererElm = closestFromAnchor.call(element, 'ytd-comment-renderer');
if (commentRendererElm) {
let header = _querySelector.call(commentRendererElm, '#header-author');
if (header) {
let anchor = _querySelector.call(header, 'a[href*="lc="]');
if (anchor) {
let href = (anchor.getAttribute('href') || '');
let m = /[&?]lc=([\w_.-]+)/.exec(href); // dot = sub-comment
if (m) {
lc = m[1];
}
}
}
}
if (commentRendererElm && lc) {
return {
lc,
commentRendererElm
}
}
}
}
return null;
}
function lcSwapFuncA(targetLcId, currentLcId) {
let done = 0;
try {
// console.log(currentLcId, targetLcId)
let r1 = findLcComment(currentLcId).commentRendererElm;
let r2 = findLcComment(targetLcId).commentRendererElm;
if (typeof insp(r1).data.linkedCommentBadge === 'object' && typeof insp(r2).data.linkedCommentBadge === 'undefined') {
let p = Object.assign({}, insp(r1).data.linkedCommentBadge)
if (((p || 0).metadataBadgeRenderer || 0).trackingParams) {
delete p.metadataBadgeRenderer.trackingParams;
}
const v1 = findContentsRenderer(r1)
const v2 = findContentsRenderer(r2)
if (v1.parent === v2.parent && (v2.parent.nodeName === 'YTD-COMMENTS' || v2.parent.nodeName === 'YTD-ITEM-SECTION-RENDERER')) {
} else {
// currently not supported
return false;
}
if (v2.index >= 0) {
if (v2.parent.nodeName === 'YTD-COMMENT-REPLIES-RENDERER') {
if (lcSwapFuncB(targetLcId, currentLcId, p)) {
done = 1;
}
done = 1;
} else {
const v2pCnt = insp(v2.parent);
const v2Conents = (v2pCnt.data || 0).contents || 0;
if (!v2Conents) console.warn('v2Conents is not found');
v2pCnt.data = Object.assign({}, v2pCnt.data, { contents: [].concat([v2Conents[v2.index]], v2Conents.slice(0, v2.index), v2Conents.slice(v2.index + 1)) });
if (lcSwapFuncB(targetLcId, currentLcId, p)) {
done = 1;
}
}
}
}
} catch (e) {
console.warn(e)
}
return done === 1;
}
function lcSwapFuncB(targetLcId, currentLcId, _p) {
let done = 0;
try {
let r1 = findLcComment(currentLcId).commentRendererElm;
let r1cnt = insp(r1);
let r2 = findLcComment(targetLcId).commentRendererElm;
let r2cnt = insp(r2);
const r1d = r1cnt.data;
let p = Object.assign({}, _p)
r1d.linkedCommentBadge = null;
delete r1d.linkedCommentBadge;
let q = Object.assign({}, r1d);
q.linkedCommentBadge = null;
delete q.linkedCommentBadge;
r1cnt.data = Object.assign({}, q);
r2cnt.data = Object.assign({}, r2cnt.data, { linkedCommentBadge: p });
done = 1;
} catch (e) {
console.warn(e)
}
return done === 1;
}
const loadStartFx = async (evt) => {
let media = (evt || 0).target || 0;
if (media.nodeName === 'VIDEO' || media.nodeName === 'AUDIO') { }
else return;
const newMedia = media;
const media1 = common.getMediaElement(0); // document.querySelector('#movie_player video[src]');
const media2 = common.getMediaElements(2); // document.querySelectorAll('ytd-browse[role="main"] video[src]');
if (media1 !== null && media2.length > 0) {
if (newMedia !== media1 && media1.paused === false) {
if (isVideoPlaying(media1)) {
Promise.resolve(newMedia).then(video => video.paused === false && video.pause()).catch(console.warn);
}
} else if (newMedia === media1) {
for (const s of media2) {
if (s.paused === false) {
Promise.resolve(s).then(s => s.paused === false && s.pause()).catch(console.warn);
break;
}
}
} else {
Promise.resolve(media1).then(video1 => video1.paused === false && video1.pause()).catch(console.warn);
}
}
}
const getBrowsableEndPoint = (req) => {
let valid = false;
let endpoint = req ? req.command : null;
if (endpoint && (endpoint.commandMetadata || 0).webCommandMetadata && endpoint.watchEndpoint) {
let videoId = endpoint.watchEndpoint.videoId;
let url = endpoint.commandMetadata.webCommandMetadata.url;
if (typeof videoId === 'string' && typeof url === 'string' && url.indexOf('lc=') > 0) {
let m = /^\/watch\?v=([\w_-]+)&lc=([\w_.-]+)$/.exec(url); // dot = sub-comment
if (m && m[1] === videoId) {
/*
{
"style": "BADGE_STYLE_TYPE_SIMPLE",
"label": "注目のコメント",
"trackingParams": "XXXXXX"
}
*/
let targetLc = findLcComment(m[2])
let currentLc = targetLc ? findLcComment() : null;
if (targetLc && currentLc) {
let done = targetLc.lc === currentLc.lc ? 1 : lcSwapFuncA(targetLc.lc, currentLc.lc) ? 1 : 0
if (done === 1) {
common.xReplaceState(history.state, url);
return;
}
}
}
}
}
/*
{
"type": 0,
"command": endpoint,
"form": {
"tempData": {},
"reload": false
}
}
*/
if (endpoint && (endpoint.commandMetadata || 0).webCommandMetadata && endpoint.browseEndpoint && isChannelId(endpoint.browseEndpoint.browseId)) {
valid = true;
} else if (endpoint && (endpoint.browseEndpoint || endpoint.searchEndpoint) && !endpoint.urlEndpoint && !endpoint.watchEndpoint) {
if (endpoint.browseEndpoint && endpoint.browseEndpoint.browseId === "FEwhat_to_watch") {
// valid = false;
const playerMedia = common.getMediaElement(1);
if (playerMedia && playerMedia.paused === false) valid = true; // home page
} else if (endpoint.commandMetadata && endpoint.commandMetadata.webCommandMetadata) {
let meta = endpoint.commandMetadata.webCommandMetadata
if (meta && /*meta.apiUrl &&*/ meta.url && meta.webPageType) {
valid = true;
}
}
}
if (!valid) endpoint = null;
return endpoint;
}
const shouldUseMiniPlayer = () => {
const isSubTypeExist = document.querySelector('ytd-page-manager#page-manager > ytd-browse[page-subtype]');
if (isSubTypeExist) return true;
const movie_player = [...document.querySelectorAll('#movie_player')].filter(e => !e.closest('[hidden]'))[0];
if (movie_player) {
const media = qsOne(movie_player, 'video[class], audio[class]');
if (media && media.currentTime > 3 && media.duration - media.currentTime > 3 && media.paused === false) {
return true;
}
}
return false;
// return true;
// return !!document.querySelector('ytd-page-manager#page-manager > ytd-browse[page-subtype]');
}
const conditionFulfillment = (req) => {
const endpoint = req ? req.command : null;
if (!endpoint) return;
if (endpoint && (endpoint.commandMetadata || 0).webCommandMetadata && endpoint.watchEndpoint) {
} else if (endpoint && (endpoint.commandMetadata || 0).webCommandMetadata && endpoint.browseEndpoint && isChannelId(endpoint.browseEndpoint.browseId)) {
} else if (endpoint && (endpoint.browseEndpoint || endpoint.searchEndpoint) && !endpoint.urlEndpoint && !endpoint.watchEndpoint) {
} else {
return false;
}
if (!shouldUseMiniPlayer()) return false;
/*
// user would like to switch page immediately without playing the video;
// attribute appear after playing video for more than 2s
if (!document.head.dataset.viTime) return false;
else {
let currentVideo = common.getMediaElement(0);
if (currentVideo && currentVideo.readyState > currentVideo.HAVE_CURRENT_DATA && currentVideo.currentTime > 2.2 && currentVideo.duration - 2.2 < currentVideo.currentTime) {
// disable miniview browsing if the media is near to the end
return false;
}
}
*/
if (pageType !== "watch") return false;
if (!checkElementExist('ytd-watch-flexy #player button.ytp-miniplayer-button.ytp-button', '[hidden]')) {
return false;
}
return true;
}
let u38 = 0;
const fixChannelAboutPopup = async (t38) => {
let promise = new PromiseExternal();
const f = () => {
promise && promise.resolve();
promise = null;
}
document.addEventListener('yt-navigate-finish', f, false);
await promise.then();
promise = null;
document.removeEventListener('yt-navigate-finish', f, false);
if (t38 !== u38) return;
setTimeout(() => {
const currentAbout = [...document.querySelectorAll('ytd-about-channel-renderer')].filter(e => !e.closest('[hidden]'))[0];
let okay = false;
if (!currentAbout) okay = true;
else {
const popupContainer = currentAbout.closest('ytd-popup-container');
if (popupContainer) {
const cnt = insp(popupContainer);
let arr = null;
try {
arr = cnt.handleGetOpenedPopupsAction_()
} catch (e) {
}
if (arr && arr.length === 0) okay = true;
} else {
okay = false;
}
}
if (okay) {
const descriptionModel = [...document.querySelectorAll('yt-description-preview-view-model')].filter(e => !e.closest('[hidden]'))[0];
if (descriptionModel) {
const button = [...descriptionModel.querySelectorAll('button')].filter(e => !e.closest('[hidden]') && `${e.textContent}`.trim().length > 0)[0];
if (button) {
button.click();
}
}
}
}, 80);
}
const handleNavigateFactory = (handleNavigate) => {
return function (req) {
if (u38 > 1e9) u38 = 9;
const t38 = ++u38;
const $this = this;
const $arguments = arguments;
let endpoint = null;
if (conditionFulfillment(req)) {
endpoint = getBrowsableEndPoint(req);
}
if (!endpoint || !shouldUseMiniPlayer()) return handleNavigate.apply($this, $arguments);
// console.log('tabview-script-handleNavigate')
const ytdAppElm = document.querySelector('ytd-app');
const ytdAppCnt = insp(ytdAppElm);
let object = null;
try {
object = ytdAppCnt.data.response.currentVideoEndpoint.watchEndpoint || null;
} catch (e) {
object = null;
}
if (typeof object !== 'object') object = null;
const once = { once: true }; // browsers supporting async function can also use once option.
if (object !== null && !('playlistId' in object)) {
let wObject = mWeakRef(object)
const N = 3;
let count = 0;
/*
rcb(b) => a = playlistId = undefinded
var scb = function(a, b, c, d) {
a.isInitialized() && (B("kevlar_miniplayer_navigate_to_shorts_killswitch") ? c || d ? ("watch" !== Xu(b) && "shorts" !== Xu(b) && os(a.miniplayerEl, "yt-cache-miniplayer-page-action", [b]),
qs(a.miniplayerEl, "yt-deactivate-miniplayer-action")) : "watch" === Xu(b) && rcb(b) && (qt.getInstance().playlistWatchPageActivation = !0,
a.activateMiniplayer(b)) : c ? ("watch" !== Xu(b) && os(a.miniplayerEl, "yt-cache-miniplayer-page-action", [b]),
qs(a.miniplayerEl, "yt-deactivate-miniplayer-action")) : d ? qs(a.miniplayerEl, "yt-pause-miniplayer-action") : "watch" === Xu(b) && rcb(b) && (qt.getInstance().playlistWatchPageActivation = !0,
a.activateMiniplayer(b)))
};
*/
Object.defineProperty((kRef(wObject) || {}), 'playlistId', {
get() {
count++;
if (count === N) {
delete this.playlistId;
}
return '*';
},
set(value) {
delete this.playlistId; // remove property definition
this.playlistId = value; // assign as normal property
},
enumerable: false,
configurable: true
});
let playlistClearout = null;
let timeoutid = 0;
Promise.race([
new Promise(r => {
timeoutid = setTimeout(r, 4000)
}),
new Promise(r => {
playlistClearout = () => {
if (timeoutid > 0) {
clearTimeout(timeoutid);
timeoutid = 0;
}
r();
}
document.addEventListener('yt-page-type-changed', playlistClearout, once);
})
]).then(() => {
if (timeoutid !== 0) {
playlistClearout && document.removeEventListener('yt-page-type-changed', playlistClearout, once);
timeoutid = 0;
}
playlistClearout = null;
count = N - 1;
let object = kRef(wObject)
wObject = null;
return object ? object.playlistId : null;
}).catch(console.warn);
}
if (!isLoadStartListened) {
isLoadStartListened = true;
document.addEventListener('loadstart', loadStartFx, true)
}
const endpointURL = `${(endpoint?.commandMetadata?.webCommandMetadata?.url || '')}`;
if (endpointURL && endpointURL.endsWith('/about') && /\/channel\/UC[-_a-zA-Z0-9+=.]{22}\/about/.test(endpointURL)) {
fixChannelAboutPopup(t38);
}
handleNavigate.apply($this, $arguments);
}
};
return { handleNavigateFactory };
})();
const common = (() => {
let mediaModeLock = 0;
const _getMediaElement = (i) => {
if (mediaModeLock === 0) {
let e = document.querySelector('.video-stream.html5-main-video') || document.querySelector('#movie_player video, #movie_player audio') || document.querySelector('body video[src], body audio[src]');
if (e) {
if (e.nodeName === 'VIDEO') mediaModeLock = 1;
else if (e.nodeName === 'AUDIO') mediaModeLock = 2;
}
}
if (!mediaModeLock) return null;
if (mediaModeLock === 1) {
switch (i) {
case 1:
return ('ytd-player#ytd-player video[src]');
case 2:
return ('ytd-browse[role="main"] video[src]');
case 0:
default:
return ('#movie_player video[src]');
}
} else if (mediaModeLock === 2) {
switch (i) {
case 1:
return ('ytd-player#ytd-player audio.video-stream.html5-main-video[src]');
case 2:
return ('ytd-browse[role="main"] audio.video-stream.html5-main-video[src]');
case 0:
default:
return ('#movie_player audio.video-stream.html5-main-video[src]');
}
}
return null;
}
return {
xReplaceState(s, u) {
try {
history.replaceState(s, '', u);
} catch (e) {
// in case error occurs if replaceState is replaced by any external script / extension
}
if (s.endpoint) {
try {
const ytdAppElm = document.querySelector('ytd-app');
const ytdAppCnt = insp(ytdAppElm);
ytdAppCnt.replaceState(s.endpoint, '', u)
} catch (e) {
}
}
},
getMediaElement(i) {
let s = _getMediaElement(i) || '';
if (s) return document.querySelector(s);
return null;
},
getMediaElements(i) {
let s = _getMediaElement(i) || '';
if (s) return document.querySelectorAll(s);
return [];
}
};
})();
let inPageRearrange = false;
let tmpLastVideoId = '';
// const nsMap = new Map();
const getCurrentVideoId = () => {
const ytdFlexyElm = elements.flexy;
const ytdFlexyCnt = insp(ytdFlexyElm);
if (ytdFlexyCnt && typeof ytdFlexyCnt.videoId === 'string') return ytdFlexyCnt.videoId;
if (ytdFlexyElm && typeof ytdFlexyElm.videoId === 'string') return ytdFlexyElm.videoId;
console.log('video id not found');
return '';
};
const holdInlineExpanderAlwaysExpanded = (inlineExpanderCnt) => {
console.log('holdInlineExpanderAlwaysExpanded')
if (inlineExpanderCnt.alwaysShowExpandButton === true) inlineExpanderCnt.alwaysShowExpandButton = false;
if (typeof (inlineExpanderCnt.collapseLabel || 0) === 'string') inlineExpanderCnt.collapseLabel = '';
if (typeof (inlineExpanderCnt.expandLabel || 0) === 'string') inlineExpanderCnt.expandLabel = '';
if (inlineExpanderCnt.showCollapseButton === true) inlineExpanderCnt.showCollapseButton = false;
if (inlineExpanderCnt.showExpandButton === true) inlineExpanderCnt.showExpandButton = false;
if (inlineExpanderCnt.expandButton instanceof HTMLElement_) {
inlineExpanderCnt.expandButton = null;
inlineExpanderCnt.expandButton.remove();
}
};
const fixInlineExpanderDisplay = (inlineExpanderCnt) => {
try {
inlineExpanderCnt.updateIsAttributedExpanded();
} catch (e) { }
try {
inlineExpanderCnt.updateIsFormattedExpanded();
} catch (e) { }
try {
inlineExpanderCnt.updateTextOnSnippetTypeChange();
} catch (e) { }
try {
inlineExpanderCnt.updateStyles();
} catch (e) { }
};
const fixInlineExpanderMethods = (inlineExpanderCnt) => {
if (inlineExpanderCnt && !inlineExpanderCnt.__$$idncjk8487$$__) {
inlineExpanderCnt.__$$idncjk8487$$__ = true;
inlineExpanderCnt.updateTextOnSnippetTypeChange = function () {
true || this.isResetMutation && this.mutationCallback();
}
// inlineExpanderCnt.hasAttributedStringText = true;
inlineExpanderCnt.isResetMutation = true;
fixInlineExpanderDisplay(inlineExpanderCnt); // do the initial fix
}
};
const fixInlineExpanderContent = () => {
// console.log(21886,1)
const mainInfo = getMainInfo();
if (!mainInfo) return;
// console.log(21886,2)
const inlineExpanderElm = mainInfo.querySelector('ytd-text-inline-expander');
const inlineExpanderCnt = insp(inlineExpanderElm);
fixInlineExpanderMethods(inlineExpanderCnt);
// console.log(21886, 3)
// if (inlineExpanderCnt && inlineExpanderCnt.isExpanded === true && plugin.autoExpandInfoDesc.activated) {
// // inlineExpanderCnt.isExpandedChanged();
// // holdInlineExpanderAlwaysExpanded(inlineExpanderCnt);
// }
// if(inlineExpanderCnt){
// // console.log(21886,4, inlineExpanderCnt.isExpanded, inlineExpanderCnt.isTruncated)
// if (inlineExpanderCnt.isExpanded === false && inlineExpanderCnt.isTruncated === true) {
// // console.log(21881)
// inlineExpanderCnt.isTruncated = false;
// }
// }
}
const plugin = {
'minibrowser': {
activated: false,
toUse: true, // depends on shouldUseMiniPlayer()
activate() {
if (this.activated) return;
const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
// https://caniuse.com/?search=observer
// https://caniuse.com/?search=addEventListener%20passive
if (!isPassiveArgSupport) return;
this.activated = true;
const ytdAppElm = document.querySelector('ytd-app');
const ytdAppCnt = insp(ytdAppElm);
if (!ytdAppCnt) return;
const cProto = ytdAppCnt.constructor.prototype;
if (!cProto.handleNavigate) return;
if (cProto.handleNavigate.__ma355__) return;
cProto.handleNavigate = handleNavigateFactory(cProto.handleNavigate);
cProto.handleNavigate.__ma355__ = 1;
}
},
'autoExpandInfoDesc': {
activated: false,
toUse: false, // false by default; once the expand is clicked, maintain the feature until the browser is closed.
/** @type { MutationObserver | null } */
mo: null,
promiseReady: new PromiseExternal(),
moFn(lockId) {
if (lockGet['autoExpandInfoDescAttrAsyncLock'] !== lockId) return;
const mainInfo = getMainInfo();
if (!mainInfo) return;
switch (((mainInfo || 0).nodeName || '').toLowerCase()) {
case 'ytd-expander':
if (mainInfo.hasAttribute000('collapsed')) {
let success = false;
try {
insp(mainInfo).handleMoreTap(new Event("tap"));
success = true;
} catch (e) {
}
if (success) mainInfo.setAttribute111('tyt-no-less-btn', '');
}
break;
case 'ytd-expandable-video-description-body-renderer':
const inlineExpanderElm = mainInfo.querySelector('ytd-text-inline-expander');
const inlineExpanderCnt = insp(inlineExpanderElm);
if (inlineExpanderCnt && inlineExpanderCnt.isExpanded === false) {
inlineExpanderCnt.isExpanded = true;
inlineExpanderCnt.isExpandedChanged();
// holdInlineExpanderAlwaysExpanded(inlineExpanderCnt);
}
break;
}
},
activate() {
if (this.activated) return;
this.moFn = this.moFn.bind(this);
this.mo = new MutationObserver(() => {
Promise.resolve(lockSet['autoExpandInfoDescAttrAsyncLock']).then(this.moFn).catch(console.warn);
});
this.activated = true;
this.promiseReady.resolve();
},
async onMainInfoSet(mainInfo) {
await this.promiseReady.then();
if (mainInfo.nodeName.toLowerCase() === 'ytd-expander') {
this.mo.observe(mainInfo, { attributes: true, attributeFilter: ['collapsed', 'attr-8ifv7'] });
} else {
this.mo.observe(mainInfo, { attributes: true, attributeFilter: ['attr-8ifv7'] });
}
mainInfo.incAttribute111('attr-8ifv7');
}
},
'fullChannelNameOnHover': {
activated: false,
toUse: true,
/** @type { MutationObserver | null } */
mo: null,
/** @type { ResizeObserver | null} */
ro: null,
promiseReady: new PromiseExternal(),
checkResize: 0,
mouseEnterFn(evt) {
const target = evt ? evt.target : null;
if (!(target instanceof HTMLElement_)) return;
const metaDataElm = target.closest('ytd-watch-metadata');
metaDataElm.classList.remove('tyt-metadata-hover-resized');
this.checkResize = Date.now() + 300;
metaDataElm.classList.add('tyt-metadata-hover');
// console.log('mouseEnter')
},
mouseLeaveFn(evt) {
const target = evt ? evt.target : null;
if (!(target instanceof HTMLElement_)) return;
const metaDataElm = target.closest('ytd-watch-metadata');
metaDataElm.classList.remove('tyt-metadata-hover-resized');
metaDataElm.classList.remove('tyt-metadata-hover');
// console.log('mouseLeaveFn')
},
moFn(lockId) {
if (lockGet['fullChannelNameOnHoverAttrAsyncLock'] !== lockId) return;
const uploadInfo = document.querySelector('#primary.ytd-watch-flexy ytd-watch-metadata #upload-info');
if (!uploadInfo) return;
const evtOpt = { passive: true, capture: false };
uploadInfo.removeEventListener('pointerenter', this.mouseEnterFn, evtOpt);
uploadInfo.removeEventListener('pointerleave', this.mouseLeaveFn, evtOpt);
uploadInfo.addEventListener('pointerenter', this.mouseEnterFn, evtOpt);
uploadInfo.addEventListener('pointerleave', this.mouseLeaveFn, evtOpt);
},
async onNavigateFinish() {
await this.promiseReady.then();
const uploadInfo = document.querySelector('#primary.ytd-watch-flexy ytd-watch-metadata #upload-info');
if (!uploadInfo) return;
this.mo.observe(uploadInfo, { attributes: true, attributeFilter: ['hidden', 'attr-3wb0k'] });
uploadInfo.incAttribute111('attr-3wb0k');
this.ro.observe(uploadInfo);
},
activate() {
if (this.activated) return;
const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
// https://caniuse.com/?search=observer
// https://caniuse.com/?search=addEventListener%20passive
if (!isPassiveArgSupport) return;
this.activated = true;
this.mouseEnterFn = this.mouseEnterFn.bind(this);
this.mouseLeaveFn = this.mouseLeaveFn.bind(this);
this.moFn = this.moFn.bind(this);
this.mo = new MutationObserver(() => {
Promise.resolve(lockSet['fullChannelNameOnHoverAttrAsyncLock']).then(this.moFn).catch(console.warn);
});
this.ro = new ResizeObserver((mutations) => {
if (Date.now() > this.checkResize) return;
for (const mutation of mutations) {
const uploadInfo = mutation.target;
if (uploadInfo && mutation.contentRect.width > 0 && mutation.contentRect.height > 0) {
const metaDataElm = uploadInfo.closest('ytd-watch-metadata');
if (metaDataElm.classList.contains('tyt-metadata-hover')) {
metaDataElm.classList.add('tyt-metadata-hover-resized');
}
break;
}
}
});
this.promiseReady.resolve();
}
}
}
if (sessionStorage.__$$tmp_UseAutoExpandInfoDesc$$__) plugin.autoExpandInfoDesc.toUse = true;
// let shouldFixInfo = false;
const __attachedSymbol__ = Symbol();
const makeInitAttached = (tag) => {
const inPageRearrange_ = inPageRearrange;
inPageRearrange = false;
for (const elm of document.querySelectorAll(`${tag}`)) {
const cnt = insp(elm) || 0;
if (typeof cnt.attached498 === 'function' && !elm[__attachedSymbol__]) Promise.resolve(elm).then(eventMap[`${tag}::attached`]).catch(console.warn);
}
inPageRearrange = inPageRearrange_;
};
const getGeneralChatElement = async () => {
for (let i = 2; i-- > 0;) {
let t = document.querySelector('#columns.style-scope.ytd-watch-flexy ytd-live-chat-frame#chat');
if (t instanceof Element) return t;
if (i > 0) {
// try later
console.log('ytd-live-chat-frame::attached - delayPn(200)')
await delayPn(200);
}
}
return null;
};
const nsTemplateObtain = () => {
let nsTemplate = document.querySelector('ytd-watch-flexy noscript[ns-template]');
if (!nsTemplate) {
nsTemplate = document.createElement('noscript');
nsTemplate.setAttribute('ns-template', '');
document.querySelector('ytd-watch-flexy').appendChild(nsTemplate);
}
return nsTemplate;
};
const isPageDOM = (elm, selector) => {
if (!elm || !(elm instanceof Element) || !(elm.nodeName)) return false;
if (!elm.closest(selector)) return false;
if (elm.isConnected !== true) return false;
return true;
};
const invalidFlexyParent = (hostElement) => {
if (hostElement instanceof HTMLElement) {
const hasFlexyParent = HTMLElement.prototype.closest.call(hostElement, 'ytd-watch-flexy'); // eg short
if (!hasFlexyParent) return true;
const currentFlexy = elements.flexy;
if (currentFlexy && currentFlexy !== hasFlexyParent) return true;
}
return false;
}
// const mutationComment = document.createComment('1');
// let mutationPromise = new PromiseExternal();
// const mutationPromiseObs = new MutationObserver(()=>{
// mutationPromise.resolve();
// mutationPromise = new PromiseExternal();
// });
// mutationPromiseObs.observe(mutationComment, {characterData: true});
let headerMutationObserver = null;
let headerMutationTmpNode = null;
const eventMap = {
'ceHack': () => {
mLoaded.flag |= 2;
document.documentElement.setAttribute111('tabview-loaded', mLoaded.makeString());
retrieveCE('ytd-watch-flexy').then(eventMap['ytd-watch-flexy::defined']).catch(console.warn);
retrieveCE('ytd-expander').then(eventMap['ytd-expander::defined']).catch(console.warn);
retrieveCE('ytd-watch-next-secondary-results-renderer').then(eventMap['ytd-watch-next-secondary-results-renderer::defined']).catch(console.warn);
retrieveCE('ytd-comments-header-renderer').then(eventMap['ytd-comments-header-renderer::defined']).catch(console.warn);
retrieveCE('ytd-live-chat-frame').then(eventMap['ytd-live-chat-frame::defined']).catch(console.warn);
retrieveCE('ytd-comments').then(eventMap['ytd-comments::defined']).catch(console.warn);
retrieveCE('ytd-engagement-panel-section-list-renderer').then(eventMap['ytd-engagement-panel-section-list-renderer::defined']).catch(console.warn);
retrieveCE('ytd-watch-metadata').then(eventMap['ytd-watch-metadata::defined']).catch(console.warn);
retrieveCE('ytd-playlist-panel-renderer').then(eventMap['ytd-playlist-panel-renderer::defined']).catch(console.warn);
retrieveCE('ytd-expandable-video-description-body-renderer').then(eventMap['ytd-expandable-video-description-body-renderer::defined']).catch(console.warn);
},
'fixForTabDisplay': (isResize) => {
// isResize is true if the layout is resized (not due to tab switching)
// youtube components shall handle the resize issue. can skip some checkings.
bFixForResizedTabLater = false;
for (const element of document.querySelectorAll('[io-intersected]')) {
const cnt = insp(element);
if (element instanceof HTMLElement_ && typeof cnt.calculateCanCollapse === 'function') {
try {
cnt.calculateCanCollapse(true);
} catch (e) { }
}
}
if (!isResize && lastTab === '#tab-info') {
// #tab-info is now shown.
// to fix the sizing issue (description info cards in tab info)
for (const element of document.querySelectorAll('#tab-info ytd-video-description-infocards-section-renderer, #tab-info yt-chip-cloud-renderer, #tab-info ytd-horizontal-card-list-renderer, #tab-info yt-horizontal-list-renderer')) {
const cnt = insp(element);
if (element instanceof HTMLElement_ && typeof cnt.notifyResize === 'function') {
try {
cnt.notifyResize();
} catch (e) { }
}
}
// to fix expand/collapse sizing issue (inline-expander in tab info)
// for example, expand button is required but not shown as it was rendered in the hidden state
for (const element of document.querySelectorAll('#tab-info ytd-text-inline-expander')) {
const cnt = insp(element);
if (element instanceof HTMLElement_ && typeof cnt.resize === 'function') {
cnt.resize(false); // reflow due to offsetWidth calling
}
fixInlineExpanderDisplay(cnt); // just in case
}
}
if (!isResize && typeof lastTab === 'string' && lastTab.startsWith('#tab-')) {
const tabContent = document.querySelector('.tab-content-cld:not(.tab-content-hidden)');
if (tabContent) {
const renderers = tabContent.querySelectorAll('yt-chip-cloud-renderer');
for (const renderer of renderers) {
const cnt = insp(renderer);
if (typeof cnt.notifyResize === 'function') {
try {
cnt.notifyResize();
} catch (e) { }
}
}
}
}
},
'ytd-watch-flexy::defined': (cProto) => {
if (!cProto.updateChatLocation498 && typeof cProto.updateChatLocation === 'function' && cProto.updateChatLocation.length === 0) {
cProto.updateChatLocation498 = cProto.updateChatLocation;
cProto.updateChatLocation = updateChatLocation498;
}
},
'ytd-watch-next-secondary-results-renderer::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-watch-next-secondary-results-renderer::attached']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-watch-next-secondary-results-renderer::detached']).catch(console.warn);
return this.detached498();
}
}
makeInitAttached('ytd-watch-next-secondary-results-renderer');
},
'ytd-watch-next-secondary-results-renderer::attached': (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-watch-next-secondary-results-renderer::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
if (hostElement instanceof HTMLElement_ && hostElement.matches('#columns #related ytd-watch-next-secondary-results-renderer') && !hostElement.matches('#right-tabs ytd-watch-next-secondary-results-renderer, [hidden] ytd-watch-next-secondary-results-renderer')) {
elements.related = hostElement.closest('#related');
hostElement.setAttribute111('tyt-videos-list', '');
}
// console.log('ytd-watch-next-secondary-results-renderer::attached', hostElement);
},
'ytd-watch-next-secondary-results-renderer::detached': (hostElement) => {
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-watch-next-secondary-results-renderer::detached');
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
if (hostElement.hasAttribute000('tyt-videos-list')) {
elements.related = null;
hostElement.removeAttribute000('tyt-videos-list');
}
console.log('ytd-watch-next-secondary-results-renderer::detached', hostElement);
},
'settingCommentsVideoId': (hostElement) => {
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
const cnt = insp(hostElement);
const commentsArea = elements.comments;
if (commentsArea !== hostElement || hostElement.isConnected !== true || cnt.isAttached !== true || !cnt.data || cnt.hidden !== false) return;
const ytdFlexyElm = elements.flexy;
const ytdFlexyCnt = ytdFlexyElm ? insp(ytdFlexyElm) : null;
if (ytdFlexyCnt && ytdFlexyCnt.videoId) {
hostElement.setAttribute111('tyt-comments-video-id', ytdFlexyCnt.videoId)
} else {
hostElement.removeAttribute000('tyt-comments-video-id')
}
},
'checkCommentsShouldBeHidden': (lockId) => {
if (lockGet['checkCommentsShouldBeHiddenLock'] !== lockId) return;
// commentsArea's attribute: tyt-comments-video-id
// ytdFlexyElm's attribute: video-id
const commentsArea = elements.comments;
const ytdFlexyElm = elements.flexy;
if (commentsArea && ytdFlexyElm && !commentsArea.hasAttribute000('hidden')) {
const ytdFlexyCnt = insp(ytdFlexyElm);
if (typeof ytdFlexyCnt.videoId === 'string') {
const commentsVideoId = commentsArea.getAttribute('tyt-comments-video-id');
if (commentsVideoId && commentsVideoId !== ytdFlexyCnt.videoId) {
commentsArea.setAttribute111('hidden', '');
// removeKeepCommentsScroller();
}
}
}
},
'ytd-comments::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-comments::attached']).catch(console.warn);
// Promise.resolve(this.hostElement).then(eventMap['ytd-comments::dataChanged_']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-comments::detached']).catch(console.warn);
// Promise.resolve(this.hostElement).then(eventMap['ytd-comments::dataChanged_']).catch(console.warn);
return this.detached498();
}
}
cProto._createPropertyObserver('data', '_dataChanged498', undefined)
cProto._dataChanged498 = function () {
// console.log('_dataChanged498', this.hostElement)
Promise.resolve(this.hostElement).then(eventMap['ytd-comments::_dataChanged498']).catch(console.warn);
}
// if (!cProto.dataChanged498_ && typeof cProto.dataChanged_ === 'function') {
// cProto.dataChanged498_ = cProto.dataChanged_;
// cProto.dataChanged_ = function () {
// Promise.resolve(this.hostElement).then(eventMap['ytd-comments::dataChanged_']).catch(console.warn);
// return this.dataChanged498_();
// }
// }
makeInitAttached('ytd-comments');
},
'ytd-comments::_dataChanged498': (hostElement) => {
// console.log(18984, hostElement.hasAttribute('tyt-comments-area'))
if (!hostElement.hasAttribute000('tyt-comments-area')) return;
let commentsDataStatus = 0;
const cnt = insp(hostElement);
const data = cnt ? cnt.data : null
const contents = data ? data.contents : null;
if (data) {
if (contents && contents.length === 1 && contents[0].messageRenderer) {
commentsDataStatus = 2;
}
if (contents && contents.length > 1 && contents[0].commentThreadRenderer) {
commentsDataStatus = 1;
}
}
if (commentsDataStatus) {
hostElement.setAttribute111('tyt-comments-data-status', commentsDataStatus);
// ytdFlexyElm.setAttribute111('tyt-comment-disabled', '')
} else {
// ytdFlexyElm.removeAttribute000('tyt-comment-disabled')
hostElement.removeAttribute000('tyt-comments-data-status')
}
Promise.resolve(hostElement).then(eventMap['settingCommentsVideoId']).catch(console.warn);
},
'ytd-comments::attached': async (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-comments::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
if (!hostElement || hostElement.id !== 'comments') return;
// if (!hostElement || hostElement.closest('[hidden]')) return;
elements.comments = hostElement;
console.log('ytd-comments::attached')
Promise.resolve(hostElement).then(eventMap['settingCommentsVideoId']).catch(console.warn);
aoComment.observe(hostElement, { attributes: true });
hostElement.setAttribute111('tyt-comments-area', '');
const lockId = lockSet['rightTabReadyLock02'];
await rightTabsProvidedPromise.then();
if (lockGet['rightTabReadyLock02'] !== lockId) return;
if (elements.comments !== hostElement) return;
if (hostElement.isConnected === false) return;
console.log(7932, 'comments');
// if(!elements.comments || elements.comments.isConnected === false) return;
if (hostElement && !hostElement.closest('#right-tabs')) {
document.querySelector('#tab-comments').assignChildren111(null, hostElement, null);
} else {
const shouldTabVisible = elements.comments && elements.comments.closest('#tab-comments') && !elements.comments.closest('[hidden]');
document.querySelector('[tyt-tab-content="#tab-comments"]').classList.toggle('tab-btn-hidden', !shouldTabVisible);
// document.querySelector('#tab-comments').classList.remove('tab-content-hidden')
// document.querySelector('[tyt-tab-content="#tab-comments"]').classList.remove('tab-btn-hidden')
Promise.resolve(lockSet['removeKeepCommentsScrollerLock']).then(removeKeepCommentsScroller).catch(console.warn);
}
},
'ytd-comments::detached': (hostElement) => {
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-comments::detached');
// console.log(858, hostElement)
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
if (hostElement.hasAttribute000('tyt-comments-area')) {
// foComments.disconnect();
// foComments.takeRecords();
hostElement.removeAttribute000('tyt-comments-area');
// document.querySelector('#tab-comments').classList.add('tab-content-hidden')
// document.querySelector('[tyt-tab-content="#tab-comments"]').classList.add('tab-btn-hidden')
aoComment.disconnect();
aoComment.takeRecords();
elements.comments = null;
document.querySelector('[tyt-tab-content="#tab-comments"]').classList.add('tab-btn-hidden');
Promise.resolve(lockSet['removeKeepCommentsScrollerLock']).then(removeKeepCommentsScroller).catch(console.warn);
}
},
'ytd-comments-header-renderer::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-comments-header-renderer::attached']).catch(console.warn);
Promise.resolve(this.hostElement).then(eventMap['ytd-comments-header-renderer::dataChanged']).catch(console.warn); // force dataChanged on attached
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-comments-header-renderer::detached']).catch(console.warn);
return this.detached498();
}
}
if (!cProto.dataChanged498 && typeof cProto.dataChanged === 'function') {
cProto.dataChanged498 = cProto.dataChanged;
cProto.dataChanged = function () {
Promise.resolve(this.hostElement).then(eventMap['ytd-comments-header-renderer::dataChanged']).catch(console.warn);
return this.dataChanged498();
}
}
makeInitAttached('ytd-comments-header-renderer');
},
'ytd-comments-header-renderer::attached': (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-comments-header-renderer::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
if (!hostElement || !hostElement.classList.contains('ytd-item-section-renderer')) return;
// console.log(12991, 'ytd-comments-header-renderer::attached')
const targetElement = document.querySelector('[tyt-comments-area] ytd-comments-header-renderer');
if (hostElement === targetElement) {
hostElement.setAttribute111('tyt-comments-header-field', '');
} else {
const parentNode = hostElement.parentNode;
if (parentNode instanceof HTMLElement_ && parentNode.querySelector('[tyt-comments-header-field]')) {
hostElement.setAttribute111('tyt-comments-header-field', '')
}
}
},
'ytd-comments-header-renderer::detached': (hostElement) => {
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-comments-header-renderer::detached');
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
// console.log(12992, 'ytd-comments-header-renderer::detached')
if (hostElement.hasAttribute000('field-of-cm-count')) {
hostElement.removeAttribute000('field-of-cm-count');
const cmCount = document.querySelector('#tyt-cm-count');
if (cmCount && !document.querySelector('#tab-comments ytd-comments-header-renderer[field-of-cm-count]')) {
cmCount.textContent = '';
}
}
if (hostElement.hasAttribute000('tyt-comments-header-field')) {
hostElement.removeAttribute000('tyt-comments-header-field');
}
},
'ytd-comments-header-renderer::dataChanged': (hostElement) => {
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
const ytdFlexyElm = elements.flexy;
let b = false;
const cnt = insp(hostElement);
if (cnt && hostElement.closest('#tab-comments') && document.querySelector('#tab-comments ytd-comments-header-renderer') === hostElement) {
b = true;
} else if (hostElement instanceof HTMLElement_ && hostElement.parentNode instanceof HTMLElement_ && hostElement.parentNode.querySelector('[tyt-comments-header-field]')) {
b = true;
}
if (b) {
hostElement.setAttribute111('tyt-comments-header-field', '')
ytdFlexyElm && ytdFlexyElm.removeAttribute000('tyt-comment-disabled');
}
if (hostElement.hasAttribute000('tyt-comments-header-field') && hostElement.isConnected === true) {
if (!headerMutationObserver) {
headerMutationObserver = new MutationObserver(eventMap['ytd-comments-header-renderer::deferredCounterUpdate']);
}
headerMutationObserver.observe(hostElement.parentNode, { subtree: false, childList: true })
if (!headerMutationTmpNode) headerMutationTmpNode = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
const tmpNode = headerMutationTmpNode;
hostElement.insertAdjacentElement("afterend", tmpNode);
tmpNode.remove();
}
},
'ytd-comments-header-renderer::deferredCounterUpdate': () => {
const nodes = document.querySelectorAll('#tab-comments ytd-comments-header-renderer[class]');
if (nodes.length === 1) {
const hostElement = nodes[0];
const cnt = insp(hostElement);
const data = cnt.data;
if (!data) return;
let ez = '';
if (data.commentsCount && data.commentsCount.runs && data.commentsCount.runs.length >= 1) {
let max = -1;
const z = data.commentsCount.runs.map(e => {
let c = e.text.replace(/\D+/g, '').length;
if (c > max) max = c;
return [e.text, c];
}).filter(a => a[1] === max);
if (z.length >= 1) {
ez = z[0][0];
}
} else if (data.countText && data.countText.runs && data.countText.runs.length >= 1) {
let max = -1;
const z = data.countText.runs.map(e => {
let c = e.text.replace(/\D+/g, '').length;
if (c > max) max = c;
return [e.text, c];
}).filter(a => a[1] === max);
if (z.length >= 1) {
ez = z[0][0];
}
}
const cmCount = document.querySelector('#tyt-cm-count');
if (ez) {
hostElement.setAttribute111('field-of-cm-count', '');
cmCount && (cmCount.textContent = ez.trim());
} else {
hostElement.removeAttribute000('field-of-cm-count');
cmCount && (cmCount.textContent = '');
console.warn('no text for #tyt-cm-count')
}
}
},
'ytd-expander::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-expander::attached']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-expander::detached']).catch(console.warn);
return this.detached498();
}
}
if (!cProto.calculateCanCollapse498 && typeof cProto.calculateCanCollapse === 'function') {
cProto.calculateCanCollapse498 = cProto.calculateCanCollapse;
cProto.calculateCanCollapse = funcCanCollapse;
}
if (!cProto.childrenChanged498 && typeof cProto.childrenChanged === 'function') {
cProto.childrenChanged498 = cProto.childrenChanged;
cProto.childrenChanged = function () {
Promise.resolve(this.hostElement).then(eventMap['ytd-expander::childrenChanged']).catch(console.warn);
return this.childrenChanged498();
}
}
/*
console.log('ytd-expander::defined 01');
CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.connectedCallback = connectedCallbackY(CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.connectedCallback)
CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.disconnectedCallback = disconnectedCallbackY(CustomElementRegistry.prototype.get.call(customElements, 'ytd-expander').prototype.disconnectedCallback)
console.log('ytd-expander::defined 02');
*/
makeInitAttached('ytd-expander');
},
'ytd-expander::childrenChanged': (hostElement) => {
if (hostElement instanceof Node && hostElement.hasAttribute000('hidden') && hostElement.hasAttribute000('tyt-main-info') && hostElement.firstElementChild) {
hostElement.removeAttribute('hidden');
}
},
'ytd-expandable-video-description-body-renderer::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-expandable-video-description-body-renderer::attached']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-expandable-video-description-body-renderer::detached']).catch(console.warn);
return this.detached498();
}
}
makeInitAttached('ytd-expandable-video-description-body-renderer')
},
'ytd-expandable-video-description-body-renderer::attached': async (hostElement) => {
if (hostElement instanceof HTMLElement_ && isPageDOM(hostElement, '[tyt-info-renderer]') && !hostElement.matches('[tyt-main-info]')) {
elements.infoExpander = hostElement;
console.log(128384, elements.infoExpander)
// console.log(1299, hostElement.parentNode, isRightTabsInserted)
infoExpanderElementProvidedPromise.resolve();
hostElement.setAttribute111('tyt-main-info', '');
if (plugin.autoExpandInfoDesc.toUse) {
plugin.autoExpandInfoDesc.onMainInfoSet(hostElement);
}
const lockId = lockSet['rightTabReadyLock03'];
await rightTabsProvidedPromise.then();
if (lockGet['rightTabReadyLock03'] !== lockId) return;
if (elements.infoExpander !== hostElement) return;
if (hostElement.isConnected === false) return;
console.log(7932, 'infoExpander');
elements.infoExpander.classList.add('tyt-main-info'); // add a classname for it
const infoExpander = elements.infoExpander;
// const infoExpanderBack = elements.infoExpanderBack;
// console.log(5438,infoExpander, qt);
// const dummy = document.createElement('noscript');
// dummy.setAttribute000('id', 'info-expander-vid');
// dummy.setAttribute000('video-id', getCurrentVideoId());
// infoExpander.insertBefore000(dummy, infoExpander.firstChild);
// aoInfo.observe(infoExpander, { attributes: true, attributeFilter: ['tyt-display-for', 'tyt-video-id'] });
// zoInfo.observe(infoExpanderBack, { attributes: true, attributeFilter: ['hidden', 'attr-w20ts'], childList: true, subtree: true});
// new MutationObserver(()=>{
// console.log(591499)
// }).observe(infoExpanderBack, {childList: true, subtree: true})
const inlineExpanderElm = infoExpander.querySelector('ytd-text-inline-expander');
if (inlineExpanderElm) {
const mo = new MutationObserver(() => {
const p = document.querySelector('#tab-info ytd-text-inline-expander');
sessionStorage.__$$tmp_UseAutoExpandInfoDesc$$__ = p && p.hasAttribute('is-expanded') ? '1' : '';
if (p) fixInlineExpanderContent();
});
mo.observe(inlineExpanderElm, { attributes: ['is-expanded', 'attr-6v8qu', 'hidden'], subtree: true }); // hidden + subtree to trigger the fn by delayedUpdate
inlineExpanderElm.incAttribute111('attr-6v8qu');
const cnt = insp(inlineExpanderElm);
if (cnt) fixInlineExpanderDisplay(cnt);
}
if (infoExpander && !infoExpander.closest('#right-tabs')) {
document.querySelector('#tab-info').assignChildren111(null, infoExpander, null);
} else {
if (document.querySelector('[tyt-tab-content="#tab-info"]')) {
const shouldTabVisible = elements.infoExpander && elements.infoExpander.closest('#tab-info');
document.querySelector('[tyt-tab-content="#tab-info"]').classList.toggle('tab-btn-hidden', !shouldTabVisible);
}
}
Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn); // required when the page is switched from channel to watch
// if (infoExpander && infoExpander.closest('#right-tabs')) Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
// infoExpanderBack.incAttribute111('attr-w20ts');
// return;
}
DEBUG_5084 && console.log(5084, 'ytd-expandable-video-description-body-renderer::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
if (isPageDOM(hostElement, '#tab-info [tyt-main-info]')) {
// const cnt = insp(hostElement);
// if(cnt.data){
// cnt.data = Object.assign({}, cnt.data);
// }
} else if (!hostElement.closest('#tab-info')) {
const bodyRenderer = hostElement;
let bodyRendererNew = document.querySelector('ytd-expandable-video-description-body-renderer[tyt-info-renderer]');
if (!bodyRendererNew) {
bodyRendererNew = document.createElement('ytd-expandable-video-description-body-renderer');
bodyRendererNew.setAttribute('tyt-info-renderer', '');
nsTemplateObtain().appendChild(bodyRendererNew);
}
// document.querySelector('#tab-info').assignChildren111(null, bodyRendererNew, null);
const cnt = insp(bodyRendererNew);
cnt.data = Object.assign({}, insp(bodyRenderer).data);
const inlineExpanderElm = bodyRendererNew.querySelector('ytd-text-inline-expander');
const inlineExpanderCnt = insp(inlineExpanderElm);
fixInlineExpanderMethods(inlineExpanderCnt);
// insp(bodyRendererNew).data = insp(bodyRenderer).data;
// if((bodyRendererNew.hasAttribute('hidden')?1:0)^(bodyRenderer.hasAttribute('hidden')?1:0)){
// if(bodyRenderer.hasAttribute('hidden')) bodyRendererNew.setAttribute('hidden', '');
// else bodyRendererNew.removeAttribute('hidden');
// }
elements.infoExpanderRendererBack = bodyRenderer;
elements.infoExpanderRendererFront = bodyRendererNew;
bodyRenderer.setAttribute('tyt-info-renderer-back', '');
bodyRendererNew.setAttribute('tyt-info-renderer-front', '');
// elements.infoExpanderBack = {{ytd-expander}};
}
},
'ytd-expandable-video-description-body-renderer::detached': async (hostElement) => {
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
// console.log(5992, hostElement)
if (hostElement.hasAttribute000('tyt-main-info')) {
DEBUG_5084 && console.log(5084, 'ytd-expandable-video-description-body-renderer::detached');
elements.infoExpander = null;
hostElement.removeAttribute000('tyt-main-info');
}
},
'ytd-expander::attached': async (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
// console.log(4959, hostElement)
if (hostElement instanceof HTMLElement_ && hostElement.matches('[tyt-comments-area] #contents ytd-expander#expander') && !hostElement.matches('[hidden] ytd-expander#expander')) {
hostElement.setAttribute111('tyt-content-comment-entry', '')
ioComment.observe(hostElement);
}
// --------------
// else if (hostElement instanceof HTMLElement_ && hostElement.matches('ytd-expander#expander.style-scope.ytd-expandable-video-description-body-renderer')) {
// // && !hostElement.matches('#right-tabs ytd-expander#expander, [hidden] ytd-expander#expander')
// console.log(5084, 'ytd-expander::attached');
// const bodyRenderer = hostElement.closest('ytd-expandable-video-description-body-renderer');
// let bodyRendererNew = document.querySelector('ytd-expandable-video-description-body-renderer[tyt-info-renderer]');
// if (!bodyRendererNew) {
// bodyRendererNew = document.createElement('ytd-expandable-video-description-body-renderer');
// bodyRendererNew.setAttribute('tyt-info-renderer', '');
// nsTemplateObtain().appendChild(bodyRendererNew);
// }
// // document.querySelector('#tab-info').assignChildren111(null, bodyRendererNew, null);
// insp(bodyRendererNew).data = insp(bodyRenderer).data;
// // if((bodyRendererNew.hasAttribute('hidden')?1:0)^(bodyRenderer.hasAttribute('hidden')?1:0)){
// // if(bodyRenderer.hasAttribute('hidden')) bodyRendererNew.setAttribute('hidden', '');
// // else bodyRendererNew.removeAttribute('hidden');
// // }
// elements.infoExpanderRendererBack = bodyRenderer;
// elements.infoExpanderRendererFront = bodyRendererNew;
// bodyRenderer.setAttribute('tyt-info-renderer-back','')
// bodyRendererNew.setAttribute('tyt-info-renderer-front','')
// elements.infoExpanderBack = hostElement;
// }
// --------------
// console.log('ytd-expander::attached', hostElement);
},
'ytd-expander::detached': (hostElement) => {
// if (inPageRearrange) return;
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
// console.log(5992, hostElement)
if (hostElement.hasAttribute000('tyt-content-comment-entry')) {
ioComment.unobserve(hostElement);
hostElement.removeAttribute000('tyt-content-comment-entry')
} else if (hostElement.hasAttribute000('tyt-main-info')) {
DEBUG_5084 && console.log(5084, 'ytd-expander::detached');
elements.infoExpander = null;
hostElement.removeAttribute000('tyt-main-info');
}
// console.log('ytd-expander::detached', hostElement);
},
'ytd-live-chat-frame::defined': (cProto) => {
let lastDomAction = 0;
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
lastDomAction = Date.now();
// console.log('chat868-attached', Date.now());
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-live-chat-frame::attached']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
lastDomAction = Date.now();
// console.log('chat868-detached', Date.now());
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-live-chat-frame::detached']).catch(console.warn);
return this.detached498();
}
}
if (typeof cProto.urlChanged === 'function' && !cProto.urlChanged66 && !cProto.urlChangedAsync12 && cProto.urlChanged.length === 0) {
cProto.urlChanged66 = cProto.urlChanged;
let ath = 0;
cProto.urlChangedAsync12 = async function () {
await this.__urlChangedAsyncT689__;
const t = ath = (ath & 1073741823) + 1;
const chatframe = this.chatframe || (this.$ || 0).chatframe || 0;
if (chatframe instanceof HTMLIFrameElement) {
if (chatframe.contentDocument === null) {
await Promise.resolve('#').catch(console.warn);
if (t !== ath) return;
}
await new Promise(resolve => setTimeout_(resolve, 1)).catch(console.warn); // neccessary for Brave
if (t !== ath) return;
const isBlankPage = !this.data || this.collapsed;
const p1 = new Promise(resolve => setTimeout_(resolve, 706)).catch(console.warn);
const p2 = new Promise(resolve => {
(new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
const rect = entry.boundingClientRect || 0;
if (isBlankPage || (rect.width > 0 && rect.height > 0)) {
observer.disconnect();
resolve('#');
break;
}
}
})).observe(chatframe);
}).catch(console.warn);
await Promise.race([p1, p2]);
if (t !== ath) return;
}
this.urlChanged66();
}
cProto.urlChanged = function () {
const t = this.__urlChangedAsyncT688__ = (this.__urlChangedAsyncT688__ & 1073741823) + 1;
nextBrowserTick(() => {
if (t !== this.__urlChangedAsyncT688__) return;
this.urlChangedAsync12();
});
}
}
makeInitAttached('ytd-live-chat-frame');
},
'ytd-live-chat-frame::attached': async (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-live-chat-frame::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
if (!hostElement || hostElement.id !== 'chat') return;
console.log('ytd-live-chat-frame::attached')
const lockId = lockSet['ytdLiveAttachedLock'];
const chatElem = await getGeneralChatElement();
if (lockGet['ytdLiveAttachedLock'] !== lockId) return;
if (chatElem === hostElement) {
elements.chat = chatElem;
aoChat.observe(chatElem, { attributes: true });
const isFlexyReady = (elements.flexy instanceof Element);
chatElem.setAttribute111('tyt-active-chat-frame', isFlexyReady ? 'CF' : 'C');
const chatContainer = chatElem ? (chatElem.closest('#chat-container') || chatElem) : null;
if (chatContainer && !chatContainer.hasAttribute000('tyt-chat-container')) {
for (const p of document.querySelectorAll('[tyt-chat-container]')) {
p.removeAttribute000('[tyt-chat-container]');
}
chatContainer.setAttribute111('tyt-chat-container', '')
}
const cnt = insp(hostElement);
const q = cnt.__urlChangedAsyncT688__;
const p = cnt.__urlChangedAsyncT689__ = new PromiseExternal();
setTimeout_(() => {
if (p !== cnt.__urlChangedAsyncT689__) return;
if (cnt.isAttached === true && hostElement.isConnected === true) {
p.resolve();
if (q === cnt.__urlChangedAsyncT688__) {
cnt.urlChanged();
}
}
}, 320);
Promise.resolve(lockSet['layoutFixLock']).then(layoutFix);
} else {
console.log('Issue found in ytd-live-chat-frame::attached', chatElem, hostElement);
}
},
'ytd-live-chat-frame::detached': (hostElement) => {
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-live-chat-frame::detached');
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
console.log('ytd-live-chat-frame::detached')
if (hostElement.hasAttribute000('tyt-active-chat-frame')) {
aoChat.disconnect();
aoChat.takeRecords();
hostElement.removeAttribute000('tyt-active-chat-frame');
elements.chat = null;
const ytdFlexyElm = elements.flexy;
if (ytdFlexyElm) {
ytdFlexyElm.removeAttribute000('tyt-chat-collapsed')
ytdFlexyElm.setAttribute111('tyt-chat', '');
}
}
},
'ytd-engagement-panel-section-list-renderer::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-engagement-panel-section-list-renderer::attached']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-engagement-panel-section-list-renderer::detached']).catch(console.warn);
return this.detached498();
}
}
makeInitAttached('ytd-engagement-panel-section-list-renderer');
},
'ytd-engagement-panel-section-list-renderer::bindTarget': (hostElement) => {
if (hostElement.matches('#panels.ytd-watch-flexy > ytd-engagement-panel-section-list-renderer[target-id][visibility]')) {
hostElement.setAttribute111('tyt-egm-panel', '');
Promise.resolve(lockSet['updateEgmPanelsLock']).then(updateEgmPanels).catch(console.warn);
aoEgmPanels.observe(hostElement, { attributes: true, attributeFilter: ['visibility', 'hidden'] });
// console.log(5094, 2, 'ytd-engagement-panel-section-list-renderer::attached', hostElement);
}
},
'ytd-engagement-panel-section-list-renderer::attached': (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-engagement-panel-section-list-renderer::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
// console.log('ytd-engagement-panel-section-list-renderer::attached', hostElement)
// console.log(5094, 1, 'ytd-engagement-panel-section-list-renderer::attached', hostElement);
if (!hostElement.matches('#panels.ytd-watch-flexy > ytd-engagement-panel-section-list-renderer')) return;
if (hostElement.hasAttribute000('target-id') && hostElement.hasAttribute000('visibility')) {
Promise.resolve(hostElement).then(eventMap['ytd-engagement-panel-section-list-renderer::bindTarget']).catch(console.warn);
} else {
hostElement.setAttribute000('tyt-egm-panel-jclmd', '');
moEgmPanelReady.observe(hostElement, { attributes: true, attributeFilter: ['visibility', 'target-id'] });
}
},
'ytd-engagement-panel-section-list-renderer::detached': (hostElement) => {
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-engagement-panel-section-list-renderer::detached');
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
if (hostElement.hasAttribute000('tyt-egm-panel')) {
hostElement.removeAttribute000('tyt-egm-panel');
Promise.resolve(lockSet['updateEgmPanelsLock']).then(updateEgmPanels).catch(console.warn);
} else if (hostElement.hasAttribute000('tyt-egm-panel-jclmd')) {
hostElement.removeAttribute000('tyt-egm-panel-jclmd');
moEgmPanelReadyClearFn();
}
},
'ytd-watch-metadata::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-watch-metadata::attached']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-watch-metadata::detached']).catch(console.warn);
return this.detached498();
}
}
makeInitAttached('ytd-watch-metadata');
},
'ytd-watch-metadata::attached': (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-watch-metadata::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
if (plugin.fullChannelNameOnHover.activated) plugin.fullChannelNameOnHover.onNavigateFinish();
},
'ytd-watch-metadata::detached': (hostElement) => {
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-watch-metadata::detached');
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
},
'ytd-playlist-panel-renderer::defined': (cProto) => {
if (!cProto.attached498 && typeof cProto.attached === 'function') {
cProto.attached498 = cProto.attached;
cProto.attached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-playlist-panel-renderer::attached']).catch(console.warn);
return this.attached498();
}
}
if (!cProto.detached498 && typeof cProto.detached === 'function') {
cProto.detached498 = cProto.detached;
cProto.detached = function () {
if (!inPageRearrange) Promise.resolve(this.hostElement).then(eventMap['ytd-playlist-panel-renderer::detached']).catch(console.warn);
return this.detached498();
}
}
makeInitAttached('ytd-playlist-panel-renderer');
},
'ytd-playlist-panel-renderer::attached': (hostElement) => {
if (invalidFlexyParent(hostElement)) return;
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-playlist-panel-renderer::attached');
if (hostElement instanceof Element) hostElement[__attachedSymbol__] = true;
if (!(hostElement instanceof HTMLElement_) || !(hostElement.classList.length > 0) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== true) return;
// if (hostElement.__connectedFlg__ !== 4) return;
// hostElement.__connectedFlg__ = 5;
elements.playlist = hostElement;
aoPlayList.observe(hostElement, { attributes: true, attributeFilter: ['hidden', 'collapsed', 'attr-1y6nu'] });
hostElement.incAttribute111('attr-1y6nu')
},
'ytd-playlist-panel-renderer::detached': (hostElement) => {
// if (inPageRearrange) return;
DEBUG_5084 && console.log(5084, 'ytd-playlist-panel-renderer::detached');
if (!(hostElement instanceof HTMLElement_) || hostElement.closest('noscript')) return;
if (hostElement.isConnected !== false) return;
// if (hostElement.__connectedFlg__ !== 8) return;
// hostElement.__connectedFlg__ = 9;
},
'_yt_playerProvided': () => {
mLoaded.flag |= 4;
document.documentElement.setAttribute111('tabview-loaded', mLoaded.makeString());
},
'relatedElementProvided': (target) => {
if (target.closest('[hidden]')) return;
elements.related = target;
console.log('relatedElementProvided')
videosElementProvidedPromise.resolve();
},
'onceInfoExpanderElementProvidedPromised': () => {
console.log('hide-default-text-inline-expander');
const ytdFlexyElm = elements.flexy;
if (ytdFlexyElm) {
ytdFlexyElm.setAttribute111('hide-default-text-inline-expander', '');
}
},
'refreshSecondaryInner': (lockId) => {
if (lockGet['refreshSecondaryInnerLock'] !== lockId) return;
/*
ytd-watch-flexy:not([panels-beside-player]):not([fixed-panels]) #panels-full-bleed-container.ytd-watch-flexy{
display: none;}
#player-full-bleed-container.ytd-watch-flexy{
position: relative;
flex: 1;}
*/
const ytdFlexyElm = elements.flexy;
// if(ytdFlexyElm && ytdFlexyElm.matches('ytd-watch-flexy[fixed-panels][theater]')){
// // ytdFlexyElm.fixedPanels = true;
// ytdFlexyElm.removeAttribute000('fixed-panels');
// }
if (ytdFlexyElm && ytdFlexyElm.matches('ytd-watch-flexy[theater][flexy][full-bleed-player]:not([full-bleed-no-max-width-columns])')) {
// ytdFlexyElm.fullBleedNoMaxWidthColumns = true;
ytdFlexyElm.setAttribute111('full-bleed-no-max-width-columns', '');
}
const related = elements.related;
if (related && related.isConnected && !related.closest('#right-tabs #tab-videos')) {
document.querySelector('#tab-videos').assignChildren111(null, related, null);
}
const infoExpander = elements.infoExpander;
if (infoExpander && infoExpander.isConnected && !infoExpander.closest('#right-tabs #tab-info')) {
document.querySelector('#tab-info').assignChildren111(null, infoExpander, null);
} else {
// if (infoExpander && ytdFlexyElm && shouldFixInfo) {
// shouldFixInfo = false;
// Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
// }
}
const commentsArea = elements.comments;
if (commentsArea) {
const isConnected = commentsArea.isConnected;
if (isConnected && !commentsArea.closest('#right-tabs #tab-comments')) {
const tab = document.querySelector('#tab-comments');
tab.assignChildren111(null, commentsArea, null);
} else {
// if (!isConnected || tab.classList.contains('tab-content-hidden')) removeKeepCommentsScroller();
}
}
},
'yt-navigate-finish': (evt) => {
const ytdAppElm = document.querySelector('ytd-page-manager#page-manager.style-scope.ytd-app');
const ytdAppCnt = insp(ytdAppElm);
pageType = ytdAppCnt ? (ytdAppCnt.data || 0).page : null;
if (!document.querySelector('ytd-watch-flexy #player')) return;
// shouldFixInfo = true;
// console.log('yt-navigate-finish')
const flexyArr = [...document.querySelectorAll('ytd-watch-flexy')].filter(e => !e.closest('[hidden]') && e.querySelector('#player'));
if (flexyArr.length === 1) {
// const lockId = lockSet['yt-navigate-finish-videos'];
elements.flexy = flexyArr[0];
if (isRightTabsInserted) {
Promise.resolve(lockSet['refreshSecondaryInnerLock']).then(eventMap['refreshSecondaryInner']).catch(console.warn);
Promise.resolve(lockSet['removeKeepCommentsScrollerLock']).then(removeKeepCommentsScroller).catch(console.warn);
} else {
navigateFinishedPromise.resolve();
if (plugin.minibrowser.toUse) plugin.minibrowser.activate();
if (plugin.autoExpandInfoDesc.toUse) plugin.autoExpandInfoDesc.activate();
if (plugin.fullChannelNameOnHover.toUse) plugin.fullChannelNameOnHover.activate();
}
const chat = elements.chat;
if (chat instanceof Element) {
chat.setAttribute111('tyt-active-chat-frame', 'CF'); // chat and flexy ready
}
const infoExpander = elements.infoExpander;
if (infoExpander && infoExpander.closest('#right-tabs')) {
Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
}
Promise.resolve(lockSet['layoutFixLock']).then(layoutFix);
if (plugin.fullChannelNameOnHover.activated) plugin.fullChannelNameOnHover.onNavigateFinish();
}
},
'onceInsertRightTabs': () => {
// if(lockId !== lockGet['yt-navigate-finish-videos']) return;
const related = elements.related;
let rightTabs = document.querySelector('#right-tabs');
if (!document.querySelector('#right-tabs') && related) {
getLangForPage();
let docTmp = document.createElement('template');
docTmp.innerHTML = createHTML(getTabsHTML());
let newElm = docTmp.content.firstElementChild;
if (newElm !== null) {
inPageRearrange = true;
related.parentNode.insertBefore000(newElm, related);
inPageRearrange = false;
}
rightTabs = newElm;
rightTabs.querySelector('[tyt-tab-content="#tab-comments"]').classList.add('tab-btn-hidden');
const secondaryWrapper = document.createElement('secondary-wrapper');
const secondaryInner = document.querySelector('#secondary-inner.style-scope.ytd-watch-flexy');
inPageRearrange = true;
secondaryWrapper.replaceChildren000(...secondaryInner.childNodes);
secondaryInner.insertBefore000(secondaryWrapper, secondaryInner.firstChild);
inPageRearrange = false;
rightTabs.querySelector('#material-tabs').addEventListener('click', eventMap['tabs-btn-click'], true)
inPageRearrange = true;
if (!rightTabs.closest('secondary-wrapper')) secondaryWrapper.appendChild000(rightTabs);
inPageRearrange = false;
}
if (rightTabs) {
isRightTabsInserted = true;
const ioTabBtns = new IntersectionObserver((entries) => {
for (const entry of entries) {
const rect = entry.boundingClientRect;
entry.target.classList.toggle('tab-btn-visible', rect.width && rect.height)
}
}, { rootMargin: '0px' });
for (const btn of document.querySelectorAll('.tab-btn[tyt-tab-content]')) {
ioTabBtns.observe(btn);
}
if (!related.closest('#right-tabs')) {
document.querySelector('#tab-videos').assignChildren111(null, related, null);
}
const infoExpander = elements.infoExpander;
if (infoExpander && !infoExpander.closest('#right-tabs')) {
document.querySelector('#tab-info').assignChildren111(null, infoExpander, null);
}
const commentsArea = elements.comments;
if (commentsArea && !commentsArea.closest('#right-tabs')) {
document.querySelector('#tab-comments').assignChildren111(null, commentsArea, null);
}
rightTabsProvidedPromise.resolve();
roRightTabs.disconnect();
roRightTabs.observe(rightTabs);
const ytdFlexyElm = elements.flexy;
const aoFlexy = new MutationObserver(eventMap['aoFlexyFn']);
aoFlexy.observe(ytdFlexyElm, { attributes: true });
// Promise.resolve(lockSet['tabsStatusCorrectionLock']).then(eventMap['tabsStatusCorrection']).catch(console.warn);
Promise.resolve(lockSet['fixInitialTabStateLock']).then(eventMap['fixInitialTabStateFn']).catch(console.warn);
ytdFlexyElm.incAttribute111('attr-7qlsy'); // tabsStatusCorrectionLock and video-id
}
},
'aoFlexyFn': () => {
Promise.resolve(lockSet['checkCommentsShouldBeHiddenLock']).then(eventMap['checkCommentsShouldBeHidden']).catch(console.warn);
Promise.resolve(lockSet['refreshSecondaryInnerLock']).then(eventMap['refreshSecondaryInner']).catch(console.warn);
Promise.resolve(lockSet['tabsStatusCorrectionLock']).then(eventMap['tabsStatusCorrection']).catch(console.warn);
const videoId = getCurrentVideoId();
if (videoId !== tmpLastVideoId) {
tmpLastVideoId = videoId;
Promise.resolve(lockSet['updateOnVideoIdChangedLock']).then(eventMap['updateOnVideoIdChanged']).catch(console.warn);
}
},
'twoColumnChanged10': (lockId) => {
if (lockId !== lockGet['twoColumnChanged10Lock']) return;
for (const continuation of document.querySelectorAll('#tab-videos ytd-watch-next-secondary-results-renderer ytd-continuation-item-renderer')) {
if (continuation.closest('[hidden]')) continue;
const cnt = insp(continuation);
if (typeof cnt.showButton === 'boolean') {
if (cnt.showButton === false) continue;
cnt.showButton = false;
const behavior = (cnt.ytRendererBehavior || cnt);
if (typeof behavior.invalidate === 'function') {
behavior.invalidate(!1);
}
}
}
},
'tabsStatusCorrection': (lockId) => {
if (lockId !== lockGet['tabsStatusCorrectionLock']) return;
const ytdFlexyElm = elements.flexy;
if (!ytdFlexyElm) return;
const p = tabAStatus;
const q = calculationFn(p, 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128);
let resetForPanelDisappeared = false;
if (p !== q) {
console.log(388, p, q)
let actioned = false;
if ((p & 128) === 0 && (q & 128) === 128) {
lastPanel = 'playlist'
} else if ((p & 8) === 0 && (q & 8) === 8) {
lastPanel = 'chat'
} else if ((((p & 4) == 4 && (q & (4 | 8)) == (0 | 0)) || ((p & 8) == 8 && (q & (4 | 8)) === (0 | 0))) && lastPanel === 'chat') {
// 24 -> 16 = -8; 'd'
lastPanel = (lastTab || '');
resetForPanelDisappeared = true;
} else if ((p & (4 | 8)) === 8 && (q & (4 | 8)) === 4 && lastPanel === 'chat') {
// click close
lastPanel = (lastTab || '');
resetForPanelDisappeared = true;
} else if ((p & 128) === 128 && (q & 128) === 0 && lastPanel === 'playlist') {
lastPanel = (lastTab || '');
resetForPanelDisappeared = true;
}
tabAStatus = q;
let bFixForResizedTab = false;
if ((q ^ 2) === 2 && bFixForResizedTabLater) {
bFixForResizedTab = true;
}
if ((p & 16) === 16 & (q & 16) === 0) {
Promise.resolve(lockSet['twoColumnChanged10Lock']).then(eventMap['twoColumnChanged10']).catch(console.warn);
}
if ((((p & 2) === 2) ^ ((q & 2) === 2)) && ((q & 2) === 2)) {
bFixForResizedTab = true;
}
// p->q +2
if ((p & 2) === 0 && (q & 2) === 2 && (p & 128) === 128 && (q & 128) === 128) {
lastPanel = (lastTab || '');
ytBtnClosePlaylist();
actioned = true;
}
// p->q +8
if ((p & (8 | 128)) === (0 | 128) && (q & (8 | 128)) === (8 | 128) && lastPanel === 'chat') {
lastPanel = (lastTab || '');
ytBtnClosePlaylist();
actioned = true;
}
// p->q +128
if ((p & (2 | 128)) === (2 | 0) && (q & (2 | 128)) === (2 | 128) && lastPanel === 'playlist') {
switchToTab(null);
actioned = true;
}
// p->q +128
if ((p & (8 | 128)) === (8 | 0) && (q & (8 | 128)) === (8 | 128) && lastPanel === 'playlist') {
lastPanel = (lastTab || '');
ytBtnCollapseChat();
actioned = true;
}
// p->q +128
if ((p & (1 | 16 | 128)) == (1 | 16) && (q & (1 | 16 | 128)) == (1 | 16 | 128)) {
ytBtnCancelTheater();
actioned = true;
}
// p->q +1
if ((p & (1 | 16 | 128)) == (16 | 128) && (q & (1 | 16 | 128)) == (1 | 16 | 128)) {
lastPanel = (lastTab || '');
ytBtnClosePlaylist();
actioned = true;
}
if ((q & 64) === 64) {
actioned = false;
} else if ((p & 64) == 64 && (q & 64) === 0) {
// p->q -64
if ((q & 32) === 32) {
ytBtnCloseEngagementPanels();
}
if ((q & (2 | 8)) === (2 | 8)) {
if (lastPanel === 'chat') {
switchToTab(null);
actioned = true;
} else if (lastPanel) {
ytBtnCollapseChat();
actioned = true;
}
}
} else if ((p & (1 | 2 | 8 | 16 | 32)) === (1 | 0 | 0 | 16 | 0) && (q & (1 | 2 | 8 | 16 | 32)) === (1 | 0 | 8 | 16 | 0)) {
// p->q +8
ytBtnCancelTheater();
actioned = true;
} else if ((p & (1 | 16 | 32)) === (0 | 16 | 0) && (q & (1 | 16 | 32)) === (0 | 16 | 32) && (q & (2 | 8)) > 0) {
// p->q +32
if (q & 2) {
switchToTab(null);
actioned = true;
}
if (q & 8) {
ytBtnCollapseChat();
actioned = true;
}
} else if ((p & (1 | 16 | 8 | 2)) === (16 | 8) && (q & (1 | 16 | 8 | 2)) === (16) && (q & 128) === 0) {
// p->q -8
if (lastTab) {
switchToTab(lastTab)
actioned = true;
}
} else if ((p & 1) === 0 && (q & 1) === 1) {
// p->q +1
if ((q & 32) === 32) {
ytBtnCloseEngagementPanels();
}
if ((p & 9) === 8 && (q & 9) === 9) {
ytBtnCollapseChat();
}
switchToTab(null)
actioned = true;
} else if ((p & 3) === 1 && (q & 3) === 3) {
// p->q +2
ytBtnCancelTheater();
actioned = true;
} else if ((p & 10) === 2 && (q & 10) === 10) {
// p->q +8
switchToTab(null)
actioned = true;
} else if ((p & (8 | 32)) === (0 | 32) && (q & (8 | 32)) === (8 | 32)) {
// p->q +8
ytBtnCloseEngagementPanels();
actioned = true;
} else if ((p & (2 | 32)) === (0 | 32) && (q & (2 | 32)) === (2 | 32)) {
// p->q +2
ytBtnCloseEngagementPanels();
actioned = true;
} else if ((p & (2 | 8)) === (0 | 8) && (q & (2 | 8)) === (2 | 8)) {
// p->q +2
ytBtnCollapseChat();
actioned = true;
// if( lastPanel && (p & (1|16) === 16) && (q & (1 | 16 | 8 | 2)) === (16) ){
// switchToTab(lastTab)
// actioned = true;
// }
} else if ((p & 1) === 1 && (q & (1 | 32)) === (0 | 0)) {
// p->q -1
if (lastPanel === 'chat') {
ytBtnExpandChat()
actioned = true;
} else if (lastPanel === lastTab && lastTab) {
switchToTab(lastTab)
actioned = true;
}
}
// 24 20
// 8 16 4 16
if (!actioned && (q & 128) === 128) {
lastPanel = 'playlist'
if ((q & 2) === 2) {
switchToTab(null)
actioned = true;
}
}
if ((p & 2) === 2 && (q & (2 | 128)) === (0 | 128)) {
// p->q -2
} else if ((p & 8) === 8 && (q & (8 | 128)) === (0 | 128)) {
// p->q -8
} else if (!actioned && (p & (1 | 16)) === 16 && (q & (1 | 16 | 8 | 2 | 32 | 64)) === (16 | 0 | 0)) {
console.log(388, 'd')
if (lastPanel === 'chat') {
console.log(388, 'd1c')
ytBtnExpandChat()
actioned = true;
} else if (lastPanel === 'playlist') {
console.log(388, 'd1p')
ytBtnOpenPlaylist()
actioned = true;
} else if (lastTab) {
console.log(388, 'd2t')
switchToTab(lastTab)
actioned = true;
} else if (resetForPanelDisappeared) {
// if lastTab is undefined
console.log(388, 'd2d')
Promise.resolve(lockSet['fixInitialTabStateLock']).then(eventMap['fixInitialTabStateFn']).catch(console.warn);
actioned = true;
}
}
if (bFixForResizedTab) {
bFixForResizedTabLater = false;
Promise.resolve(0).then(eventMap['fixForTabDisplay']).catch(console.warn);
}
if (((p & 16) === 16) ^ ((q & 16) === 16)) {
Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
Promise.resolve(lockSet['removeKeepCommentsScrollerLock']).then(removeKeepCommentsScroller).catch(console.warn);
Promise.resolve(lockSet['layoutFixLock']).then(layoutFix).catch(console.warn);
}
}
},
'updateOnVideoIdChanged': (lockId) => {
if (lockId !== lockGet['updateOnVideoIdChangedLock']) return;
const videoId = tmpLastVideoId;
if (!videoId) return;
const bodyRenderer = elements.infoExpanderRendererBack;
const bodyRendererNew = elements.infoExpanderRendererFront;
if (bodyRendererNew && bodyRenderer) {
insp(bodyRendererNew).data = insp(bodyRenderer).data;
// if ((bodyRendererNew.hasAttribute('hidden') ? 1 : 0) ^ (bodyRenderer.hasAttribute('hidden') ? 1 : 0)) {
// if (bodyRenderer.hasAttribute('hidden')) bodyRendererNew.setAttribute('hidden', '');
// else bodyRendererNew.removeAttribute('hidden');
// }
}
Promise.resolve(lockSet['infoFixLock']).then(infoFix).catch(console.warn);
},
'fixInitialTabStateFn': async (lockId) => {
// console.log('fixInitialTabStateFn 0a');
if (lockGet['fixInitialTabStateLock'] !== lockId) return;
// console.log('fixInitialTabStateFn 0b');
const delayTime = fixInitialTabStateK > 0 ? 200 : 1;
await delayPn(delayTime);
if (lockGet['fixInitialTabStateLock'] !== lockId) return;
// console.log('fixInitialTabStateFn 0c');
const kTab = document.querySelector('[tyt-tab]');
const qTab = (!kTab || kTab.getAttribute('tyt-tab') === '') ? checkElementExist('ytd-watch-flexy[is-two-columns_]', '[hidden]') : null;
if (checkElementExist('ytd-playlist-panel-renderer#playlist', '[hidden], [collapsed]')) {
console.log('fixInitialTabStateFn 1p');
switchToTab(null);
} else if (checkElementExist('ytd-live-chat-frame#chat', '[hidden], [collapsed]')) {
console.log('fixInitialTabStateFn 1a');
switchToTab(null);
if (checkElementExist('ytd-watch-flexy[theater]', '[hidden]')) {
ytBtnCollapseChat();
}
} else if (qTab) {
const hasTheater = qTab.hasAttribute('theater');
if (!hasTheater) {
console.log('fixInitialTabStateFn 1b');
const btn0 = document.querySelector('.tab-btn-visible') // or default button
if (btn0) {
switchToTab(btn0);
} else {
switchToTab(null);
}
} else {
console.log('fixInitialTabStateFn 1c');
switchToTab(null);
}
} else {
console.log('fixInitialTabStateFn 1z');
}
// console.log('fixInitialTabStateFn 0d');
fixInitialTabStateK++;
},
'tabs-btn-click': (evt) => {
const target = evt.target;
if (target instanceof HTMLElement_ && target.classList.contains('tab-btn') && target.hasAttribute000('tyt-tab-content')) {
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
const activeLink = target;
switchToTab(activeLink);
}
}
}
Promise.all([videosElementProvidedPromise, navigateFinishedPromise]).then(eventMap['onceInsertRightTabs']).catch(console.warn);
Promise.all([navigateFinishedPromise, infoExpanderElementProvidedPromise]).then(eventMap['onceInfoExpanderElementProvidedPromised']).catch(console.warn);
const isCustomElementsProvided = typeof customElements !== "undefined" && typeof (customElements || 0).whenDefined === "function";
const promiseForCustomYtElementsReady = isCustomElementsProvided ? Promise.resolve(0) : new Promise((callback) => {
const EVENT_KEY_ON_REGISTRY_READY = "ytI-ce-registry-created";
if (typeof customElements === 'undefined') {
if (!('__CE_registry' in document)) {
// https://github.com/webcomponents/polyfills/
Object.defineProperty(document, '__CE_registry', {
get() {
// return undefined
},
set(nv) {
if (typeof nv == 'object') {
delete this.__CE_registry;
this.__CE_registry = nv;
this.dispatchEvent(new CustomEvent(EVENT_KEY_ON_REGISTRY_READY));
}
return true;
},
enumerable: false,
configurable: true
})
}
let eventHandler = (evt) => {
document.removeEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
const f = callback;
callback = null;
eventHandler = null;
f();
};
document.addEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
} else {
callback();
}
});
const _retrieveCE = async (nodeName) => {
try {
isCustomElementsProvided || (await promiseForCustomYtElementsReady);
await customElements.whenDefined(nodeName);
} catch (e) {
console.warn(e);
}
}
const retrieveCE = async (nodeName) => {
try {
isCustomElementsProvided || (await promiseForCustomYtElementsReady);
await customElements.whenDefined(nodeName);
const dummy = document.querySelector(nodeName) || document.createElement(nodeName);
const cProto = insp(dummy).constructor.prototype;
return cProto;
} catch (e) {
console.warn(e);
}
}
const moOverallRes = {
_yt_playerProvided: () => (((window || 0)._yt_player || 0) || 0)
}
let promiseWaitNext = null;
const moOverall = new MutationObserver(() => {
if (promiseWaitNext) {
promiseWaitNext.resolve();
promiseWaitNext = null;
}
if (typeof moOverallRes._yt_playerProvided === 'function') {
const r = moOverallRes._yt_playerProvided();
if (r) {
moOverallRes._yt_playerProvided = r;
eventMap._yt_playerProvided();
}
}
});
moOverall.observe(document, { subtree: true, childList: true });
const moEgmPanelReady = new MutationObserver(mutations => {
for (const mutation of mutations) {
const target = mutation.target;
if (!target.hasAttribute000('tyt-egm-panel-jclmd')) continue;
if (target.hasAttribute000('target-id') && target.hasAttribute000('visibility')) {
target.removeAttribute000('tyt-egm-panel-jclmd');
moEgmPanelReadyClearFn();
Promise.resolve(target).then(eventMap['ytd-engagement-panel-section-list-renderer::bindTarget']).catch(console.warn);
}
}
});
const moEgmPanelReadyClearFn = () => {
if (document.querySelector('[tyt-egm-panel-jclmd]') === null) {
moEgmPanelReady.takeRecords();
moEgmPanelReady.disconnect();
}
};
document.addEventListener('yt-navigate-finish', eventMap['yt-navigate-finish'], false);
document.addEventListener('animationstart', evt => {
const f = eventMap[evt.animationName];
if (typeof f === 'function') f(evt.target);
}, capturePassive);
// console.log('hi122')
mLoaded.flag |= 1;
document.documentElement.setAttribute111('tabview-loaded', mLoaded.makeString());
promiseForCustomYtElementsReady.then(eventMap['ceHack']).catch(console.warn);
executionFinished = 1;
} catch (e) {
console.log('error 0xF491');
console.error(e);
}
};
const styles = {
'main': `
@keyframes relatedElementProvided{0%{background-position-x:3px;}100%{background-position-x:4px;}}
html[tabview-loaded="icp"] #related.ytd-watch-flexy{animation:relatedElementProvided 1ms linear 0s 1 normal forwards;}
html[tabview-loaded="icp"] #right-tabs #related.ytd-watch-flexy,html[tabview-loaded="icp"] [hidden] #related.ytd-watch-flexy,html[tabview-loaded="icp"] #right-tabs ytd-expander#expander,html[tabview-loaded="icp"] [hidden] ytd-expander#expander,html[tabview-loaded="icp"] ytd-comments ytd-expander#expander{animation:initial;}
#secondary.ytd-watch-flexy{position:relative;}
#secondary-inner.style-scope.ytd-watch-flexy{height:100%;}
#secondary-inner secondary-wrapper{display:flex;flex-direction:column;flex-wrap:nowrap;box-sizing:border-box;padding:0;margin:0;border:0;height:100%;max-height:calc(100vh - var(--ytd-toolbar-height,56px));position:absolute;top:0;right:0;left:0;contain:strict;padding:var(--ytd-margin-6x) var(--ytd-margin-6x) var(--ytd-margin-6x) 0;}
#right-tabs{position:relative;display:flex;padding:0;margin:0;flex-grow:1;flex-direction:column;}
[tyt-tab=""] #right-tabs{flex-grow:0;}
[tyt-tab=""] #right-tabs .tab-content{border:0;}
#right-tabs .tab-content{flex-grow:1;}
ytd-watch-flexy[hide-default-text-inline-expander] #primary.style-scope.ytd-watch-flexy ytd-text-inline-expander{display:none;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden{--comment-pre-load-sizing:90px;visibility:collapse;z-index:-1;position:fixed!important;left:2px;top:2px;width:var(--comment-pre-load-sizing)!important;height:var(--comment-pre-load-sizing)!important;display:block!important;pointer-events:none!important;overflow:hidden;contain:strict;border:0;margin:0;padding:0;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments>ytd-item-section-renderer#sections{display:block!important;overflow:hidden;height:var(--comment-pre-load-sizing);width:var(--comment-pre-load-sizing);contain:strict;border:0;margin:0;padding:0;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments>ytd-item-section-renderer#sections>#contents{display:flex!important;flex-direction:row;gap:60px;overflow:hidden;height:var(--comment-pre-load-sizing);width:var(--comment-pre-load-sizing);contain:strict;border:0;margin:0;padding:0;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents{--comment-pre-load-display:none;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*:only-of-type,ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*:last-child{--comment-pre-load-display:block;}
ytd-watch-flexy:not([keep-comments-scroller]) #tab-comments.tab-content-hidden ytd-comments#comments #contents>*{display:var(--comment-pre-load-display)!important;}
#right-tabs #material-tabs{position:relative;display:flex;padding:0;border:1px solid var(--ytd-searchbox-legacy-border-color);overflow:hidden;}
[tyt-tab] #right-tabs #material-tabs{border-radius:var(--tyt-rounded-a1);}
[tyt-tab^="#"] #right-tabs #material-tabs{border-radius:var(--tyt-rounded-a1) var(--tyt-rounded-a1) 0 0;}
ytd-watch-flexy[flexy]:not([is-two-columns_]) #right-tabs #material-tabs{outline:0;}
#right-tabs #material-tabs a.tab-btn[tyt-tab-content]>*{pointer-events:none;}
#right-tabs #material-tabs a.tab-btn[tyt-tab-content]>.font-size-right{pointer-events:initial;display:none;}
ytd-watch-flexy #right-tabs .tab-content{padding:0;box-sizing:border-box;display:block;border:1px solid var(--ytd-searchbox-legacy-border-color);border-top:0;position:relative;top:0;display:flex;flex-direction:row;overflow:hidden;border-radius:0 0 var(--tyt-rounded-a1) var(--tyt-rounded-a1);}
ytd-watch-flexy:not([is-two-columns_]) #right-tabs .tab-content{height:100%;}
ytd-watch-flexy #right-tabs .tab-content-cld{box-sizing:border-box;position:relative;display:block;width:100%;overflow:auto;--tab-content-padding:var(--ytd-margin-4x);padding:var(--tab-content-padding);contain:layout paint;}
.tab-content-cld,#right-tabs,.tab-content{transition:none;animation:none;}
#right-tabs #emojis.ytd-commentbox{inset:auto 0 auto 0;width:auto;}
ytd-watch-flexy[is-two-columns_] #right-tabs .tab-content-cld{height:100%;width:100%;contain:size layout paint style;position:absolute;}
ytd-watch-flexy #right-tabs .tab-content-cld.tab-content-hidden{display:none;width:100%;contain:size layout paint style;}
@supports (color:var(--tabview-tab-btn-define)){
ytd-watch-flexy #right-tabs .tab-btn{background:var(--yt-spec-general-background-a);}
html{--tyt-tab-btn-flex-grow:1;--tyt-tab-btn-flex-basis:0%;--tyt-tab-bar-color-1-def:#ff4533;--tyt-tab-bar-color-2-def:var(--yt-brand-light-red);--tyt-tab-bar-color-1:var(--main-color,var(--tyt-tab-bar-color-1-def));--tyt-tab-bar-color-2:var(--main-color,var(--tyt-tab-bar-color-2-def));}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]{flex:var(--tyt-tab-btn-flex-grow) 1 var(--tyt-tab-btn-flex-basis);position:relative;display:inline-block;text-decoration:none;text-transform:uppercase;--tyt-tab-btn-color:var(--yt-spec-text-secondary);color:var(--tyt-tab-btn-color);text-align:center;padding:14px 8px 10px;border:0;border-bottom:4px solid transparent;font-weight:500;font-size:12px;line-height:18px;cursor:pointer;transition:border 200ms linear 100ms;background-color:var(--ytd-searchbox-legacy-button-color);text-transform:var(--yt-button-text-transform,inherit);user-select:none!important;overflow:hidden;white-space:nowrap;text-overflow:clip;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]>svg{height:18px;padding-right:0;vertical-align:bottom;opacity:.5;margin-right:0;color:var(--yt-button-color,inherit);fill:var(--iron-icon-fill-color,currentcolor);stroke:var(--iron-icon-stroke-color,none);pointer-events:none;}
ytd-watch-flexy #right-tabs .tab-btn{--tabview-btn-txt-ml:8px;}
ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"]{--tabview-btn-txt-ml:0;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]>svg+span{margin-left:var(--tabview-btn-txt-ml);}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].active{font-weight:500;outline:0;--tyt-tab-btn-color:var(--yt-spec-text-primary);background-color:var(--ytd-searchbox-legacy-button-focus-color);border-bottom:2px var(--tyt-tab-bar-color-2) solid;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].active svg{opacity:.9;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]:not(.active):hover{background-color:var(--ytd-searchbox-legacy-button-hover-color);--tyt-tab-btn-color:var(--yt-spec-text-primary);}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content]:not(.active):hover svg{opacity:.9;}
ytd-watch-flexy #right-tabs .tab-btn[tyt-tab-content].tab-btn-hidden{display:none;}
ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"],ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"]:hover{--tyt-tab-btn-color:var(--yt-spec-icon-disabled);}
ytd-watch-flexy[tyt-comment-disabled] #right-tabs .tab-btn[tyt-tab-content="#tab-comments"] span#tyt-cm-count:empty{display:none;}
ytd-watch-flexy #right-tabs .tab-btn span#tyt-cm-count:empty::after{display:inline-block;width:4em;text-align:left;font-size:inherit;color:currentColor;transform:scaleX(.8);}
}
@supports (color:var(--tyt-cm-count-define)){
ytd-watch-flexy{--tyt-x-loading-content-letter-spacing:2px;}
html{--tabview-text-loading:"Loading";--tabview-text-fetching:"Fetching";--tabview-panel-loading:var(--tabview-text-loading);}
html:lang(ja){--tabview-text-loading:"読み込み中";--tabview-text-fetching:"フェッチ..";}
html:lang(ko){--tabview-text-loading:"로딩..";--tabview-text-fetching:"가져오기..";}
html:lang(zh-Hant){--tabview-text-loading:"載入中";--tabview-text-fetching:"擷取中";}
html:lang(zh-Hans){--tabview-text-loading:"加载中";--tabview-text-fetching:"抓取中";}
html:lang(ru){--tabview-text-loading:"Загрузка";--tabview-text-fetching:"Получение";}
ytd-watch-flexy #right-tabs .tab-btn span#tyt-cm-count:empty::after{content:var(--tabview-text-loading);letter-spacing:var(--tyt-x-loading-content-letter-spacing);}
}
@supports (color:var(--tabview-font-size-btn-define)){
.font-size-right{display:inline-flex;flex-direction:column;position:absolute;right:0;top:0;bottom:0;width:16px;padding:4px 0;justify-content:space-evenly;align-content:space-evenly;pointer-events:none;}
html body ytd-watch-flexy.style-scope .font-size-btn{user-select:none!important;}
.font-size-btn{--tyt-font-size-btn-display:none;display:var(--tyt-font-size-btn-display,none);width:12px;height:12px;color:var(--yt-spec-text-secondary);background-color:var(--yt-spec-badge-chip-background);box-sizing:border-box;cursor:pointer;transform-origin:left top;margin:0;padding:0;position:relative;font-family:'Menlo','Lucida Console','Monaco','Consolas',monospace;line-height:100%;font-weight:900;transition:background-color 90ms linear,color 90ms linear;pointer-events:all;}
.font-size-btn:hover{background-color:var(--yt-spec-text-primary);color:var(--yt-spec-general-background-a);}
@supports (zoom:.5){
.tab-btn .font-size-btn{--tyt-font-size-btn-display:none;}
.tab-btn.active:hover .font-size-btn{--tyt-font-size-btn-display:inline-block;}
body ytd-watch-flexy:not([is-two-columns_]) #columns.ytd-watch-flexy{flex-direction:column;}
body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy{display:block;width:100%;box-sizing:border-box;}
body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy secondary-wrapper{padding-left:var(--ytd-margin-6x);contain:content;height:initial;}
body ytd-watch-flexy:not([is-two-columns_]) #secondary.ytd-watch-flexy secondary-wrapper #right-tabs{overflow:auto;}
[tyt-chat="+"] secondary-wrapper>[tyt-chat-container]{flex-grow:1;flex-shrink:0;display:flex;flex-direction:column;}
[tyt-chat="+"] secondary-wrapper>[tyt-chat-container]>#chat{flex-grow:1;}
ytd-watch-flexy[is-two-columns_]:not([theater]) #columns.style-scope.ytd-watch-flexy{min-height:calc(100vh - var(--ytd-toolbar-height,56px));}
ytd-watch-flexy[is-two-columns_] ytd-live-chat-frame#chat{min-height:initial!important;height:initial!important;}
ytd-watch-flexy[tyt-tab^="#"]:not([is-two-columns_]):not([tyt-chat="+"]) #right-tabs{min-height:var(--ytd-watch-flexy-chat-max-height);}
body ytd-watch-flexy:not([is-two-columns_]) #chat.ytd-watch-flexy{margin-top:0;}
body ytd-watch-flexy:not([is-two-columns_]) ytd-watch-metadata.ytd-watch-flexy{margin-bottom:0;}
ytd-watch-metadata.ytd-watch-flexy ytd-metadata-row-container-renderer{display:none;}
#tab-info [show-expand-button] #expand-sizer.ytd-text-inline-expander{visibility:initial;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#left-arrow-container.ytd-video-description-infocards-section-renderer>#left-arrow,#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#right-arrow-container.ytd-video-description-infocards-section-renderer>#right-arrow{border:6px solid transparent;opacity:.65;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#left-arrow-container.ytd-video-description-infocards-section-renderer>#left-arrow:hover,#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>#right-arrow-container.ytd-video-description-infocards-section-renderer>#right-arrow:hover{opacity:1;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>div#left-arrow-container::before{content:'';background:transparent;width:40px;display:block;height:40px;position:absolute;left:-20px;top:0;z-index:-1;}
#tab-info #social-links.style-scope.ytd-video-description-infocards-section-renderer>div#right-arrow-container::before{content:'';background:transparent;width:40px;display:block;height:40px;position:absolute;right:-20px;top:0;z-index:-1;}
body ytd-watch-flexy[is-two-columns_][tyt-egm-panel_] #columns.style-scope.ytd-watch-flexy #panels.style-scope.ytd-watch-flexy{flex-grow:1;flex-shrink:0;display:flex;flex-direction:column;}
body ytd-watch-flexy[is-two-columns_][tyt-egm-panel_] #columns.style-scope.ytd-watch-flexy #panels.style-scope.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]{height:initial;max-height:initial;min-height:initial;flex-grow:1;flex-shrink:0;display:flex;flex-direction:column;}
secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] ytd-transcript-renderer:not(:empty),secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] #body.ytd-transcript-renderer:not(:empty),secondary-wrapper [visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"] #content.ytd-transcript-renderer:not(:empty){flex-grow:1;height:initial;max-height:initial;min-height:initial;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer{position:relative;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer>[panel-target-id]:only-child{contain:style size;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-list-renderer.ytd-transcript-search-panel-renderer{flex-grow:1;contain:strict;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-renderer.style-scope.ytd-transcript-segment-list-renderer{contain:layout paint style;}
secondary-wrapper #content.ytd-engagement-panel-section-list-renderer ytd-transcript-segment-renderer.style-scope.ytd-transcript-segment-list-renderer>.segment{contain:layout paint style;}
body ytd-watch-flexy[theater] #secondary.ytd-watch-flexy{margin-top:var(--ytd-margin-3x);padding-top:0;}
body ytd-watch-flexy[theater] secondary-wrapper{margin-top:0;padding-top:0;}
body ytd-watch-flexy[theater] #chat.ytd-watch-flexy{margin-bottom:var(--ytd-margin-2x);}
ytd-watch-flexy[theater] #right-tabs .tab-btn[tyt-tab-content]{padding:8px 4px 6px;border-bottom:0 solid transparent;}
ytd-watch-flexy[theater] #playlist.ytd-watch-flexy{margin-bottom:var(--ytd-margin-2x);}
ytd-watch-flexy[theater] ytd-playlist-panel-renderer[collapsible][collapsed] .header.ytd-playlist-panel-renderer{padding:6px 8px;}
#tab-comments ytd-comments#comments [field-of-cm-count]{margin-top:0;}
#tab-info>ytd-expandable-video-description-body-renderer{margin-bottom:var(--ytd-margin-3x);}
#tab-info [class]:last-child{margin-bottom:0;padding-bottom:0;}
#tab-info ytd-rich-metadata-row-renderer ytd-rich-metadata-renderer{max-width:initial;}
ytd-watch-flexy[is-two-columns_] secondary-wrapper #chat.ytd-watch-flexy{margin-bottom:var(--ytd-margin-3x);}
ytd-watch-flexy[tyt-tab] tp-yt-paper-tooltip{white-space:nowrap;contain:content;}
ytd-watch-info-text tp-yt-paper-tooltip.style-scope.ytd-watch-info-text{margin-bottom:-300px;margin-top:-96px;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata{font-size:1.2rem;line-height:1.8rem;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata yt-animated-rolling-number{font-size:inherit;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata #info-container.style-scope.ytd-watch-info-text{align-items:center;}
ytd-watch-flexy[hide-default-text-inline-expander]{--tyt-bottom-watch-metadata-margin:6px;}
[hide-default-text-inline-expander] #bottom-row #description.ytd-watch-metadata>#description-inner.ytd-watch-metadata{margin:6px 12px;}
[hide-default-text-inline-expander] ytd-watch-metadata[title-headline-xs] h1.ytd-watch-metadata{font-size:1.8rem;}
ytd-watch-flexy[is-two-columns_][hide-default-text-inline-expander] #below.style-scope.ytd-watch-flexy ytd-merch-shelf-renderer{padding:0;border:0;margin:0;}
ytd-watch-flexy[is-two-columns_][hide-default-text-inline-expander] #below.style-scope.ytd-watch-flexy ytd-watch-metadata.ytd-watch-flexy{margin-bottom:6px;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--horizontal .yt-video-attribute-view-model__link-container .yt-video-attribute-view-model__hero-section{flex-shrink:0;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model__overflow-menu{background:var(--yt-emoji-picker-category-background-color);border-radius:99px;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--image-square.yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section{max-height:128px;}
#tab-info yt-video-attribute-view-model .yt-video-attribute-view-model--image-large .yt-video-attribute-view-model__hero-section{max-width:128px;}
#tab-info ytd-reel-shelf-renderer #items.yt-horizontal-list-renderer ytd-reel-item-renderer.yt-horizontal-list-renderer{max-width:142px;}
ytd-watch-info-text#ytd-watch-info-text.style-scope.ytd-watch-metadata #view-count.style-scope.ytd-watch-info-text,ytd-watch-info-text#ytd-watch-info-text.style-scope.ytd-watch-metadata #date-text.style-scope.ytd-watch-info-text{align-items:center;}
ytd-watch-info-text:not([detailed]) #info.ytd-watch-info-text a.yt-simple-endpoint.yt-formatted-string{pointer-events:none;}
body ytd-app>ytd-popup-container>tp-yt-iron-dropdown>#contentWrapper>[slot="dropdown-content"]{backdrop-filter:none;}
#tab-info [tyt-clone-refresh-count]{overflow:visible!important;}
#tab-info #items.ytd-horizontal-card-list-renderer yt-video-attribute-view-model.ytd-horizontal-card-list-renderer{contain:layout;}
#tab-info #thumbnail-container.ytd-structured-description-channel-lockup-renderer,#tab-info ytd-media-lockup-renderer[is-compact] #thumbnail-container.ytd-media-lockup-renderer{flex-shrink:0;}
secondary-wrapper ytd-donation-unavailable-renderer{--ytd-margin-6x:var(--ytd-margin-2x);--ytd-margin-5x:var(--ytd-margin-2x);--ytd-margin-4x:var(--ytd-margin-2x);--ytd-margin-3x:var(--ytd-margin-2x);}
[tyt-no-less-btn] #less{display:none;}
.tyt-metadata-hover-resized #purchase-button,.tyt-metadata-hover-resized #sponsor-button,.tyt-metadata-hover-resized #analytics-button,.tyt-metadata-hover-resized #subscribe-button{display:none!important;}
.tyt-metadata-hover #upload-info{max-width:max-content;min-width:max-content;flex-basis:100vw;flex-shrink:0;}
.tyt-info-invisible{display:none;}
[tyt-playlist-expanded] secondary-wrapper>ytd-playlist-panel-renderer#playlist{overflow:auto;flex-shrink:1;flex-grow:1;max-height:unset!important;}
[tyt-playlist-expanded] secondary-wrapper>ytd-playlist-panel-renderer#playlist>#container{max-height:unset!important;}
secondary-wrapper ytd-playlist-panel-renderer{--ytd-margin-6x:var(--ytd-margin-3x);}
#tab-info ytd-structured-description-playlist-lockup-renderer[collections] #playlist-thumbnail.style-scope.ytd-structured-description-playlist-lockup-renderer{max-width:100%;}
#tab-info ytd-structured-description-playlist-lockup-renderer[collections] #lockup-container.ytd-structured-description-playlist-lockup-renderer{padding:1px;}
#tab-info ytd-structured-description-playlist-lockup-renderer[collections] #thumbnail.ytd-structured-description-playlist-lockup-renderer{outline:1px solid rgba(127,127,127,.5);}
ytd-live-chat-frame#chat[collapsed] ytd-message-renderer~#show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame{padding:0;}
ytd-watch-flexy{--tyt-bottom-watch-metadata-margin:12px;}
ytd-watch-flexy[rounded-info-panel],ytd-watch-flexy[rounded-player-large]{--tyt-rounded-a1:12px;}
#bottom-row.style-scope.ytd-watch-metadata .item.ytd-watch-metadata{margin-right:var(--tyt-bottom-watch-metadata-margin,12px);margin-top:var(--tyt-bottom-watch-metadata-margin,12px);}
#cinematics{contain:layout style size;}
ytd-watch-flexy[is-two-columns_]{contain:layout style;}
`
};
(async () => {
const communicationKey = `ck-${Date.now()}-${Math.floor(Math.random() * 314159265359 + 314159265359).toString(36)}`;
/** @type {globalThis.PromiseConstructor} */
const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
if (!document.documentElement) {
await Promise.resolve(0);
while (!document.documentElement) {
await new Promise(resolve => nextBrowserTick(resolve)).then().catch(console.warn);
}
}
const sourceURL = 'debug://tabview-youtube/tabview.execution.js'
const textContent = `(${executionScript})("${communicationKey}");${"\n\n"}//# sourceURL=${sourceURL}${'\n'}`
let button = document.createElement('button');
button.setAttribute('onclick', createHTML(textContent)); // max size 10 million bytes
button.click();
button = null;
let style = document.createElement('style');
const sourceURLMainCSS = 'debug://tabview-youtube/tabview.main.css';
style.textContent = `${styles['main'].trim()}${'\n\n'}/*# sourceURL=${sourceURLMainCSS} */${'\n'}`;
document.documentElement.appendChild(style);
//-----------------------------------------------------------------------------
// YouTube enhancements module
(function () {
"use strict";
const YouTubeEnhancer = {
// Speed control variables
speedControl: {
currentSpeed: 1,
activeAnimationId: null,
storageKey: "youtube_playback_speed",
},
// Settings
settings: {
enableSpeedControl: true,
enableScreenshot: true,
enableDownload: true,
storageKey: "youtube_plus_settings",
},
// Cache DOM queries
_cache: new Map(),
// Initialize everything
init() {
if (!/youtube\.com/.test(location.host)) return;
this.loadSettings();
this.speedControl.currentSpeed = parseFloat(
localStorage.getItem(this.speedControl.storageKey) || 1
);
this.insertStyles();
this.addSettingsButtonToHeader();
this.setupNavigationObserver();
this.setupCurrentPage();
},
// Cached element getter
getElement(selector, useCache = true) {
if (useCache && this._cache.has(selector)) {
const element = this._cache.get(selector);
if (element?.isConnected) return element;
this._cache.delete(selector);
}
const element = document.querySelector(selector);
if (element && useCache) this._cache.set(selector, element);
return element;
},
loadSettings() {
try {
const saved = localStorage.getItem(this.settings.storageKey);
if (saved) Object.assign(this.settings, JSON.parse(saved));
} catch (e) {
console.error("Error loading settings:", e);
}
},
saveSettings() {
localStorage.setItem(this.settings.storageKey, JSON.stringify(this.settings));
this.updatePageBasedOnSettings();
},
updatePageBasedOnSettings() {
const settingsMap = {
"ytp-screenshot-button": "enableScreenshot",
"ytp-download-button": "enableDownload",
"speed-control-btn": "enableSpeedControl"
};
Object.entries(settingsMap).forEach(([className, setting]) => {
const button = this.getElement(`.${className}`, false);
if (button) button.style.display = this.settings[setting] ? "" : "none";
});
},
setupCurrentPage() {
this.waitForElement("#player-container-outer .html5-video-player, .ytp-right-controls", 5000)
.then(() => {
this.addCustomButtons();
this.setupVideoObserver();
this.applyCurrentSpeed();
this.updatePageBasedOnSettings();
})
.catch(() => { });
},
insertStyles() {
// Glassmorphism styles for modal and controls
const styles = `:root{--yt-accent:#ff0000;--yt-accent-hover:#cc0000;--yt-radius-sm:4px;--yt-radius-md:8px;--yt-radius-lg:12px;--yt-transition:all .2s ease;--yt-space-xs:4px;--yt-space-sm:8px;--yt-space-md:16px;--yt-space-lg:24px;}
html[dark],html:not([dark]):not([light]){--yt-bg-primary:rgba(15,15,15,.85);--yt-bg-secondary:rgba(28,28,28,.85);--yt-bg-tertiary:rgba(34,34,34,.85);--yt-text-primary:#fff;--yt-text-secondary:#aaa;--yt-border-color:rgba(255,255,255,.2);--yt-hover-bg:rgba(255,255,255,.1);--yt-shadow:0 4px 12px rgba(0,0,0,.25);--yt-glass-bg:rgba(255,255,255,.1);--yt-glass-border:rgba(255,255,255,.2);--yt-glass-shadow:0 8px 32px rgba(0,0,0,.2);--yt-modal-bg:rgba(0,0,0,.75);--yt-notification-bg:rgba(28,28,28,.9);--yt-panel-bg:rgba(34,34,34,.3);--yt-header-bg:rgba(20,20,20,.6);--yt-input-bg:rgba(255,255,255,.1);--yt-button-bg:rgba(255,255,255,.2);--yt-text-stroke:white;}
html[light]{--yt-bg-primary:rgba(255,255,255,.85);--yt-bg-secondary:rgba(248,248,248,.85);--yt-bg-tertiary:rgba(240,240,240,.85);--yt-text-primary:#030303;--yt-text-secondary:#606060;--yt-border-color:rgba(0,0,0,.2);--yt-hover-bg:rgba(0,0,0,.05);--yt-shadow:0 4px 12px rgba(0,0,0,.15);--yt-glass-bg:rgba(255,255,255,.7);--yt-glass-border:rgba(0,0,0,.1);--yt-glass-shadow:0 8px 32px rgba(0,0,0,.1);--yt-modal-bg:rgba(0,0,0,.5);--yt-notification-bg:rgba(255,255,255,.95);--yt-panel-bg:rgba(255,255,255,.7);--yt-header-bg:rgba(248,248,248,.8);--yt-input-bg:rgba(0,0,0,.05);--yt-button-bg:rgba(0,0,0,.1);--yt-text-stroke:#030303;}
.ytp-screenshot-button,.ytp-cobalt-button,.ytp-pip-button{position:relative;bottom:12px;width:44px;transition:opacity .15s,transform .15s;}
.ytp-screenshot-button:hover,.ytp-cobalt-button:hover,.ytp-pip-button:hover{transform:scale(1.1);}
.speed-control-btn{width:4em!important;float:left;text-align:center!important;border-radius:var(--yt-radius-sm);font-size:13px;color:var(--yt-text-primary);cursor:pointer;user-select:none;font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:color .2s;}
.speed-control-btn:hover{color:var(--yt-accent);font-weight:bold;}
.speed-options{position:absolute!important;background:var(--yt-glass-bg)!important;color:var(--yt-text-primary)!important;border-radius:var(--yt-radius-sm)!important;display:none;bottom:calc(100% + 10px)!important;width:48px!important;z-index:9999!important;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);overflow:hidden;backdrop-filter:blur(18px) saturate(180%);-webkit-backdrop-filter:blur(18px) saturate(180%);}
.speed-option-item{cursor:pointer!important;height:25px!important;line-height:25px!important;font-size:12px!important;text-align:center!important;transition:background-color .15s,color .15s;}
.speed-option-active,.speed-option-item:hover{color:var(--yt-accent)!important;font-weight:bold!important;background:var(--yt-hover-bg)!important;}
#speed-indicator{position:absolute!important;margin:auto!important;top:0!important;right:0!important;bottom:0!important;left:0!important;border-radius:20px!important;font-size:30px!important;background:var(--yt-glass-bg)!important;color:var(--yt-text-primary)!important;z-index:99999!important;width:80px!important;height:80px!important;line-height:80px!important;text-align:center!important;display:none;box-shadow:var(--yt-glass-shadow);backdrop-filter:blur(18px) saturate(180%);-webkit-backdrop-filter:blur(18px) saturate(180%);border:1px solid var(--yt-glass-border);}
.youtube-enhancer-notification{position:fixed;bottom:70px;left:50%;transform:translateX(-50%);background:var(--yt-glass-bg);color:var(--yt-text-primary);padding:10px 20px;border-radius:var(--yt-radius-sm);z-index:9999;transition:opacity .5s,transform .3s;box-shadow:var(--yt-glass-shadow);border:1px solid var(--yt-glass-border);backdrop-filter:blur(18px) saturate(180%);-webkit-backdrop-filter:blur(18px) saturate(180%);font-weight:500;}
.ytp-plus-settings-button{background:transparent;border:none;color:var(--yt-text-secondary);cursor:pointer;padding:var(--yt-space-sm);margin-right:var(--yt-space-sm);border-radius:50%;display:flex;align-items:center;justify-content:center;transition:background-color .2s,transform .2s;}
.ytp-plus-settings-button svg{width:24px;height:24px;}
.ytp-plus-settings-button:hover{background:var(--yt-hover-bg);transform:rotate(30deg);color:var(--yt-text-secondary);}
.ytp-plus-settings-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--yt-modal-bg);display:flex;align-items:center;justify-content:center;z-index:99999;backdrop-filter:blur(18px) saturate(180%);-webkit-backdrop-filter:blur(18px) saturate(180%);}
.ytp-plus-settings-panel{background:var(--yt-glass-bg);color:var(--yt-text-primary);border-radius:var(--yt-radius-lg);width:720px;max-width:90%;max-height:90vh;overflow:hidden;box-shadow:var(--yt-glass-shadow);animation:ytEnhanceFadeIn .2s;backdrop-filter:blur(18px) saturate(180%);-webkit-backdrop-filter:blur(18px) saturate(180%);border:1px solid var(--yt-glass-border);will-change:transform,opacity;display:flex;flex-direction:row;}
.ytp-plus-settings-sidebar{width:200px;background:var(--yt-header-bg);border-right:1px solid var(--yt-glass-border);display:flex;flex-direction:column;backdrop-filter:blur(18px) saturate(180%);-webkit-backdrop-filter:blur(18px) saturate(180%);}
.ytp-plus-settings-sidebar-header{padding:var(--yt-space-md) var(--yt-space-lg);border-bottom:1px solid var(--yt-glass-border);display:flex;justify-content:space-between;align-items:center;}
.ytp-plus-settings-title{font-size:18px;font-weight:500;margin:0;color:var(--yt-text-primary);}
.ytp-plus-settings-sidebar-close{padding:var(--yt-space-md) var(--yt-space-lg);display:flex;justify-content:flex-end;background:transparent;}
.ytp-plus-settings-close{background:none;border:none;cursor:pointer;padding:var(--yt-space-sm);margin:-8px;color:var(--yt-text-primary);transition:color .2s,transform .2s;}
.ytp-plus-settings-close:hover{color:var(--yt-accent);transform:scale(1.1);}
.ytp-plus-settings-nav{flex:1;padding:var(--yt-space-md) 0;}
.ytp-plus-settings-nav-item{display:flex;align-items:center;padding:12px var(--yt-space-lg);cursor:pointer;transition:background .2s,color .2s;font-size:14px;border-left:3px solid transparent;color:var(--yt-text-primary);}
.ytp-plus-settings-nav-item:hover{background:var(--yt-hover-bg);}
.ytp-plus-settings-nav-item.active{background:rgba(255,0,0,.1);border-left-color:var(--yt-accent);color:var(--yt-accent);}
.ytp-plus-settings-nav-item svg{width:18px;height:18px;margin-right:12px;opacity:.8;}
.ytp-plus-settings-nav-item.active svg{opacity:1;}
.ytp-plus-settings-main{flex:1;display:flex;flex-direction:column;overflow-y:auto;}
.ytp-plus-settings-header{padding:var(--yt-space-md) var(--yt-space-lg);border-bottom:1px solid var(--yt-glass-border);background:var(--yt-header-bg);backdrop-filter:blur(18px) saturate(180%);-webkit-backdrop-filter:blur(18px) saturate(180%);}
.ytp-plus-settings-content{flex:1;padding:var(--yt-space-md) var(--yt-space-lg);overflow-y:auto;}
.ytp-plus-settings-section{margin-bottom:var(--yt-space-lg);}
.ytp-plus-settings-section-title{font-size:16px;font-weight:500;margin-bottom:var(--yt-space-md);color:var(--yt-text-primary);}
.ytp-plus-settings-section.hidden{display:none;}
.ytp-plus-settings-item{display:flex;align-items:center;margin-bottom:var(--yt-space-md);padding:12px 16px;background:transparent;transition:transform .2s,background .3s;border-radius:var(--yt-radius-md);}
.ytp-plus-settings-item:hover{background:var(--yt-hover-bg);transform:translateX(5px);}
.ytp-plus-settings-item-label{flex:1;font-size:14px;color:var(--yt-text-primary);}
.ytp-plus-settings-item-description{font-size:12px;color:var(--yt-text-secondary);margin-top:4px;}
.ytp-plus-settings-checkbox{appearance:none;-webkit-appearance:none;-moz-appearance:none;width:15px;height:15px;right:5%;transform-origin:center;border:1px solid var(--yt-glass-border);border-radius:50%;background:transparent;transition:all 250ms cubic-bezier(.4,0,.23,1);cursor:pointer;position:absolute;box-shadow:0 1px 3px rgba(0,0,0,.2);}
.ytp-plus-settings-checkbox:hover{background:var(--yt-hover-bg);transform:scale(1.1);}
.ytp-plus-settings-checkbox::before{content:"";width:4px;height:2px;background:var(--yt-text-primary);position:absolute;transform:rotate(45deg);top:4px;left:3px;transition:width 100ms ease 50ms,opacity 50ms;transform-origin:0% 0%;opacity:0;}
.ytp-plus-settings-checkbox::after{content:"";width:0;height:2px;background:var(--yt-text-primary);position:absolute;transform:rotate(305deg);top:9px;left:6px;transition:width 100ms ease,opacity 50ms;transform-origin:0% 0%;opacity:0;}
.ytp-plus-settings-checkbox:checked{transform:rotate(0deg) scale(1.2);}
.ytp-plus-settings-checkbox:checked::before{width:8px;opacity:1;background:#fff;transition:width 150ms ease 100ms,opacity 150ms ease 100ms;}
.ytp-plus-settings-checkbox:checked::after{width:15px;opacity:1;background:#fff;transition:width 150ms ease 250ms,opacity 150ms ease 250ms;}
.ytp-plus-footer{padding:var(--yt-space-md) var(--yt-space-lg);border-top:1px solid var(--yt-glass-border);display:flex;justify-content:flex-end;background:transparent;}
.ytp-plus-button{padding:var(--yt-space-sm) var(--yt-space-md);border-radius:18px;border:none;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s,transform .2s,box-shadow .2s;}
.ytp-plus-button-primary{background:transparent;border:1px solid var(--yt-glass-border);color:var(--yt-text-primary);}
.ytp-plus-button-primary:hover{background:var(--yt-accent);color:#fff;box-shadow:0 4px 12px rgba(255,0,0,.3);transform:translateY(-2px);}
.app-icon{fill:var(--yt-text-primary);stroke:var(--yt-text-primary);transition:all .3s;}
@keyframes ytEnhanceFadeIn{from{opacity:0;transform:scale(.95);}to{opacity:1;transform:scale(1);}}
@media(max-width:768px){.ytp-plus-settings-panel{width:95%;max-height:80vh;flex-direction:column;}
.ytp-plus-settings-sidebar{width:100%;max-height:120px;flex-direction:row;overflow-x:auto;}
.ytp-plus-settings-nav{display:flex;flex-direction:row;padding:0;}
.ytp-plus-settings-nav-item{white-space:nowrap;border-left:none;border-bottom:3px solid transparent;}
.ytp-plus-settings-nav-item.active{border-left:none;border-bottom-color:var(--yt-accent);}
.ytp-plus-settings-item{padding:10px 12px;}
}
.ytp-plus-settings-section h1{margin:-95px 90px 8px;font-family:'Montserrat',sans-serif;font-size:52px;font-weight:600;color:transparent;-webkit-text-stroke-width:1px;-webkit-text-stroke-color:var(--yt-text-stroke);cursor:pointer;transition:color .2s;}
.ytp-plus-settings-section h1:hover{color:var(--yt-accent);-webkit-text-stroke-width:1px;-webkit-text-stroke-color:transparent;}
`;
if (!document.getElementById('yt-enhancer-styles')) {
const styleEl = document.createElement("style");
styleEl.id = 'yt-enhancer-styles';
styleEl.textContent = styles;
document.head.appendChild(styleEl);
}
},
addSettingsButtonToHeader() {
this.waitForElement("ytd-masthead #end", 5000)
.then((headerEnd) => {
if (!this.getElement(".ytp-plus-settings-button")) {
const settingsButton = document.createElement("div");
settingsButton.className = "ytp-plus-settings-button";
settingsButton.setAttribute("title", "YouTube + Settings");
settingsButton.innerHTML = `
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M39.23,26a16.52,16.52,0,0,0,.14-2,16.52,16.52,0,0,0-.14-2l4.33-3.39a1,1,0,0,0,.25-1.31l-4.1-7.11a1,1,0,0,0-1.25-.44l-5.11,2.06a15.68,15.68,0,0,0-3.46-2l-.77-5.43a1,1,0,0,0-1-.86H19.9a1,1,0,0,0-1,.86l-.77,5.43a15.36,15.36,0,0,0-3.46,2L9.54,9.75a1,1,0,0,0-1.25.44L4.19,17.3a1,1,0,0,0,.25,1.31L8.76,22a16.66,16.66,0,0,0-.14,2,16.52,16.52,0,0,0,.14,2L4.44,29.39a1,1,0,0,0-.25,1.31l4.1,7.11a1,1,0,0,0,1.25.44l5.11-2.06a15.68,15.68,0,0,0,3.46,2l.77,5.43a1,1,0,0,0,1,.86h8.2a1,1,0,0,0,1-.86l.77-5.43a15.36,15.36,0,0,0,3.46-2l5.11,2.06a1,1,0,0,0,1.25-.44l4.1-7.11a1,1,0,0,0-.25-1.31ZM24,31.18A7.18,7.18,0,1,1,31.17,24,7.17,7.17,0,0,1,24,31.18Z"/>
</svg>
`;
settingsButton.addEventListener("click", this.openSettingsModal.bind(this));
const avatarButton = headerEnd.querySelector("ytd-topbar-menu-button-renderer");
if (avatarButton) {
headerEnd.insertBefore(settingsButton, avatarButton);
} else {
headerEnd.appendChild(settingsButton);
}
}
})
.catch(() => { });
},
createSettingsModal() {
const modal = document.createElement("div");
modal.className = "ytp-plus-settings-modal";
modal.innerHTML = `
<div class="ytp-plus-settings-panel">
<div class="ytp-plus-settings-sidebar">
<div class="ytp-plus-settings-sidebar-header">
<h2 class="ytp-plus-settings-title">Settings</h2>
</div>
<div class="ytp-plus-settings-nav">
<div class="ytp-plus-settings-nav-item active" data-section="basic">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<circle cx="9" cy="9" r="2"/>
<path d="m21 15-3.086-3.086a2 2 0 0 0-1.414-.586H13l-2-2v3h6l3 3"/>
</svg>
Basic
</div>
<div class="ytp-plus-settings-nav-item" data-section="advanced">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<path d="m12 1 0 6m0 6 0 6"/>
<path d="m17.5 6.5-4.5 4.5m0 0-4.5 4.5m9-9L12 12l5.5 5.5"/>
</svg>
Advanced
</div>
<div class="ytp-plus-settings-nav-item" data-section="experimental">
<svg width="64px" height="64px" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.019 4V15.0386L6.27437 39.3014C5.48686 40.9283 6.16731 42.8855 7.79421 43.673C8.23876 43.8882 8.72624 44 9.22013 44H38.7874C40.5949 44 42.0602 42.5347 42.0602 40.7273C42.0602 40.2348 41.949 39.7488 41.7351 39.3052L30.0282 15.0386V4H18.019Z" stroke="currentColor" stroke-width="4" stroke-linejoin="round"></path> <path d="M10.9604 29.9998C13.1241 31.3401 15.2893 32.0103 17.4559 32.0103C19.6226 32.0103 21.7908 31.3401 23.9605 29.9998C26.1088 28.6735 28.2664 28.0103 30.433 28.0103C32.5997 28.0103 34.7755 28.6735 36.9604 29.9998" stroke="currentColor" stroke-width="4" stroke-linecap="round"></path>
</svg>
Experimental
</div>
<div class="ytp-plus-settings-nav-item" data-section="about">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="m9 12 2 2 4-4"/>
</svg>
About
</div>
</div>
</div>
<div class="ytp-plus-settings-main">
<div class="ytp-plus-settings-sidebar-close">
<button class="ytp-plus-settings-close" aria-label="Close">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
</svg>
</button>
</div>
<div class="ytp-plus-settings-content">
<div class="ytp-plus-settings-section" data-section="basic">
<div class="ytp-plus-settings-item">
<div>
<label class="ytp-plus-settings-item-label">Speed Control</label>
<div class="ytp-plus-settings-item-description">Add speed control buttons to video player</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableSpeedControl" ${this.settings.enableSpeedControl ? 'checked' : ''}>
</div>
<div class="ytp-plus-settings-item">
<div>
<label class="ytp-plus-settings-item-label">Screenshot Button</label>
<div class="ytp-plus-settings-item-description">Add screenshot capture button to video player</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableScreenshot" ${this.settings.enableScreenshot ? 'checked' : ''}>
</div>
<div class="ytp-plus-settings-item">
<div>
<label class="ytp-plus-settings-item-label">Download Button</label>
<div class="ytp-plus-settings-item-description">Add download button with multiple site options to video player</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enableDownload" ${this.settings.enableDownload ? 'checked' : ''}>
</div>
</div>
<div class="ytp-plus-settings-section hidden" data-section="advanced">
</div>
<div class="ytp-plus-settings-section hidden" data-section="experimental">
</div>
<div class="ytp-plus-settings-section hidden" data-section="about">
<svg class="app-icon" width="90" height="90" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
<path d="m23.24,4.62c-0.85,0.45 -2.19,2.12 -4.12,5.13c-1.54,2.41 -2.71,4.49 -3.81,6.8c-0.55,1.14 -1.05,2.2 -1.13,2.35c-0.08,0.16 -0.78,0.7 -1.66,1.28c-1.38,0.91 -1.8,1.29 -1.4,1.28c0.08,0 0.67,-0.35 1.31,-0.77c0.64,-0.42 1.19,-0.76 1.2,-0.74c0.02,0.02 -0.1,0.31 -0.25,0.66c-1.03,2.25 -1.84,5.05 -1.84,6.37c0.01,1.89 0.84,2.67 2.86,2.67c1.08,0 1.94,-0.31 3.66,-1.29c1.84,-1.06 3.03,-1.93 4.18,-3.09c1.69,-1.7 2.91,-3.4 3.28,-4.59c0.59,-1.9 -0.1,-3.08 -2.02,-3.44c-0.87,-0.16 -2.85,-0.14 -3.75,0.06c-1.78,0.38 -2.74,0.76 -2.5,1c0.03,0.03 0.5,-0.1 1.05,-0.28c1.49,-0.48 2.34,-0.59 3.88,-0.53c1.64,0.07 2.09,0.19 2.69,0.75l0.46,0.43l0,0.87c0,0.74 -0.05,0.98 -0.35,1.6c-0.69,1.45 -2.69,3.81 -4.37,5.14c-0.93,0.74 -2.88,1.94 -4.07,2.5c-1.64,0.77 -3.56,0.72 -4.21,-0.11c-0.39,-0.5 -0.5,-1.02 -0.44,-2.11c0.05,-0.85 0.16,-1.32 0.67,-2.86c0.34,-1.01 0.86,-2.38 1.15,-3.04c0.52,-1.18 0.55,-1.22 1.6,-2.14c4.19,-3.65 8.42,-9.4 9.02,-12.26c0.2,-0.94 0.13,-1.46 -0.21,-1.7c-0.31,-0.22 -0.38,-0.21 -0.89,0.06m0.19,0.26c-0.92,0.41 -3.15,3.44 -5.59,7.6c-1.05,1.79 -3.12,5.85 -3.02,5.95c0.07,0.07 1.63,-1.33 2.58,-2.34c1.57,-1.65 3.73,-4.39 4.88,-6.17c1.31,-2.03 2.06,-4.11 1.77,-4.89c-0.13,-0.34 -0.16,-0.35 -0.62,-0.15m11.69,13.32c-0.3,0.6 -1.19,2.54 -1.98,4.32c-1.6,3.62 -1.67,3.71 -2.99,4.34c-1.13,0.54 -2.31,0.85 -3.54,0.92c-0.99,0.06 -1.08,0.04 -1.38,-0.19c-0.28,-0.22 -0.31,-0.31 -0.26,-0.7c0.03,-0.25 0.64,-1.63 1.35,-3.08c1.16,-2.36 2.52,-5.61 2.52,-6.01c0,-0.49 -0.36,0.19 -1.17,2.22c-0.51,1.26 -1.37,3.16 -1.93,4.24c-0.55,1.08 -1.04,2.17 -1.09,2.43c-0.1,0.59 0.07,1.03 0.49,1.28c0.78,0.46 3.3,0.06 5.13,-0.81l0.93,-0.45l-0.66,1.25c-0.7,1.33 -3.36,6.07 -4.31,7.67c-2.02,3.41 -3.96,5.32 -6.33,6.21c-2.57,0.96 -4.92,0.74 -6.14,-0.58c-0.81,-0.88 -0.82,-1.71 -0.04,-3.22c1.22,-2.36 6.52,-6.15 10.48,-7.49c0.52,-0.18 0.95,-0.39 0.95,-0.46c0,-0.21 -0.19,-0.18 -1.24,0.2c-1.19,0.43 -3.12,1.37 -4.34,2.11c-2.61,1.59 -5.44,4.09 -6.13,5.43c-1.15,2.2 -0.73,3.61 1.4,4.6c0.59,0.28 0.75,0.3 2.04,0.3c1.67,0 2.42,-0.18 3.88,-0.89c1.87,-0.92 3.17,-2.13 4.72,-4.41c0.98,-1.44 4.66,-7.88 5.91,-10.33c0.25,-0.49 0.68,-1.19 0.96,-1.56c0.28,-0.37 0.76,-1.15 1.06,-1.73c0.82,-1.59 2.58,-6.1 2.58,-6.6c0,-0.06 -0.07,-0.1 -0.17,-0.1c-0.1,0 -0.39,0.44 -0.71,1.09m-1.34,3.7c-0.93,2.08 -1.09,2.48 -0.87,2.2c0.19,-0.24 1.66,-3.65 1.6,-3.71c-0.02,-0.02 -0.35,0.66 -0.73,1.51" fill="none" fill-rule="evenodd" stroke="currentColor" />
</svg>
<h1>YouTube +</h1><br><br>
</div>
</div>
<div class="ytp-plus-footer">
<button class="ytp-plus-button ytp-plus-button-primary" id="ytp-plus-save-settings">Save Changes</button>
</div>
</div>
</div>
`;
// Event delegation for better performance
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
if (e.target.classList.contains('ytp-plus-settings-close') || e.target.closest('.ytp-plus-settings-close')) modal.remove();
if (e.target.classList.contains('ytp-plus-settings-nav-item')) {
// Handle sidebar navigation
const section = e.target.dataset.section;
modal.querySelectorAll('.ytp-plus-settings-nav-item').forEach(item => item.classList.remove('active'));
modal.querySelectorAll('.ytp-plus-settings-section').forEach(section => section.classList.add('hidden'));
e.target.classList.add('active');
modal.querySelector(`.ytp-plus-settings-section[data-section="${section}"]`).classList.remove('hidden');
}
if (e.target.classList.contains('ytp-plus-settings-checkbox')) {
const setting = e.target.dataset.setting;
if (setting) {
this.settings[setting] = e.target.checked;
}
}
if (e.target.id === 'ytp-plus-save-settings') {
this.saveSettings();
modal.remove();
this.showNotification("Settings saved");
}
});
return modal;
},
openSettingsModal() {
const existingModal = this.getElement(".ytp-plus-settings-modal", false);
if (existingModal) existingModal.remove();
document.body.appendChild(this.createSettingsModal());
},
waitForElement(selector, timeout = 5000) {
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (element) return resolve(element);
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Element ${selector} not found within ${timeout}ms`));
}, timeout);
});
},
addCustomButtons() {
const controls = this.getElement(".ytp-right-controls");
if (!controls) return;
if (!this.getElement(".ytp-screenshot-button")) this.addScreenshotButton(controls);
if (!this.getElement(".ytp-download-button")) this.addDownloadButton(controls);
if (!this.getElement(".speed-control-btn")) this.addSpeedControlButton(controls);
if (!document.getElementById("speed-indicator")) {
const indicator = document.createElement("div");
indicator.id = "speed-indicator";
const player = document.getElementById("movie_player");
if (player) player.appendChild(indicator);
}
this.handleFullscreenChange();
},
addScreenshotButton(controls) {
const button = document.createElement("button");
button.className = "ytp-button ytp-screenshot-button";
button.setAttribute("title", "Take screenshot");
button.innerHTML = `
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19.83,8.77l-2.77,2.84H6.29A1.79,1.79,0,0,0,4.5,13.4V36.62a1.8,1.8,0,0,0,1.79,1.8H41.71a1.8,1.8,0,0,0,1.79-1.8V13.4a1.79,1.79,0,0,0-1.79-1.79H30.94L28.17,8.77Zm18.93,5.74a1.84,1.84,0,1,1,0,3.68A1.84,1.84,0,0,1,38.76,14.51ZM24,17.71a8.51,8.51,0,1,1-8.51,8.51A8.51,8.51,0,0,1,24,17.71Z"/>
</svg>
`;
button.addEventListener("click", this.captureFrame.bind(this));
controls.insertBefore(button, controls.firstChild);
},
addDownloadButton(controls) {
if (!this.settings.enableDownload) return;
const button = document.createElement("div");
button.className = "ytp-button ytp-download-button";
button.setAttribute("title", "Download options");
button.setAttribute("tabindex", "0");
button.setAttribute("role", "button");
button.setAttribute("aria-haspopup", "true");
button.setAttribute("aria-expanded", "false");
button.style.display = "inline-block";
button.style.padding = "0 10px 0 0";
button.style.height = "36px";
button.innerHTML = `
<svg fill="currentColor" width="24" height="24" viewBox="0 0 256 256" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;margin:auto;vertical-align:middle;">
<path d="M83.17188,112.83984a4.00026,4.00026,0,0,1,5.65624-5.6582L124,142.34473V40a4,4,0,0,1,8,0V142.34473l35.17188-35.16309a4.00026,4.00026,0,0,1,5.65624,5.6582l-42,41.98926a4.00088,4.00088,0,0,1-5.65624,0ZM216,148a4.0002,4.0002,0,0,0-4,4v56a4.00427,4.00427,0,0,1-4,4H48a4.00427,4.00427,0,0,1-4-4V152a4,4,0,0,0-8,0v56a12.01343,12.01343,0,0,0,12,12H208a12.01343,12.01343,0,0,0,12-12V152A4.0002,4.0002,0,0,0,216,148Z"/>
</svg>
`;
// Dropdown options
const options = document.createElement("div");
options.className = "download-options";
options.style.display = "none";
options.style.position = "fixed";
options.style.background = "var(--yt-glass-bg)";
options.style.color = "var(--yt-text-primary)";
options.style.borderRadius = "var(--yt-radius-sm)";
options.style.width = "150px";
options.style.zIndex = "99999";
options.style.boxShadow = "var(--yt-glass-shadow)";
options.style.border = "1px solid var(--yt-glass-border)";
options.style.overflow = "hidden";
options.style.backdropFilter = "blur(18px) saturate(180%)";
options.style.webkitBackdropFilter = "blur(18px) saturate(180%)";
options.setAttribute("role", "menu");
// Position dropdown below button
function positionDropdown() {
const rect = button.getBoundingClientRect();
options.style.left = `${rect.left + rect.width / 2 - 75}px`;
options.style.bottom = `${window.innerHeight - rect.top + 12}px`;
}
// Helper to open download site
function openDownloadSite(url) {
window.open(url, "_blank");
options.style.display = "none";
button.setAttribute("aria-expanded", "false");
}
// Get current video URL
const videoId = new URLSearchParams(location.search).get("v");
const videoUrl = videoId ? `https://www.youtube.com/watch?v=${videoId}` : location.href;
// List of download sites
const downloadSites = [
{ name: "Y2Mate", url: `https://www.y2mate.com/youtube/${videoId || ""}` },
{ name: "9xbuddy", url: `https://9xbuddy.org/process?url=${encodeURIComponent(videoUrl)}` },
{ name: "by YTDL", url: `https://github.com/diorhc/YouTube-Downloader` }
];
// Centered list
const list = document.createElement("div");
list.style.display = "flex";
list.style.flexDirection = "column";
list.style.alignItems = "center";
list.style.justifyContent = "center";
list.style.width = "100%";
downloadSites.forEach(site => {
const opt = document.createElement("div");
opt.className = "download-option-item";
opt.textContent = site.name;
opt.style.cursor = "pointer";
opt.style.padding = "10px";
opt.setAttribute("role", "menuitem");
opt.setAttribute("tabindex", "0");
opt.style.textAlign = "center";
opt.addEventListener("click", () => openDownloadSite(site.url));
opt.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
openDownloadSite(site.url);
}
});
list.appendChild(opt);
});
options.appendChild(list);
button.appendChild(options);
let dropdownTimeout;
function showDropdown() {
clearTimeout(dropdownTimeout);
positionDropdown();
options.style.display = "block";
button.setAttribute("aria-expanded", "true");
}
function hideDropdown() {
dropdownTimeout = setTimeout(() => {
options.style.display = "none";
button.setAttribute("aria-expanded", "false");
}, 150);
}
button.addEventListener("mouseenter", showDropdown);
button.addEventListener("mouseleave", hideDropdown);
options.addEventListener("mouseenter", showDropdown);
options.addEventListener("mouseleave", hideDropdown);
button.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
if (options.style.display === "block") {
hideDropdown();
} else {
showDropdown();
}
}
});
controls.insertBefore(button, controls.firstChild);
},
addSpeedControlButton(controls) {
const speedBtn = document.createElement("div");
speedBtn.className = "ytp-button speed-control-btn";
speedBtn.innerHTML = `<span>${this.speedControl.currentSpeed}×</span>`;
const speedOptions = document.createElement("div");
speedOptions.className = "speed-options";
[0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0].forEach((speed) => {
const option = document.createElement("div");
option.className = `speed-option-item${parseFloat(speed) === this.speedControl.currentSpeed ? " speed-option-active" : ""}`;
option.textContent = `${speed}x`;
option.dataset.speed = speed;
option.addEventListener("click", () => this.changeSpeed(speed));
speedOptions.appendChild(option);
});
speedBtn.appendChild(speedOptions);
let isHovering = false;
speedBtn.addEventListener("mouseenter", () => {
isHovering = true;
speedOptions.style.display = "block";
});
speedBtn.addEventListener("mouseleave", () => {
isHovering = false;
setTimeout(() => {
if (!isHovering) speedOptions.style.display = "none";
}, 150);
});
controls.insertBefore(speedBtn, controls.firstChild);
},
captureFrame() {
const video = this.getElement("video", false);
if (!video) return;
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const videoTitle = document.title.replace(/\s-\sYouTube$/, "").trim();
const link = document.createElement("a");
link.href = canvas.toDataURL("image/png");
link.download = `${videoTitle}.png`;
link.click();
},
showNotification(message, duration = 2000) {
const existingNotification = this.getElement(".youtube-enhancer-notification", false);
if (existingNotification) existingNotification.remove();
const notification = document.createElement("div");
notification.className = "youtube-enhancer-notification";
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = "0";
setTimeout(() => notification.remove(), 500);
}, duration);
},
handleFullscreenChange() {
const isFullscreen = document.fullscreenElement || document.webkitFullscreenElement;
document.querySelectorAll(".ytp-screenshot-button, .ytp-cobalt-button").forEach((button) => {
button.style.bottom = isFullscreen ? "15px" : "12px";
});
},
changeSpeed(speed) {
speed = parseFloat(speed);
this.speedControl.currentSpeed = speed;
localStorage.setItem(this.speedControl.storageKey, speed);
const speedBtn = this.getElement(".speed-control-btn span", false);
if (speedBtn) speedBtn.textContent = `${speed}×`;
document.querySelectorAll(".speed-option-item").forEach((option) => {
option.classList.toggle("speed-option-active", parseFloat(option.dataset.speed) === speed);
});
this.applyCurrentSpeed();
this.showSpeedIndicator(speed);
},
applyCurrentSpeed() {
document.querySelectorAll("video").forEach((video) => {
if (video && video.playbackRate !== this.speedControl.currentSpeed) {
video.playbackRate = this.speedControl.currentSpeed;
}
});
},
setupVideoObserver() {
if (this._speedInterval) clearInterval(this._speedInterval);
this._speedInterval = setInterval(() => this.applyCurrentSpeed(), 1000);
},
setupNavigationObserver() {
let lastUrl = location.href;
document.addEventListener("fullscreenchange", this.handleFullscreenChange.bind(this));
document.addEventListener("yt-navigate-finish", () => {
if (location.href.includes("watch?v=")) this.setupCurrentPage();
this.addSettingsButtonToHeader();
});
const observer = new MutationObserver(() => {
if (lastUrl !== location.href) {
lastUrl = location.href;
if (location.href.includes("watch?v=")) {
setTimeout(() => this.setupCurrentPage(), 500);
}
this.addSettingsButtonToHeader();
}
});
observer.observe(document.body, { childList: true, subtree: true });
},
showSpeedIndicator(speed) {
const indicator = document.getElementById("speed-indicator");
if (!indicator) return;
if (this.speedControl.activeAnimationId) {
cancelAnimationFrame(this.speedControl.activeAnimationId);
}
indicator.textContent = `${speed}×`;
indicator.style.display = "block";
indicator.style.opacity = "0.8";
let startTime = performance.now();
const fadeOut = (timestamp) => {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / 1500, 1);
indicator.style.opacity = 0.8 * (1 - progress);
if (progress < 1) {
this.speedControl.activeAnimationId = requestAnimationFrame(fadeOut);
} else {
indicator.style.display = "none";
this.speedControl.activeAnimationId = null;
}
};
this.speedControl.activeAnimationId = requestAnimationFrame(fadeOut);
},
};
document.readyState === "loading"
? document.addEventListener("DOMContentLoaded", YouTubeEnhancer.init.bind(YouTubeEnhancer))
: YouTubeEnhancer.init();
})();
// Update checker module
(function () {
'use strict';
const UPDATE_CONFIG = {
enabled: true,
checkInterval: 24 * 60 * 60 * 1000, // 24 hours
updateUrl: 'https://update.greasyfork.org/scripts/537017/YouTube%20%2B.meta.js',
currentVersion: '1.9',
storageKey: 'youtube_plus_update_check',
notificationDuration: 8000,
autoInstallUrl: 'https://update.greasyfork.org/scripts/537017/YouTube%20%2B.user.js'
};
const updateState = {
lastCheck: 0,
lastVersion: UPDATE_CONFIG.currentVersion,
updateAvailable: false,
checkInProgress: false,
updateDetails: null
};
// Optimized utilities
const utils = {
loadSettings: () => {
try {
const saved = localStorage.getItem(UPDATE_CONFIG.storageKey);
if (saved) Object.assign(updateState, JSON.parse(saved));
} catch (e) { }
},
saveSettings: () => {
try {
localStorage.setItem(UPDATE_CONFIG.storageKey, JSON.stringify({
lastCheck: updateState.lastCheck,
lastVersion: updateState.lastVersion,
updateAvailable: updateState.updateAvailable,
updateDetails: updateState.updateDetails
}));
} catch (e) { }
},
compareVersions: (v1, v2) => {
const normalize = v => v.replace(/[^\d.]/g, '').split('.').map(n => parseInt(n) || 0);
const [parts1, parts2] = [normalize(v1), normalize(v2)];
const maxLength = Math.max(parts1.length, parts2.length);
for (let i = 0; i < maxLength; i++) {
const diff = (parts1[i] || 0) - (parts2[i] || 0);
if (diff !== 0) return diff;
}
return 0;
},
parseMetadata: text => {
const extractField = field => text.match(new RegExp(`@${field}\\s+([^\\r\\n]+)`))?.[1]?.trim();
return {
version: extractField('version'),
description: extractField('description') || '',
downloadUrl: extractField('downloadURL') || UPDATE_CONFIG.autoInstallUrl
};
},
formatTimeAgo: timestamp => {
if (!timestamp) return 'Never';
const diffMs = Date.now() - timestamp;
const diffDays = Math.floor(diffMs / 86400000);
const diffHours = Math.floor(diffMs / 3600000);
const diffMinutes = Math.floor(diffMs / 60000);
if (diffDays > 0) return `${diffDays}d ago`;
if (diffHours > 0) return `${diffHours}h ago`;
if (diffMinutes > 0) return `${diffMinutes}m ago`;
return 'Just now';
},
showNotification: (text, type = 'info', duration = 3000) => {
const colors = {
info: 'rgba(34, 197, 94, 0.9)',
error: 'rgba(220, 38, 38, 0.9)',
update: 'linear-gradient(135deg, rgba(255, 69, 0, 0.95), rgba(255, 140, 0, 0.95))'
};
const notification = document.createElement('div');
notification.className = 'youtube-enhancer-notification';
notification.style.cssText = `
position: fixed; bottom: 20px; right: 20px; z-index: 10001;
background: ${colors[type] || colors.info}; color: white;
padding: 12px 20px; border-radius: 8px; max-width: 300px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3); backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.2);
animation: slideInFromRight 0.3s ease-out;
`;
notification.textContent = text;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
notification.style.opacity = '0';
setTimeout(() => notification.remove(), 300);
}, duration);
}
};
// Enhanced update notification
const showUpdateNotification = updateDetails => {
const notification = document.createElement('div');
notification.className = 'youtube-enhancer-notification update-notification';
notification.style.cssText = `
position: fixed; bottom: 20px; right: 20px; z-index: 10001; max-width: 350px;
background: linear-gradient(135deg, rgba(255, 69, 0, 0.95), rgba(255, 140, 0, 0.95));
color: white; padding: 16px 20px; border-radius: 12px;
box-shadow: 0 8px 32px rgba(255, 69, 0, 0.4); backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
animation: slideInFromRight 0.4s ease-out;
`;
notification.innerHTML = `
<div style="display: flex; align-items: flex-start; gap: 12px;">
<div style="background: rgba(255, 255, 255, 0.2); border-radius: 8px; padding: 8px; flex-shrink: 0;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12c0 1-1 2-1 2s-1-1-1-2 1-2 1-2 1 1 1 2z"/>
<path d="m21 12-5-5v3H8v4h8v3l5-5z"/>
</svg>
</div>
<div style="flex: 1; min-width: 0;">
<div style="font-weight: 600; font-size: 15px; margin-bottom: 4px;">YouTube + Update Available</div>
<div style="font-size: 13px; opacity: 0.9; margin-bottom: 12px;">
Version ${updateDetails.version} • ${updateDetails.description || 'New features and improvements'}
</div>
<div style="display: flex; gap: 8px;">
<button id="update-install-btn" style="
background: rgba(255, 255, 255, 0.9); color: #ff4500; border: none;
padding: 8px 16px; border-radius: 6px; cursor: pointer;
font-size: 13px; font-weight: 600; transition: all 0.2s ease;
">Install Update</button>
<button id="update-dismiss-btn" style="
background: rgba(255, 255, 255, 0.1); color: white;
border: 1px solid rgba(255, 255, 255, 0.3); padding: 8px 12px;
border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s ease;
">Later</button>
</div>
</div>
<button id="update-close-btn" style="
background: none; border: none; color: rgba(255, 255, 255, 0.8);
cursor: pointer; padding: 4px; border-radius: 4px; flex-shrink: 0;
">✕</button>
</div>
<style>
@keyframes slideInFromRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>
`;
document.body.appendChild(notification);
const removeNotification = () => {
notification.style.animation = 'slideInFromRight 0.3s ease-in reverse';
setTimeout(() => notification.remove(), 300);
};
// Event handlers
notification.querySelector('#update-install-btn').addEventListener('click', () => {
try {
window.open(updateDetails.downloadUrl, '_blank');
sessionStorage.setItem('update_dismissed', updateDetails.version);
removeNotification();
setTimeout(() => utils.showNotification('Update started! Follow your userscript manager instructions.'), 500);
} catch (error) {
console.error('Error installing update:', error);
window.open('https://greasyfork.org/en/scripts/537017-youtube', '_blank');
removeNotification();
}
});
notification.querySelector('#update-dismiss-btn').addEventListener('click', () => {
sessionStorage.setItem('update_dismissed', updateDetails.version);
removeNotification();
});
notification.querySelector('#update-close-btn').addEventListener('click', removeNotification);
// Auto-dismiss
setTimeout(() => {
if (notification.isConnected) removeNotification();
}, UPDATE_CONFIG.notificationDuration);
};
// Optimized update checker
const checkForUpdates = async (force = false) => {
if (!UPDATE_CONFIG.enabled || updateState.checkInProgress) return;
const now = Date.now();
if (!force && (now - updateState.lastCheck) < UPDATE_CONFIG.checkInterval) return;
updateState.checkInProgress = true;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(UPDATE_CONFIG.updateUrl, {
method: 'GET',
cache: 'no-cache',
signal: controller.signal,
headers: { 'Accept': 'text/plain', 'User-Agent': 'YouTube+ UpdateChecker' }
});
clearTimeout(timeoutId);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
const metaText = await response.text();
const updateDetails = utils.parseMetadata(metaText);
if (updateDetails.version) {
updateState.lastCheck = now;
updateState.lastVersion = updateDetails.version;
updateState.updateDetails = updateDetails;
const comparison = utils.compareVersions(UPDATE_CONFIG.currentVersion, updateDetails.version);
updateState.updateAvailable = comparison < 0;
if (updateState.updateAvailable &&
(force || sessionStorage.getItem('update_dismissed') !== updateDetails.version)) {
showUpdateNotification(updateDetails);
console.log(`YouTube + Update available: ${updateDetails.version}`);
} else if (force) {
utils.showNotification(
updateState.updateAvailable
? `Update ${updateDetails.version} available!`
: `You're using the latest version (${UPDATE_CONFIG.currentVersion})`
);
}
utils.saveSettings();
}
} catch (error) {
console.error('Update check failed:', error);
if (force) utils.showNotification(`Update check failed: ${error.message}`, 'error', 4000);
} finally {
updateState.checkInProgress = false;
}
};
// Optimized settings UI
const addUpdateSettings = () => {
const aboutSection = document.querySelector('.ytp-plus-settings-section[data-section="about"]');
if (!aboutSection || document.querySelector('.update-settings-container')) return;
const updateContainer = document.createElement('div');
updateContainer.className = 'update-settings-container';
updateContainer.style.cssText = `
padding: 16px; margin-top: 20px; border-radius: 12px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
border: 1px solid var(--yt-glass-border); backdrop-filter: blur(8px);
`;
const lastCheckTime = utils.formatTimeAgo(updateState.lastCheck);
updateContainer.innerHTML = `
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
<h3 style="margin: 0; font-size: 16px; font-weight: 600; color: var(--yt-spec-text-primary);">
Enhanced YouTube experience with powerful features
</h3>
</div>
<div style="display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center;
padding: 16px; background: rgba(255, 255, 255, 0.03); border-radius: 10px; margin-bottom: 16px;">
<div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
<span style="font-size: 14px; font-weight: 600; color: var(--yt-spec-text-primary);">Current Version</span>
<span style="font-size: 13px; font-weight: 600; color: var(--yt-spec-text-primary);
padding: 3px 10px; background: rgba(255, 255, 255, 0.1); border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);">${UPDATE_CONFIG.currentVersion}</span>
</div>
<div style="font-size: 12px; color: var(--yt-spec-text-secondary);">
Last checked: <span style="font-weight: 500;">${lastCheckTime}</span>
${updateState.lastVersion && updateState.lastVersion !== UPDATE_CONFIG.currentVersion ?
`<br>Latest available: <span style="color: #ff6666; font-weight: 600;">${updateState.lastVersion}</span>` : ''
}
</div>
</div>
${updateState.updateAvailable ? `
<div style="display: flex; flex-direction: column; align-items: flex-end; gap: 8px;">
<div style="display: flex; align-items: center; gap: 8px; padding: 6px 12px;
background: linear-gradient(135deg, rgba(255, 68, 68, 0.2), rgba(255, 68, 68, 0.3));
border: 1px solid rgba(255, 68, 68, 0.4); border-radius: 20px;">
<div style="width: 6px; height: 6px; background: #ff4444; border-radius: 50%; animation: pulse 2s infinite;"></div>
<span style="font-size: 11px; color: #ff6666; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
Update Available
</span>
</div>
<button id="install-update-btn" style="background: linear-gradient(135deg, #ff4500, #ff6b35);
color: white; border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer;
font-size: 12px; font-weight: 600; transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(255, 69, 0, 0.3);">Install Now</button>
</div>
` : `
<div style="display: flex; align-items: center; gap: 8px; padding: 6px 12px;
background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.3));
border: 1px solid rgba(34, 197, 94, 0.4); border-radius: 20px;">
<div style="width: 6px; height: 6px; background: #22c55e; border-radius: 50%;"></div>
<span style="font-size: 11px; color: #22c55e; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">
Up to Date
</span>
</div>
`}
</div>
<div style="display: flex; gap: 12px;">
<button class="ytp-plus-button ytp-plus-button-primary" id="manual-update-check"
style="flex: 1; padding: 12px; font-size: 13px; font-weight: 600;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
<path d="M21.5 2v6h-6M2.5 22v-6h6M19.13 11.48A10 10 0 0 0 12 2C6.48 2 2 6.48 2 12c0 .34.02.67.05 1M4.87 12.52A10 10 0 0 0 12 22c5.52 0 10-4.48 10-10 0-.34-.02-.67-.05-1"/>
</svg>
Check for Updates
</button>
<button class="ytp-plus-button" id="open-update-page"
style="padding: 12px 16px; font-size: 13px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2);">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15,3 21,3 21,9"/>
<line x1="10" y1="14" x2="21" y2="3"/>
</svg>
</button>
</div>
<style>
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.7; transform: scale(1.1); } }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
</style>
`;
aboutSection.appendChild(updateContainer);
// Event listeners with optimization
const attachClickHandler = (id, handler) => {
const element = document.getElementById(id);
if (element) element.addEventListener('click', handler);
};
attachClickHandler('manual-update-check', async e => {
const button = e.target;
const originalHTML = button.innerHTML;
button.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
style="margin-right: 6px; animation: spin 1s linear infinite;">
<path d="M21.5 2v6h-6M2.5 22v-6h6M19.13 11.48A10 10 0 0 0 12 2C6.48 2 2 6.48 2 12c0 .34.02.67.05 1M4.87 12.52A10 10 0 0 0 12 22c5.52 0 10-4.48 10-10 0-.34-.02-.67-.05-1"/>
</svg>
Checking...
`;
button.disabled = true;
await checkForUpdates(true);
setTimeout(() => {
button.innerHTML = originalHTML;
button.disabled = false;
}, 1000);
});
attachClickHandler('install-update-btn', () => {
const url = updateState.updateDetails?.downloadUrl || 'https://greasyfork.org/en/scripts/537017-youtube';
window.open(url, '_blank');
});
attachClickHandler('open-update-page', () => {
window.open('https://greasyfork.org/en/scripts/537017-youtube', '_blank');
});
};
// Optimized initialization
const init = () => {
utils.loadSettings();
// Initial check with delay
setTimeout(() => checkForUpdates(), 3000);
// Periodic checks
const intervalId = setInterval(() => checkForUpdates(), UPDATE_CONFIG.checkInterval);
window.addEventListener('beforeunload', () => clearInterval(intervalId));
// Optimized settings modal observer
let settingsObserved = false;
const observer = new MutationObserver(mutations => {
if (settingsObserved) return;
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1 && node.classList?.contains('ytp-plus-settings-modal')) {
settingsObserved = true;
setTimeout(addUpdateSettings, 100);
return;
}
}
}
if (document.querySelector('.ytp-plus-settings-nav-item[data-section="about"].active:not([data-observed])')) {
document.querySelector('.ytp-plus-settings-nav-item[data-section="about"]').setAttribute('data-observed', '');
setTimeout(addUpdateSettings, 50);
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Optimized click handler
document.addEventListener('click', e => {
if (e.target.classList?.contains('ytp-plus-settings-nav-item') && e.target.dataset.section === 'about') {
setTimeout(addUpdateSettings, 50);
}
}, { passive: true, capture: true });
console.log('YouTube + Update Checker initialized', {
version: UPDATE_CONFIG.currentVersion,
enabled: UPDATE_CONFIG.enabled,
lastCheck: new Date(updateState.lastCheck).toLocaleString(),
updateAvailable: updateState.updateAvailable
});
};
// Start
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
// Enhanced Tabviews
(function () {
'use strict';
// Configuration
const config = {
enabled: true,
storageKey: 'youtube_top_button_settings'
};
// Styles
const addStyles = () => {
if (document.getElementById('custom-styles')) return;
const style = document.createElement('style');
style.id = 'custom-styles';
style.textContent = `
:root{--scrollbar-width:8px;--scrollbar-track:transparent;--scrollbar-thumb:rgba(144,144,144,.5);--scrollbar-thumb-hover:rgba(170,170,170,.7);--scrollbar-thumb-active:rgba(190,190,190,.9);}
::-webkit-scrollbar{width:var(--scrollbar-width)!important;height:var(--scrollbar-width)!important;}
::-webkit-scrollbar-track{background:var(--scrollbar-track)!important;border-radius:4px!important;}
::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb)!important;border-radius:4px!important;transition:background .2s!important;}
::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)!important;}
::-webkit-scrollbar-thumb:active{background:var(--scrollbar-thumb-active)!important;}
::-webkit-scrollbar-corner{background:transparent!important;}
*{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) var(--scrollbar-track);}
html[dark]{--scrollbar-thumb:rgba(144,144,144,.4);--scrollbar-thumb-hover:rgba(170,170,170,.6);--scrollbar-thumb-active:rgba(190,190,190,.8);}
.top-button{position:absolute;bottom:16px;right:16px;width:40px;height:40px;background:var(--yt-top-btn-bg,rgba(0,0,0,.7));color:var(--yt-top-btn-color,#fff);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:1000;opacity:0;visibility:hidden;transition:all .3s;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%);border:1px solid var(--yt-top-btn-border,rgba(255,255,255,.1));background:rgba(255,255,255,.12);box-shadow:0 8px 32px 0 rgba(31,38,135,.18);}
.top-button:hover{background:var(--yt-top-btn-hover,rgba(0,0,0,.15));transform:translateY(-2px) scale(1.07);box-shadow:0 8px 32px rgba(0,0,0,.25);}
.top-button.visible{opacity:1;visibility:visible;}
.top-button svg{transition:transform .2s;}
.top-button:hover svg{transform:translateY(-1px) scale(1.1);}
html[dark]{--yt-top-btn-bg:rgba(255,255,255,.10);--yt-top-btn-color:#fff;--yt-top-btn-border:rgba(255,255,255,.18);--yt-top-btn-hover:rgba(255,255,255,.18);}
html:not([dark]){--yt-top-btn-bg:rgba(255,255,255,.12);--yt-top-btn-color:#222;--yt-top-btn-border:rgba(0,0,0,.08);--yt-top-btn-hover:rgba(255,255,255,.18);}
ytd-watch-flexy:not([tyt-tab^="#"]) .top-button{display:none;}
`;
document.head.appendChild(style);
};
// Button functionality
const handleScroll = (scrollContainer) => {
const button = document.getElementById('right-tabs-top-button');
if (!button || !scrollContainer) return;
button.classList.toggle('visible', scrollContainer.scrollTop > 100);
};
const setupScrollListener = () => {
document.querySelectorAll('.tab-content-cld').forEach(tab => {
tab.removeEventListener('scroll', tab._topButtonScrollHandler);
});
const activeTab = document.querySelector('#right-tabs .tab-content-cld:not(.tab-content-hidden)');
if (activeTab) {
const scrollHandler = () => handleScroll(activeTab);
activeTab._topButtonScrollHandler = scrollHandler;
activeTab.addEventListener('scroll', scrollHandler, { passive: true });
handleScroll(activeTab);
}
};
const createButton = () => {
const rightTabs = document.querySelector('#right-tabs');
if (!rightTabs || document.getElementById('right-tabs-top-button')) return;
if (!config.enabled) return;
const button = document.createElement('button');
button.id = 'right-tabs-top-button';
button.className = 'top-button';
button.title = 'Scroll to top';
button.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>';
button.addEventListener('click', () => {
const activeTab = document.querySelector('#right-tabs .tab-content-cld:not(.tab-content-hidden)');
if (activeTab) activeTab.scrollTo({ top: 0, behavior: 'smooth' });
});
rightTabs.style.position = 'relative';
rightTabs.appendChild(button);
setupScrollListener();
};
// Observers
const observeTabChanges = () => {
const observer = new MutationObserver(mutations => {
if (mutations.some(m =>
m.type === 'attributes' &&
m.attributeName === 'class' &&
m.target.classList.contains('tab-content-cld'))) {
setTimeout(setupScrollListener, 100);
}
});
const rightTabs = document.querySelector('#right-tabs');
if (rightTabs) {
observer.observe(rightTabs, {
attributes: true,
subtree: true,
attributeFilter: ['class']
});
}
};
// Events
const setupEvents = () => {
document.addEventListener('click', (e) => {
if (e.target.closest('.tab-btn[tyt-tab-content]')) {
setTimeout(setupScrollListener, 100);
}
}, true);
};
// Initialize
const init = () => {
addStyles();
setupEvents();
const checkForTabs = () => {
if (document.querySelector('#right-tabs')) {
createButton();
observeTabChanges();
} else {
setTimeout(checkForTabs, 500);
}
};
checkForTabs();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
// Shorts Keyboard controls
(function () {
'use strict';
// Configuration
const config = {
enabled: true,
shortcuts: {
seekBackward: { key: 'ArrowLeft', description: 'Seek backward 5s' },
seekForward: { key: 'ArrowRight', description: 'Seek forward 5s' },
volumeUp: { key: '+', description: 'Volume up' },
volumeDown: { key: '-', description: 'Volume down' },
mute: { key: 'm', description: 'Mute/Unmute' },
showHelp: { key: '?', description: 'Show/Hide help', editable: false }
},
storageKey: 'youtube_shorts_keyboard_settings'
};
// State management
const state = {
helpVisible: false,
lastAction: null,
actionTimeout: null,
editingShortcut: null,
cachedVideo: null,
lastVideoCheck: 0
};
// Optimized video selector with caching
const getCurrentVideo = (() => {
const selectors = [
'ytd-reel-video-renderer[is-active] video',
'#shorts-player video',
'video'
];
return () => {
const now = Date.now();
if (state.cachedVideo?.isConnected && now - state.lastVideoCheck < 100) {
return state.cachedVideo;
}
for (const selector of selectors) {
const video = document.querySelector(selector);
if (video) {
state.cachedVideo = video;
state.lastVideoCheck = now;
return video;
}
}
state.cachedVideo = null;
return null;
};
})();
// Optimized utilities
const utils = {
isInShortsPage: () => location.pathname.startsWith('/shorts/'),
isInputFocused: () => {
const el = document.activeElement;
return el?.matches?.('input, textarea, [contenteditable="true"]') || el?.isContentEditable;
},
loadSettings: () => {
try {
const saved = localStorage.getItem(config.storageKey);
if (saved) Object.assign(config, JSON.parse(saved));
} catch (e) { }
},
saveSettings: () => {
try {
localStorage.setItem(config.storageKey, JSON.stringify({
enabled: config.enabled,
shortcuts: config.shortcuts
}));
} catch (e) { }
},
getDefaultShortcuts: () => ({
seekBackward: { key: 'ArrowLeft', description: 'Seek backward 5s' },
seekForward: { key: 'ArrowRight', description: 'Seek forward 5s' },
volumeUp: { key: '+', description: 'Volume up' },
volumeDown: { key: '-', description: 'Volume down' },
mute: { key: 'm', description: 'Mute/Unmute' },
showHelp: { key: '?', description: 'Show/Hide help', editable: false }
})
};
// Optimized feedback system
const feedback = (() => {
let element = null;
const create = () => {
if (element) return element;
element = document.createElement('div');
element.id = 'shorts-keyboard-feedback';
element.style.cssText = `
position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
background:var(--shorts-feedback-bg,rgba(255,255,255,.1));
backdrop-filter:blur(16px) saturate(150%);
border:1px solid var(--shorts-feedback-border,rgba(255,255,255,.15));
border-radius:20px;
color:var(--shorts-feedback-color,#fff);
padding:18px 32px;font-size:20px;font-weight:700;
z-index:10000;opacity:0;visibility:hidden;pointer-events:none;
transition:all .3s cubic-bezier(.4,0,.2,1);text-align:center;
box-shadow:0 8px 32px rgba(0,0,0,.4);
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.2);
box-shadow: 0 8px 32px 0 rgba(31,38,135,0.37);
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
`;
document.body.appendChild(element);
return element;
};
return {
show: (text) => {
state.lastAction = text;
clearTimeout(state.actionTimeout);
const el = create();
el.textContent = text;
requestAnimationFrame(() => {
el.style.opacity = '1';
el.style.visibility = 'visible';
el.style.transform = 'translate(-50%, -50%) scale(1.05)';
});
state.actionTimeout = setTimeout(() => {
el.style.opacity = '0';
el.style.visibility = 'hidden';
el.style.transform = 'translate(-50%, -50%) scale(0.95)';
}, 1500);
}
};
})();
// Optimized actions
const actions = {
seekBackward: () => {
const video = getCurrentVideo();
if (video) {
video.currentTime = Math.max(0, video.currentTime - 5);
feedback.show('-5s');
}
},
seekForward: () => {
const video = getCurrentVideo();
if (video) {
video.currentTime = Math.min(video.duration || Infinity, video.currentTime + 5);
feedback.show('+5s');
}
},
volumeUp: () => {
const video = getCurrentVideo();
if (video) {
video.volume = Math.min(1, video.volume + 0.1);
feedback.show(`${Math.round(video.volume * 100)}%`);
}
},
volumeDown: () => {
const video = getCurrentVideo();
if (video) {
video.volume = Math.max(0, video.volume - 0.1);
feedback.show(`${Math.round(video.volume * 100)}%`);
}
},
mute: () => {
const video = getCurrentVideo();
if (video) {
video.muted = !video.muted;
feedback.show(video.muted ? '🔇' : '🔊');
}
},
showHelp: () => helpPanel.toggle()
};
// Help panel system
const helpPanel = (() => {
let panel = null;
const create = () => {
if (panel) return panel;
panel = document.createElement('div');
panel.id = 'shorts-keyboard-help';
panel.className = 'shorts-help-panel';
const render = () => {
panel.innerHTML = `
<div class="help-header">
<h3>Keyboard Shortcuts</h3>
<button class="help-close">✕</button>
</div>
<div class="help-content">
${Object.entries(config.shortcuts).map(([action, shortcut]) =>
`<div class="help-item">
<kbd data-action="${action}" ${shortcut.editable === false ? 'class="non-editable"' : ''}>${shortcut.key === ' ' ? 'Space' : shortcut.key}</kbd>
<span>${shortcut.description}</span>
</div>`
).join('')}
</div>
<div class="help-footer">
<button class="reset-all-shortcuts">Reset All</button>
</div>
`;
panel.querySelector('.help-close').onclick = () => helpPanel.hide();
panel.querySelector('.reset-all-shortcuts').onclick = () => {
if (confirm('Reset all shortcuts?')) {
config.shortcuts = utils.getDefaultShortcuts();
utils.saveSettings();
feedback.show('Shortcuts reset');
render();
}
};
panel.querySelectorAll('kbd[data-action]:not(.non-editable)').forEach(kbd => {
kbd.onclick = () => editShortcut(kbd.dataset.action, config.shortcuts[kbd.dataset.action].key);
});
};
render();
document.body.appendChild(panel);
return panel;
};
return {
show: () => {
const p = create();
p.classList.add('visible');
state.helpVisible = true;
p.focus();
},
hide: () => {
if (panel) {
panel.classList.remove('visible');
state.helpVisible = false;
}
},
toggle: () => state.helpVisible ? helpPanel.hide() : helpPanel.show(),
refresh: () => {
if (panel) {
panel.remove();
panel = null;
}
}
};
})();
// Shortcut editing
const editShortcut = (actionKey, currentKey) => {
const dialog = document.createElement('div');
dialog.className = 'shortcut-edit-dialog';
dialog.innerHTML = `
<div class="shortcut-edit-content">
<h4>Edit: ${config.shortcuts[actionKey].description}</h4>
<p>Press any key to set as new shortcut</p>
<div class="current-shortcut">Current: <kbd>${currentKey === ' ' ? 'Space' : currentKey}</kbd></div>
<button class="shortcut-cancel">Cancel</button>
</div>
`;
document.body.appendChild(dialog);
state.editingShortcut = actionKey;
const handleKey = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.key === 'Escape') return cleanup();
const conflict = Object.keys(config.shortcuts).find(key =>
key !== actionKey && config.shortcuts[key].key === e.key
);
if (conflict) {
feedback.show(`Key "${e.key}" already used`);
return;
}
config.shortcuts[actionKey].key = e.key;
utils.saveSettings();
feedback.show('Shortcut updated');
helpPanel.refresh();
cleanup();
};
const cleanup = () => {
document.removeEventListener('keydown', handleKey, true);
dialog.remove();
state.editingShortcut = null;
};
dialog.querySelector('.shortcut-cancel').onclick = cleanup;
dialog.onclick = (e) => e.target === dialog && cleanup();
document.addEventListener('keydown', handleKey, true);
};
// Optimized styles with glassmorphism
const addStyles = () => {
if (document.getElementById('shorts-keyboard-styles')) return;
const style = document.createElement('style');
style.id = 'shorts-keyboard-styles';
style.textContent = `
:root{--shorts-feedback-bg:rgba(255,255,255,.15);--shorts-feedback-border:rgba(255,255,255,.2);--shorts-feedback-color:#fff;--shorts-help-bg:rgba(255,255,255,.15);--shorts-help-border:rgba(255,255,255,.2);--shorts-help-color:#fff;}
html[dark],body[dark]{--shorts-feedback-bg:rgba(34,34,34,.7);--shorts-feedback-border:rgba(255,255,255,.15);--shorts-feedback-color:#fff;--shorts-help-bg:rgba(34,34,34,.7);--shorts-help-border:rgba(255,255,255,.1);--shorts-help-color:#fff;}
html:not([dark]){--shorts-feedback-bg:rgba(255,255,255,.95);--shorts-feedback-border:rgba(0,0,0,.08);--shorts-feedback-color:#222;--shorts-help-bg:rgba(255,255,255,.98);--shorts-help-border:rgba(0,0,0,.08);--shorts-help-color:#222;}
.shorts-help-panel{position:fixed;top:50%;left:25%;transform:translate(-50%,-50%) scale(.9);background:var(--shorts-help-bg,rgba(255,255,255,.15));backdrop-filter:blur(15px) saturate(180%);-webkit-backdrop-filter:blur(15px) saturate(180%);border:1px solid var(--shorts-help-border,rgba(255,255,255,.2));color:var(--shorts-help-color,#fff);border-radius:20px;box-shadow:0 8px 32px rgba(0,0,0,.5);z-index:10001;opacity:0;visibility:hidden;transition:all .3s ease;width:340px;max-width:95vw;max-height:80vh;overflow:hidden;outline:none}
.shorts-help-panel.visible{opacity:1;visibility:visible;transform:translate(-50%,-50%) scale(1)}
.help-header{display:flex;justify-content:space-between;align-items:center;padding:24px 24px 12px 24px;border-bottom:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05)}
.help-header h3{margin:0;font-size:20px;font-weight:700}
.help-close{background:none;border:none;color:inherit;font-size:26px;cursor:pointer;padding:4px 8px;border-radius:6px;transition:background-color .2s}
.help-close:hover{color:var(--yt-accent,#ff0000);}
.help-content{padding:18px 24px;max-height:400px;overflow-y:auto}
.help-item{display:flex;align-items:center;margin-bottom:14px;gap:18px}
.help-item kbd{background:rgba(255,255,255,.15);color:inherit;padding:7px 14px;border-radius:8px;font-family:monospace;font-size:15px;font-weight:700;min-width:60px;text-align:center;border:1.5px solid rgba(255,255,255,.2);cursor:pointer;transition:all .2s;position:relative}
html:not([dark]) .help-item kbd{background:rgba(0,0,0,.06);color:#222;border:1.5px solid rgba(0,0,0,.08);}
.help-item kbd:hover{background:rgba(255,255,255,.22);transform:scale(1.07)}
.help-item kbd:after{content:"✎";position:absolute;top:-7px;right:-7px;font-size:11px;opacity:0;transition:opacity .2s}
.help-item kbd:hover:after{opacity:.7}
.help-item kbd.non-editable{cursor:default;opacity:.7}
.help-item kbd.non-editable:hover{background:rgba(255,255,255,.15);transform:none}
.help-item kbd.non-editable:after{display:none}
.help-item span{font-size:15px;color:rgba(255,255,255,.92)}
html:not([dark]) .help-item span{color:#222;}
.help-footer{padding:16px 24px 20px 24px;border-top:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05);text-align:center}
.reset-all-shortcuts{background:rgba(255,255,255,.15);color:inherit;border:1.5px solid rgba(255,255,255,.2);padding:7px 18px;border-radius:8px;font-size:13px;cursor:pointer;transition:all .2s}
.reset-all-shortcuts:hover{border:1.5px solid rgba(255,69,69,.3);background:rgba(255,69,69,.3)}
.shortcut-edit-dialog{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.7);display:flex;align-items:center;justify-content:center;z-index:10002;backdrop-filter:blur(5px)}
.shortcut-edit-content{background:rgba(255,255,255,.15);color:inherit;padding:28px 32px;border-radius:18px;backdrop-filter:blur(15px) saturate(180%);-webkit-backdrop-filter:blur(15px) saturate(180%);border:1.5px solid rgba(255,255,255,.2);text-align:center;min-width:320px;box-shadow:0 4px 32px rgba(0,0,0,.3)}
html:not([dark]) .shortcut-edit-content{background:rgba(255,255,255,.98);color:#222;border:1.5px solid rgba(0,0,0,.08);}
.shortcut-edit-content h4{margin:0 0 14px;font-size:17px;font-weight:700}
.shortcut-edit-content p{margin:0 0 18px;font-size:15px;color:rgba(255,255,255,.85)}
html:not([dark]) .shortcut-edit-content p{color:#222;}
.current-shortcut{margin:18px 0;font-size:15px}
.current-shortcut kbd{background:rgba(255,255,255,.15);padding:5px 12px;border-radius:6px;font-family:monospace;border:1.5px solid rgba(255,255,255,.2)}
html:not([dark]) .current-shortcut kbd{background:rgba(0,0,0,.06);color:#222;border:1.5px solid rgba(0,0,0,.08);}
.shortcut-cancel{padding:7px 18px;border-radius:8px;border:1.5px solid rgba(255,255,255,.2);background:rgba(255,255,255,.15);color:inherit;font-size:13px;cursor:pointer;transition:all .2s}
.shortcut-cancel:hover{background:rgba(255,255,255,.22)}
@media(max-width:480px){
.shorts-help-panel{width:98vw;max-height:85vh}
.help-header{padding:16px 10px 8px 10px}
.help-content{padding:12px 10px}
.help-item{gap:10px}
.help-item kbd{min-width:44px;font-size:13px;padding:5px 7px}
.shortcut-edit-content{margin:20px;min-width:auto}
}
#shorts-keyboard-feedback{background:var(--shorts-feedback-bg,rgba(255,255,255,.15));color:var(--shorts-feedback-color,#fff);border:1.5px solid var(--shorts-feedback-border,rgba(255,255,255,.2));border-radius:20px;box-shadow:0 8px 32px 0 rgba(31,38,135,.37);backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%);}
html:not([dark]) #shorts-keyboard-feedback{background:var(--shorts-feedback-bg,rgba(255,255,255,.95));color:var(--shorts-feedback-color,#222);border:1.5px solid var(--shorts-feedback-border,rgba(0,0,0,.08));}
`;
document.head.appendChild(style);
};
// Main keyboard handler
const handleKeydown = (e) => {
if (!config.enabled || !utils.isInShortsPage() || utils.isInputFocused() || state.editingShortcut) return;
let key = e.key;
if (e.code === 'NumpadAdd') key = '+';
else if (e.code === 'NumpadSubtract') key = '-';
const action = Object.keys(config.shortcuts).find(k => config.shortcuts[k].key === key);
if (action && actions[action]) {
e.preventDefault();
e.stopPropagation();
actions[action]();
}
};
// Initialize
const init = () => {
utils.loadSettings();
addStyles();
document.addEventListener('keydown', handleKeydown, true);
document.addEventListener('click', (e) => {
if (state.helpVisible && !e.target.closest('#shorts-keyboard-help')) {
helpPanel.hide();
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && state.helpVisible) {
e.preventDefault();
helpPanel.hide();
}
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
if (utils.isInShortsPage() && !localStorage.getItem('shorts_keyboard_help_shown')) {
setTimeout(() => {
feedback.show('Press ? for shortcuts');
localStorage.setItem('shorts_keyboard_help_shown', 'true');
}, 2000);
}
})();
// YouTube Picture-in-Picture settings
(function () {
"use strict";
// PiP settings with defaults
const pipSettings = {
enabled: true,
shortcut: { key: "P", shiftKey: true, altKey: false, ctrlKey: false },
storageKey: "youtube_pip_settings"
};
// Load/save settings
const loadSettings = () => {
try {
const saved = localStorage.getItem(pipSettings.storageKey);
if (saved) Object.assign(pipSettings, JSON.parse(saved));
} catch (e) {
console.error("Error loading PiP settings:", e);
}
};
const saveSettings = () => {
try {
localStorage.setItem(pipSettings.storageKey, JSON.stringify(pipSettings));
} catch (e) {
console.error("Error saving PiP settings:", e);
}
};
// Toggle Picture-in-Picture
const togglePictureInPicture = (video) => {
if (!pipSettings.enabled || !video) return;
if (document.pictureInPictureElement) {
document.exitPictureInPicture()
.then(() => localStorage.removeItem("pipActive"))
.catch(console.error);
} else {
video.requestPictureInPicture()
.then(() => localStorage.setItem("pipActive", "true"))
.catch(console.error);
}
};
// Add PiP settings to Advanced section of modal
const addPipSettingsToModal = () => {
// Wait for the advanced section to be available
const advancedSection = document.querySelector('.ytp-plus-settings-section[data-section="advanced"]');
if (!advancedSection || document.querySelector('.pip-settings-item')) return;
// Add styles if they don't exist
if (!document.getElementById('pip-styles')) {
const style = document.createElement('style');
style.id = 'pip-styles';
style.textContent = `
.pip-shortcut-editor { display: flex; align-items: center; gap: 8px; }
.pip-shortcut-editor select, #pip-key {background: rgba(34, 34, 34, var(--yt-header-bg-opacity)); color: var(--yt-spec-text-primary); border: 1px solid var(--yt-spec-10-percent-layer); border-radius: var(--yt-radius-sm); padding: 4px;}
`;
document.head.appendChild(style);
}
// Enable/disable toggle
const enableItem = document.createElement('div');
enableItem.className = 'ytp-plus-settings-item pip-settings-item';
enableItem.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Picture-in-Picture</label>
<div class="ytp-plus-settings-item-description">Add Picture-in-Picture functionality with keyboard shortcut</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enablePiP" id="pip-enable-checkbox" ${pipSettings.enabled ? 'checked' : ''}>
`;
advancedSection.appendChild(enableItem);
// Shortcut settings
const shortcutItem = document.createElement('div');
shortcutItem.className = 'ytp-plus-settings-item pip-shortcut-item';
shortcutItem.style.display = pipSettings.enabled ? 'flex' : 'none';
const { ctrlKey, altKey, shiftKey } = pipSettings.shortcut;
const modifierValue = ctrlKey && altKey && shiftKey ? 'ctrl+alt+shift' :
ctrlKey && altKey ? 'ctrl+alt' :
ctrlKey && shiftKey ? 'ctrl+shift' :
altKey && shiftKey ? 'alt+shift' :
ctrlKey ? 'ctrl' : altKey ? 'alt' : shiftKey ? 'shift' : 'none';
shortcutItem.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">PiP Keyboard Shortcut</label>
<div class="ytp-plus-settings-item-description">Customize keyboard combination to toggle PiP mode</div>
</div>
<div class="pip-shortcut-editor">
<select id="pip-modifier-combo">
${['none', 'ctrl', 'alt', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift', 'ctrl+alt+shift']
.map(v => `<option value="${v}" ${v === modifierValue ? 'selected' : ''}>${v === 'none' ? 'None' : v.replace(/\+/g, '+').split('+').map(k => k.charAt(0).toUpperCase() + k.slice(1)).join('+')}</option>`)
.join('')}
</select>
<span>+</span>
<input type="text" id="pip-key" value="${pipSettings.shortcut.key}" maxlength="1" style="width: 30px; text-align: center;">
</div>
`;
advancedSection.appendChild(shortcutItem);
// Event listeners
document.getElementById('pip-enable-checkbox').addEventListener('change', (e) => {
pipSettings.enabled = e.target.checked;
shortcutItem.style.display = pipSettings.enabled ? 'flex' : 'none';
saveSettings();
});
document.getElementById('pip-modifier-combo').addEventListener('change', (e) => {
const value = e.target.value;
pipSettings.shortcut.ctrlKey = value.includes('ctrl');
pipSettings.shortcut.altKey = value.includes('alt');
pipSettings.shortcut.shiftKey = value.includes('shift');
saveSettings();
});
document.getElementById('pip-key').addEventListener('input', (e) => {
if (e.target.value) {
pipSettings.shortcut.key = e.target.value.toUpperCase();
saveSettings();
}
});
document.getElementById('pip-key').addEventListener('keydown', (e) => e.stopPropagation());
};
// Initialize
loadSettings();
// Event listeners
document.addEventListener("keydown", (e) => {
if (!pipSettings.enabled) return;
const { shiftKey, altKey, ctrlKey, key } = pipSettings.shortcut;
if (e.shiftKey === shiftKey && e.altKey === altKey &&
e.ctrlKey === ctrlKey && e.key.toUpperCase() === key) {
togglePictureInPicture(document.querySelector("video"));
e.preventDefault();
}
});
window.addEventListener("storage", (e) => {
if (e.key === "pipActive" && e.newValue === null && document.pictureInPictureElement) {
document.exitPictureInPicture().catch(console.error);
} else if (e.key === pipSettings.storageKey) {
loadSettings();
}
});
window.addEventListener("load", () => {
if (pipSettings.enabled && localStorage.getItem("pipActive") === "true" && !document.pictureInPictureElement) {
const video = document.querySelector("video");
if (video) togglePictureInPicture(video);
}
});
// DOM observers for the settings modal
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.classList?.contains('ytp-plus-settings-modal')) {
setTimeout(addPipSettingsToModal, 100);
}
}
}
// Check for section changes
if (document.querySelector('.ytp-plus-settings-nav-item[data-section="advanced"].active')) {
// If advanced section is active and our settings aren't there yet, add them
if (!document.querySelector('.pip-settings-item')) {
setTimeout(addPipSettingsToModal, 50);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Also observe for changes in the tab selection
document.addEventListener('click', (e) => {
if (e.target.classList && e.target.classList.contains('ytp-plus-settings-nav-item')) {
if (e.target.dataset.section === 'advanced') {
setTimeout(addPipSettingsToModal, 50);
}
}
}, true);
})();
// YouTube Timecode Panel
(function () {
"use strict";
// Early exit for embeds to prevent duplicate panels
if (window.location.hostname !== 'www.youtube.com' ||
window.frameElement ||
document.querySelector('ytd-app') === null) {
return;
}
// Prevent multiple initializations
if (window._timecodeModuleInitialized) return;
window._timecodeModuleInitialized = true;
// Configuration
const config = {
enabled: true,
autoDetect: true,
shortcut: { key: "T", shiftKey: true, altKey: false, ctrlKey: false },
storageKey: 'youtube_timecode_settings',
autoSave: true,
autoTrackPlayback: true,
export: true
};
// State management
const state = {
timecodes: new Map(),
dom: {},
activeIndex: null,
trackingId: 0,
dragging: false,
editingIndex: null
};
// Utilities
const loadSettings = () => {
try {
const saved = localStorage.getItem(config.storageKey);
if (saved) Object.assign(config, JSON.parse(saved));
} catch (e) { }
};
const saveSettings = () => {
try {
localStorage.setItem(config.storageKey, JSON.stringify(config));
} catch (e) { }
};
const showNotification = (message, duration = 2000) => {
let notification = document.querySelector('.youtube-enhancer-notification');
if (!notification) {
notification = document.createElement('div');
notification.className = 'youtube-enhancer-notification';
document.body.appendChild(notification);
}
notification.textContent = message;
notification.style.opacity = '1';
setTimeout(() => notification.style.opacity = '0', duration);
};
// Time utilities
const formatTime = seconds => {
if (isNaN(seconds)) return "00:00";
seconds = Math.round(seconds);
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return h > 0
? `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
: `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
};
const parseTime = timeStr => {
if (!timeStr) return null;
const str = timeStr.trim();
// Handle HH:MM:SS format
let match = str.match(/^(\d+):(\d{1,2}):(\d{2})$/);
if (match) {
const [, h, m, s] = match.map(Number);
return (m < 60 && s < 60) ? h * 3600 + m * 60 + s : null;
}
// Handle MM:SS format
match = str.match(/^(\d{1,2}):(\d{2})$/);
if (match) {
const [, m, s] = match.map(Number);
return (m < 60 && s < 60) ? m * 60 + s : null;
}
return null;
};
// Timecode extraction
const extractTimecodes = text => {
if (!text) return [];
const timecodes = [];
const seen = new Set();
const patterns = [
/(\d{1,2}:\d{2}(?::\d{2})?)\s*[-–—]\s*(.+?)$/gm,
/^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+?)$/gm,
/(\d{1,2}:\d{2}(?::\d{2})?)\s*[-–—:]\s*([^\n\r]{1,100}?)(?=\s*\d{1,2}:\d{2}|\s*$)/g
];
for (const pattern of patterns) {
let match;
while ((match = pattern.exec(text)) !== null) {
const time = parseTime(match[1]);
if (time !== null && !seen.has(time)) {
seen.add(time);
const label = (match[2] || formatTime(time)).trim().replace(/^\d+[\.\)]\s*/, '').substring(0, 100);
if (label) timecodes.push({ time, label, originalText: match[1] });
}
}
}
return timecodes.sort((a, b) => a.time - b.time);
};
// Detection
const detectTimecodes = () => {
if (!config.enabled || !config.autoDetect) return [];
const videoId = new URLSearchParams(window.location.search).get("v");
if (!videoId) return [];
const cacheKey = `detect_${videoId}`;
if (state.timecodes.has(cacheKey)) return state.timecodes.get(cacheKey);
const uniqueMap = new Map();
// Extract from description
const selectors = [
"#description-inline-expander yt-attributed-string",
"#tab-info ytd-expandable-video-description-body-renderer yt-attributed-string",
"#description.ytd-watch-metadata yt-attributed-string"
];
for (const selector of selectors) {
const el = document.querySelector(selector);
if (el?.textContent) {
extractTimecodes(el.textContent).forEach(tc => {
if (tc.time >= 0 && tc.label?.trim()) {
uniqueMap.set(tc.time.toString(), tc);
}
});
if (uniqueMap.size > 0) break;
}
}
// Get native chapters
getYouTubeChapters().forEach(chapter => {
if (chapter.time >= 0 && chapter.label?.trim()) {
uniqueMap.set(chapter.time.toString(), chapter);
}
});
const result = Array.from(uniqueMap.values()).sort((a, b) => a.time - b.time);
state.timecodes.set(cacheKey, result);
if (result.length > 0) {
updateTimecodePanel(result);
if (config.autoSave) saveTimecodesToStorage(result);
}
return result;
};
const getYouTubeChapters = () => {
const items = document.querySelectorAll('ytd-macro-markers-list-item-renderer, ytd-chapter-renderer');
const chapters = new Map();
items.forEach(item => {
const timeText = item.querySelector('.time-info, .timestamp')?.textContent;
const titleText = item.querySelector('.marker-title, .chapter-title')?.textContent;
if (timeText) {
const time = parseTime(timeText.trim());
if (time !== null) {
chapters.set(time.toString(), {
time,
label: titleText?.trim() || formatTime(time),
isChapter: true
});
}
}
});
return Array.from(chapters.values()).sort((a, b) => a.time - b.time);
};
// Settings panel
const addTimecodePanelSettings = () => {
const advancedSection = document.querySelector('.ytp-plus-settings-section[data-section="advanced"]');
if (!advancedSection || document.querySelector('.timecode-settings-item')) return;
const { ctrlKey, altKey, shiftKey } = config.shortcut;
const modifierValue = [
ctrlKey && altKey && shiftKey && 'ctrl+alt+shift',
ctrlKey && altKey && 'ctrl+alt',
ctrlKey && shiftKey && 'ctrl+shift',
altKey && shiftKey && 'alt+shift',
ctrlKey && 'ctrl',
altKey && 'alt',
shiftKey && 'shift'
].find(Boolean) || 'none';
const enableDiv = document.createElement('div');
enableDiv.className = 'ytp-plus-settings-item timecode-settings-item';
enableDiv.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Timecode Panel</label>
<div class="ytp-plus-settings-item-description">Enable video timecode/chapter panel with quick navigation</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" data-setting="enabled" ${config.enabled ? 'checked' : ''}>
`;
const shortcutDiv = document.createElement('div');
shortcutDiv.className = 'ytp-plus-settings-item timecode-settings-item timecode-shortcut-item';
shortcutDiv.style.display = config.enabled ? 'flex' : 'none';
shortcutDiv.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Keyboard Shortcut</label>
<div class="ytp-plus-settings-item-description">Customize keyboard combination to toggle Timecode Panel</div>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<select id="timecode-modifier-combo" style="background: rgba(34, 34, 34, 0.6); color: white; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 4px;">
${['none', 'ctrl', 'alt', 'shift', 'ctrl+alt', 'ctrl+shift', 'alt+shift', 'ctrl+alt+shift']
.map(v => `<option value="${v}" ${v === modifierValue ? 'selected' : ''}>${v === 'none' ? 'None' : v.split('+').map(k => k.charAt(0).toUpperCase() + k.slice(1)).join('+')}</option>`)
.join('')}
</select>
<span>+</span>
<input type="text" id="timecode-key" value="${config.shortcut.key}" maxlength="1" style="width: 30px; text-align: center; background: rgba(34, 34, 34, 0.6); color: white; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 4px;">
</div>
`;
advancedSection.append(enableDiv, shortcutDiv);
// Event listeners
advancedSection.addEventListener('change', e => {
if (e.target.matches('.ytp-plus-settings-checkbox[data-setting="enabled"]')) {
config.enabled = e.target.checked;
shortcutDiv.style.display = config.enabled ? 'flex' : 'none';
toggleTimecodePanel(config.enabled);
saveSettings();
}
});
document.getElementById('timecode-modifier-combo')?.addEventListener('change', e => {
const value = e.target.value;
config.shortcut.ctrlKey = value.includes('ctrl');
config.shortcut.altKey = value.includes('alt');
config.shortcut.shiftKey = value.includes('shift');
saveSettings();
});
document.getElementById('timecode-key')?.addEventListener('input', e => {
if (e.target.value) {
config.shortcut.key = e.target.value.toUpperCase();
saveSettings();
}
});
};
// CSS
const insertTimecodeStyles = () => {
if (document.getElementById('timecode-panel-styles')) return;
const style = document.createElement('style');
style.id = 'timecode-panel-styles';
style.textContent = `
#timecode-panel{position:fixed;right:20px;top:80px;background:rgba(34,34,34,.9);border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,.4);width:250px;max-height:70vh;z-index:9999;color:#fff;backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1);transition:transform .3s,opacity .3s;overflow:hidden;display:flex;flex-direction:column}
#timecode-panel.hidden{transform:translateX(270px);opacity:0;pointer-events:none}
#timecode-panel.auto-tracking{border-color:rgba(255,0,0,.5)}
#timecode-header{display:flex;justify-content:space-between;align-items:center;padding:12px;border-bottom:1px solid rgba(255,255,255,.1);background:rgba(0,0,0,.3);cursor:move}
#timecode-title{font-weight:500;margin:0;font-size:14px;user-select:none;display:flex;align-items:center;gap:8px}
#timecode-tracking-indicator{width:8px;height:8px;background:red;border-radius:50%;opacity:0;transition:opacity .3s}
#timecode-panel.auto-tracking #timecode-tracking-indicator{opacity:1}
#timecode-current-time{font-family:monospace;font-size:12px;padding:2px 6px;background:rgba(255,0,0,.3);border-radius:3px;margin-left:auto}
#timecode-close{background:0 0;border:none;color:rgba(255,255,255,.7);cursor:pointer;width:24px;height:24px;padding:0;transition:color .2s;display:flex;align-items:center;justify-content:center}
#timecode-close:hover{color:#fff}
#timecode-list{overflow-y:auto;padding:8px 0;max-height:calc(70vh - 80px);scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.3) transparent}
#timecode-list::-webkit-scrollbar{width:6px}
#timecode-list::-webkit-scrollbar-thumb{background:rgba(255,255,255,.3);border-radius:3px}
.timecode-item{padding:8px 12px;display:flex;align-items:center;cursor:pointer;transition:background-color .2s;border-left:3px solid transparent;position:relative}
.timecode-item:hover{background:rgba(255,255,255,.1)}
.timecode-item:hover .timecode-actions{opacity:1}
.timecode-item.active{background:rgba(255,0,0,.25);border-left-color:red}
.timecode-item.active.pulse{animation:pulse .8s ease-out}
.timecode-item.editing{background:rgba(255,255,0,.15);border-left-color:#ffaa00}
.timecode-item.editing .timecode-actions{opacity:1}
@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.02)}100%{transform:scale(1)}}
.timecode-time{font-family:monospace;margin-right:10px;color:rgba(255,255,255,.8);font-size:13px;min-width:45px}
.timecode-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:13px;flex:1}
.timecode-item.has-chapter .timecode-time{color:#ff4444}
.timecode-progress{width:0;height:2px;background:#ff4444;position:absolute;bottom:0;left:0;transition:width .3s;opacity:.8}
.timecode-actions{position:absolute;right:8px;top:50%;transform:translateY(-50%);display:flex;gap:4px;opacity:0;transition:opacity .2s;background:rgba(0,0,0,.8);border-radius:4px;padding:2px}
.timecode-action{background:none;border:none;color:rgba(255,255,255,.8);cursor:pointer;padding:4px;font-size:12px;border-radius:2px;transition:color .2s,background-color .2s}
.timecode-action:hover{color:#fff;background:rgba(255,255,255,.2)}
.timecode-action.edit:hover{color:#ffaa00}
.timecode-action.delete:hover{color:#ff4444}
#timecode-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center;color:rgba(255,255,255,.7);font-size:13px}
#timecode-form{padding:10px;border-top:1px solid rgba(255,255,255,.1);display:none}
#timecode-form.visible{display:block}
#timecode-form input{width:100%;margin-bottom:8px;padding:8px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.2);border-radius:4px;color:#fff;font-size:13px}
#timecode-form input::placeholder{color:rgba(255,255,255,.6)}
#timecode-form-buttons{display:flex;gap:8px;justify-content:flex-end}
#timecode-form-buttons button{padding:6px 12px;border:none;border-radius:4px;cursor:pointer;font-size:12px;transition:background-color .2s}
#timecode-form-cancel{background:rgba(255,255,255,.2);color:#fff}
#timecode-form-cancel:hover{background:rgba(255,255,255,.3)}
#timecode-form-save{background:#ff4444;color:#fff}
#timecode-form-save:hover{background:#ff6666}
#timecode-actions{padding:8px;border-top:1px solid rgba(255,255,255,.1);display:flex;gap:8px;background:rgba(0,0,0,.2)}
#timecode-actions button{padding:6px 10px;border:none;border-radius:4px;cursor:pointer;font-size:12px;transition:background-color .2s;background:rgba(255,255,255,.2);color:#fff}
#timecode-actions button:hover{background:rgba(255,255,255,.3)}
#timecode-track-toggle.active{background:#ff4444!important}
`;
document.head.appendChild(style);
};
// Panel creation
const createTimecodePanel = () => {
if (state.dom.panel) return state.dom.panel;
// Remove any existing panels (for redundancy)
document.querySelectorAll('#timecode-panel').forEach(p => p.remove());
const panel = document.createElement('div');
panel.id = 'timecode-panel';
panel.className = config.enabled ? '' : 'hidden';
if (config.autoTrackPlayback) panel.classList.add('auto-tracking');
panel.innerHTML = `
<div id="timecode-header">
<h3 id="timecode-title">
<div id="timecode-tracking-indicator"></div>
Timecodes
<span id="timecode-current-time"></span>
</h3>
<button id="timecode-close">×</button>
</div>
<div id="timecode-list"></div>
<div id="timecode-empty">
<div>No timecodes found</div>
<div style="margin-top:5px;font-size:12px">Click + to add current time</div>
</div>
<div id="timecode-form">
<input type="text" id="timecode-form-time" placeholder="Time (e.g., 1:30)">
<input type="text" id="timecode-form-label" placeholder="Label (optional)">
<div id="timecode-form-buttons">
<button type="button" id="timecode-form-cancel">Cancel</button>
<button type="button" id="timecode-form-save" class="save">Save</button>
</div>
</div>
<div id="timecode-actions">
<button id="timecode-add-btn">+ Add</button>
<button id="timecode-export-btn" ${config.export ? '' : 'style="display:none"'}>Export</button>
<button id="timecode-track-toggle" class="${config.autoTrackPlayback ? 'active' : ''}">${config.autoTrackPlayback ? 'Tracking' : 'Track'}</button>
</div>
`;
// Cache DOM elements
state.dom = {
panel,
list: panel.querySelector('#timecode-list'),
empty: panel.querySelector('#timecode-empty'),
form: panel.querySelector('#timecode-form'),
timeInput: panel.querySelector('#timecode-form-time'),
labelInput: panel.querySelector('#timecode-form-label'),
currentTime: panel.querySelector('#timecode-current-time'),
trackToggle: panel.querySelector('#timecode-track-toggle')
};
// Event delegation
panel.addEventListener('click', handlePanelClick);
makeDraggable(panel);
document.body.appendChild(panel);
return panel;
};
// Event handling
const handlePanelClick = e => {
const { target } = e;
const item = target.closest('.timecode-item');
if (target.id === 'timecode-close') {
toggleTimecodePanel(false);
} else if (target.id === 'timecode-add-btn') {
const video = document.querySelector('video');
if (video) showTimecodeForm(video.currentTime);
} else if (target.id === 'timecode-track-toggle') {
config.autoTrackPlayback = !config.autoTrackPlayback;
target.textContent = config.autoTrackPlayback ? 'Tracking' : 'Track';
target.classList.toggle('active', config.autoTrackPlayback);
state.dom.panel.classList.toggle('auto-tracking', config.autoTrackPlayback);
saveSettings();
if (config.autoTrackPlayback) startTracking();
} else if (target.id === 'timecode-export-btn') {
exportTimecodes();
} else if (target.id === 'timecode-form-cancel') {
hideTimecodeForm();
} else if (target.id === 'timecode-form-save') {
saveTimecodeForm();
} else if (target.classList.contains('timecode-action')) {
e.stopPropagation();
const action = target.dataset.action;
const index = parseInt(target.closest('.timecode-item').dataset.index);
if (action === 'edit') {
editTimecode(index);
} else if (action === 'delete') {
deleteTimecode(index);
}
} else if (item && !target.closest('.timecode-actions')) {
const time = parseFloat(item.dataset.time);
const video = document.querySelector('video');
if (video && !isNaN(time)) {
video.currentTime = time;
if (video.paused) video.play();
updateActiveItem(item);
}
}
};
// Edit timecode
const editTimecode = (index) => {
const timecodes = getCurrentTimecodes();
if (index < 0 || index >= timecodes.length) return;
const timecode = timecodes[index];
state.editingIndex = index;
// Update item appearance
const item = state.dom.list.querySelector(`.timecode-item[data-index="${index}"]`);
if (item) {
item.classList.add('editing');
// Hide other editing items
state.dom.list.querySelectorAll('.timecode-item.editing').forEach(el => {
if (el !== item) el.classList.remove('editing');
});
}
showTimecodeForm(timecode.time, timecode.label);
};
// Delete timecode
const deleteTimecode = (index) => {
const timecodes = getCurrentTimecodes();
if (index < 0 || index >= timecodes.length) return;
const timecode = timecodes[index];
// Don't allow deletion of native YouTube chapters
if (timecode.isChapter && !timecode.isUserAdded) {
showNotification("Cannot delete YouTube chapters");
return;
}
// Confirm deletion
if (!confirm(`Delete timecode "${timecode.label}"?`)) return;
timecodes.splice(index, 1);
updateTimecodePanel(timecodes);
saveTimecodesToStorage(timecodes);
showNotification("Timecode deleted");
};
// Form handling
const showTimecodeForm = (currentTime, existingLabel = '') => {
const { form, timeInput, labelInput } = state.dom;
form.classList.add('visible');
timeInput.value = formatTime(currentTime);
labelInput.value = existingLabel;
requestAnimationFrame(() => labelInput.focus());
};
const hideTimecodeForm = () => {
state.dom.form.classList.remove('visible');
state.editingIndex = null;
// Remove editing class from all items
state.dom.list?.querySelectorAll('.timecode-item.editing').forEach(el => {
el.classList.remove('editing');
});
};
const saveTimecodeForm = () => {
const { timeInput, labelInput } = state.dom;
const timeValue = timeInput.value.trim();
const labelValue = labelInput.value.trim();
const time = parseTime(timeValue);
if (time === null) {
showNotification("Invalid time format");
return;
}
const timecodes = getCurrentTimecodes();
const newTimecode = { time, label: labelValue || formatTime(time), isUserAdded: true };
if (state.editingIndex !== null) {
// Editing existing timecode
const oldTimecode = timecodes[state.editingIndex];
if (oldTimecode.isChapter && !oldTimecode.isUserAdded) {
showNotification("Cannot edit YouTube chapters");
hideTimecodeForm();
return;
}
timecodes[state.editingIndex] = { ...oldTimecode, ...newTimecode };
showNotification("Timecode updated");
} else {
// Adding new timecode
timecodes.push(newTimecode);
showNotification("Timecode added");
}
const sorted = timecodes.sort((a, b) => a.time - b.time);
updateTimecodePanel(sorted);
saveTimecodesToStorage(sorted);
hideTimecodeForm();
};
// Export
const exportTimecodes = () => {
const timecodes = getCurrentTimecodes();
if (!timecodes.length) {
showNotification("No timecodes to export");
return;
}
const exportBtn = state.dom.panel?.querySelector('#timecode-export-btn');
if (exportBtn) {
exportBtn.textContent = "Copied!";
exportBtn.style.backgroundColor = 'rgba(0,220,0,0.8)';
setTimeout(() => {
exportBtn.textContent = "Export";
exportBtn.style.backgroundColor = '';
}, 2000);
}
const videoTitle = document.title.replace(/\s-\sYouTube$/, "");
let content = `${videoTitle}\n\nTimecodes:\n`;
timecodes.forEach(tc => content += `${formatTime(tc.time)} - ${tc.label}\n`);
if (navigator.clipboard?.writeText) {
navigator.clipboard.writeText(content).then(() => {
showNotification("Timecodes copied to clipboard");
});
}
};
// Panel updates
const updateTimecodePanel = timecodes => {
const { list, empty } = state.dom;
if (!list || !empty) return;
const isEmpty = !timecodes.length;
empty.style.display = isEmpty ? 'flex' : 'none';
list.style.display = isEmpty ? 'none' : 'block';
if (isEmpty) return;
list.innerHTML = timecodes.map((tc, i) => {
const timeStr = formatTime(tc.time);
const label = (tc.label?.trim() || timeStr).replace(/[<>&"']/g, c => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' })[c]);
const isEditable = !tc.isChapter || tc.isUserAdded;
return `
<div class="timecode-item ${tc.isChapter ? 'has-chapter' : ''}" data-time="${tc.time}" data-index="${i}">
<div class="timecode-time">${timeStr}</div>
<div class="timecode-label" title="${label}">${label}</div>
<div class="timecode-progress"></div>
${isEditable ? `
<div class="timecode-actions">
<button class="timecode-action edit" data-action="edit" title="Edit">✎</button>
<button class="timecode-action delete" data-action="delete" title="Delete">✕</button>
</div>
` : ''}
</div>
`;
}).join('');
};
const updateActiveItem = activeItem => {
const items = state.dom.list?.querySelectorAll('.timecode-item');
if (!items) return;
items.forEach(item => item.classList.remove('active', 'pulse'));
if (activeItem) {
activeItem.classList.add('active', 'pulse');
setTimeout(() => activeItem.classList.remove('pulse'), 800);
}
};
// Tracking
const startTracking = () => {
if (state.trackingId) return;
const track = () => {
try {
const video = document.querySelector('video');
const { panel, currentTime, list } = state.dom;
// Stop tracking if essential elements are missing or panel is hidden
if (!video || !panel || panel.classList.contains('hidden') || !config.autoTrackPlayback) {
if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
return;
}
// Update current time display
if (currentTime && !isNaN(video.currentTime)) {
currentTime.textContent = formatTime(video.currentTime);
}
// Update active item
const items = list?.querySelectorAll('.timecode-item');
if (items?.length) {
let activeIndex = -1;
let nextIndex = -1;
for (let i = 0; i < items.length; i++) {
const timeData = items[i].dataset.time;
if (!timeData) continue;
const time = parseFloat(timeData);
if (isNaN(time)) continue;
if (video.currentTime >= time) {
activeIndex = i;
} else if (nextIndex === -1) {
nextIndex = i;
}
}
// Update active state
if (state.activeIndex !== activeIndex) {
// Remove previous active state
if (state.activeIndex !== null && state.activeIndex >= 0 && items[state.activeIndex]) {
items[state.activeIndex].classList.remove('active');
}
// Set new active state
if (activeIndex >= 0 && items[activeIndex]) {
items[activeIndex].classList.add('active');
try {
items[activeIndex].scrollIntoView({ behavior: 'smooth', block: 'center' });
} catch (e) {
// Fallback for browsers that don't support smooth scrolling
items[activeIndex].scrollIntoView(false);
}
}
state.activeIndex = activeIndex;
}
// Update progress bar
if (activeIndex >= 0 && nextIndex >= 0 && items[activeIndex]) {
const currentTimeData = items[activeIndex].dataset.time;
const nextTimeData = items[nextIndex].dataset.time;
if (currentTimeData && nextTimeData) {
const current = parseFloat(currentTimeData);
const next = parseFloat(nextTimeData);
if (!isNaN(current) && !isNaN(next) && next > current) {
const progress = ((video.currentTime - current) / (next - current)) * 100;
const progressEl = items[activeIndex].querySelector('.timecode-progress');
if (progressEl) {
const clampedProgress = Math.min(100, Math.max(0, progress));
progressEl.style.width = `${clampedProgress}%`;
}
}
}
}
}
// Continue tracking if enabled
if (config.autoTrackPlayback) {
state.trackingId = requestAnimationFrame(track);
}
} catch (error) {
console.warn('Timecode tracking error:', error);
// Stop tracking on error to prevent infinite error loops
if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
}
};
state.trackingId = requestAnimationFrame(track);
};
// Stop tracking function
const stopTracking = () => {
if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
};
// Drag functionality
const makeDraggable = panel => {
const header = panel.querySelector('#timecode-header');
if (!header) return;
let startX, startY, startLeft, startTop;
header.addEventListener('mousedown', e => {
if (e.button !== 0) return;
state.dragging = true;
startX = e.clientX;
startY = e.clientY;
// Set initial position if not already set
if (panel.style.left === '' && panel.style.top === '') {
panel.style.left = '20px';
panel.style.top = '80px';
}
startLeft = parseInt(panel.style.left) || 0;
startTop = parseInt(panel.style.top) || 0;
const move = e => {
if (!state.dragging) return;
const x = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, startLeft + e.clientX - startX));
const y = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, startTop + e.clientY - startY));
panel.style.left = x + 'px';
panel.style.top = y + 'px';
};
const stop = () => {
state.dragging = false;
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', stop);
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', stop);
});
};
// Storage
const saveTimecodesToStorage = timecodes => {
const videoId = new URLSearchParams(window.location.search).get("v");
if (!videoId) return;
try {
const minimal = timecodes.map(tc => ({
t: tc.time,
l: tc.label?.trim() || formatTime(tc.time),
c: tc.isChapter || false,
u: tc.isUserAdded || false
}));
localStorage.setItem(`yt_tc_${videoId}`, JSON.stringify(minimal));
} catch (e) { }
};
const loadTimecodesFromStorage = () => {
const videoId = new URLSearchParams(window.location.search).get("v");
if (!videoId) return null;
try {
const data = localStorage.getItem(`yt_tc_${videoId}`);
return data ? JSON.parse(data).map(tc => ({
time: tc.t,
label: tc.l,
isChapter: tc.c,
isUserAdded: tc.u || false
})).sort((a, b) => a.time - b.time) : null;
} catch (e) {
return null;
}
};
const getCurrentTimecodes = () => {
const items = state.dom.list?.querySelectorAll('.timecode-item');
if (!items) return [];
return Array.from(items).map(item => ({
time: parseFloat(item.dataset.time),
label: item.querySelector('.timecode-label')?.textContent || formatTime(parseFloat(item.dataset.time)),
isChapter: item.classList.contains('has-chapter'),
isUserAdded: !item.classList.contains('has-chapter') || false
})).sort((a, b) => a.time - b.time);
};
// Toggle panel
const toggleTimecodePanel = show => {
// Close any existing panels first (cleanup)
document.querySelectorAll('#timecode-panel').forEach(panel => {
if (panel !== state.dom.panel) panel.remove();
});
const panel = state.dom.panel || createTimecodePanel();
if (show === undefined) show = panel.classList.contains('hidden');
panel.classList.toggle('hidden', !show);
if (show) {
const saved = loadTimecodesFromStorage();
if (saved?.length) {
updateTimecodePanel(saved);
} else if (config.autoDetect) {
detectTimecodes();
}
if (config.autoTrackPlayback) startTracking();
} else if (state.trackingId) {
cancelAnimationFrame(state.trackingId);
state.trackingId = 0;
}
};
// Navigation handling
const setupNavigation = () => {
let currentVideoId = new URLSearchParams(window.location.search).get("v");
const handleNavigationChange = () => {
const newVideoId = new URLSearchParams(window.location.search).get("v");
if (newVideoId === currentVideoId || window.location.pathname !== '/watch') return;
currentVideoId = newVideoId;
state.activeIndex = null;
state.editingIndex = null;
state.timecodes.clear();
if (config.enabled && state.dom.panel && !state.dom.panel.classList.contains('hidden')) {
const saved = loadTimecodesFromStorage();
if (saved?.length) {
updateTimecodePanel(saved);
} else if (config.autoDetect) {
setTimeout(detectTimecodes, 500);
}
if (config.autoTrackPlayback) startTracking();
}
};
document.addEventListener('yt-navigate-finish', handleNavigationChange);
// Also watch for URL changes using MutationObserver as a fallback
const observer = new MutationObserver(() => {
const newVideoId = new URLSearchParams(window.location.search).get("v");
if (newVideoId !== currentVideoId) {
handleNavigationChange();
}
});
observer.observe(document.body, { subtree: true, childList: true });
};
// Keyboard shortcuts
const setupKeyboard = () => {
document.addEventListener('keydown', e => {
if (e.target.matches('input, textarea, [contenteditable]')) return;
const { key, shiftKey, altKey, ctrlKey } = config.shortcut;
if (e.key.toUpperCase() === key && e.shiftKey === shiftKey &&
e.altKey === altKey && e.ctrlKey === ctrlKey) {
e.preventDefault();
toggleTimecodePanel();
}
});
};
// Cleanup on unload
const cleanup = () => {
stopTracking();
if (state.dom.panel) {
state.dom.panel.remove();
state.dom.panel = null;
}
};
// Initialize
const init = () => {
loadSettings();
insertTimecodeStyles();
setupKeyboard();
setupNavigation();
// Settings modal observer
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.classList?.contains('ytp-plus-settings-modal')) {
setTimeout(addTimecodePanelSettings, 100);
return;
}
}
}
if (document.querySelector('.ytp-plus-settings-section[data-section="advanced"]:not(.hidden)') &&
!document.querySelector('.timecode-settings-item')) {
setTimeout(addTimecodePanelSettings, 50);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
document.addEventListener('click', e => {
if (e.target.classList?.contains('ytp-plus-settings-nav-item') &&
e.target.dataset.section === 'advanced') {
setTimeout(addTimecodePanelSettings, 50);
}
}, true);
if (config.enabled) {
createTimecodePanel();
const saved = loadTimecodesFromStorage();
if (saved?.length) {
updateTimecodePanel(saved);
} else if (config.autoDetect) {
setTimeout(detectTimecodes, 1000);
}
if (config.autoTrackPlayback) startTracking();
}
};
// Start on document ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Cleanup on beforeunload
window.addEventListener('beforeunload', cleanup);
})();
// Ad Blocker
(function () {
'use strict';
const AdBlocker = {
config: {
skipInterval: 500,
removeInterval: 1500,
enableLogging: false,
maxRetries: 2,
enabled: true,
storageKey: 'youtube_adblocker_settings'
},
state: {
isYouTubeShorts: false,
isYouTubeMusic: location.hostname === 'music.youtube.com',
lastSkipAttempt: 0,
retryCount: 0,
initialized: false
},
// Cached DOM queries for better performance
cache: {
moviePlayer: null,
ytdPlayer: null,
lastCacheTime: 0,
cacheTimeout: 5000
},
// Optimized selectors
selectors: {
ads: '#player-ads,.ytp-ad-module,.ad-showing,.ytp-ad-timed-pie-countdown-container,.ytp-ad-survey-questions',
elements: '#masthead-ad,ytd-merch-shelf-renderer,.yt-mealbar-promo-renderer,ytmusic-mealbar-promo-renderer,ytmusic-statement-banner-renderer,.ytp-featured-product',
video: 'video.html5-main-video',
removal: 'ytd-reel-video-renderer .ytd-ad-slot-renderer'
},
// Settings with localStorage caching
settings: {
load() {
try {
const saved = localStorage.getItem(AdBlocker.config.storageKey);
if (saved) {
const parsed = JSON.parse(saved);
AdBlocker.config.enabled = parsed.enabled ?? true;
AdBlocker.config.enableLogging = parsed.enableLogging ?? false;
}
} catch (e) { }
},
save() {
try {
localStorage.setItem(AdBlocker.config.storageKey, JSON.stringify({
enabled: AdBlocker.config.enabled,
enableLogging: AdBlocker.config.enableLogging
}));
} catch (e) { }
}
},
// Cached player access
getPlayer() {
const now = Date.now();
if (now - AdBlocker.cache.lastCacheTime > AdBlocker.cache.cacheTimeout) {
AdBlocker.cache.moviePlayer = document.querySelector('#movie_player');
AdBlocker.cache.ytdPlayer = document.querySelector('#ytd-player');
AdBlocker.cache.lastCacheTime = now;
}
const playerEl = AdBlocker.cache.ytdPlayer;
return {
element: AdBlocker.cache.moviePlayer,
player: playerEl?.getPlayer?.() || playerEl
};
},
// Streamlined ad detection and skipping
skipAd() {
if (!AdBlocker.config.enabled) return;
const now = Date.now();
if (now - AdBlocker.state.lastSkipAttempt < 300) return;
AdBlocker.state.lastSkipAttempt = now;
if (location.pathname.startsWith('/shorts/')) return;
// Fast ad detection
const adElement = document.querySelector('.ad-showing, .ytp-ad-timed-pie-countdown-container');
if (!adElement) {
AdBlocker.state.retryCount = 0;
return;
}
try {
const { element: moviePlayer, player } = AdBlocker.getPlayer();
if (!player) return;
const video = document.querySelector(AdBlocker.selectors.video);
// Mute ad immediately
if (video) video.muted = true;
// Skip logic based on platform
if (AdBlocker.state.isYouTubeMusic && video) {
video.currentTime = video.duration || 999;
} else if (typeof player.getVideoData === 'function') {
const videoData = player.getVideoData();
if (videoData?.video_id) {
const currentTime = Math.floor(player.getCurrentTime?.() || 0);
// Use most efficient skip method
if (typeof player.loadVideoById === 'function') {
player.loadVideoById(videoData.video_id, currentTime);
}
}
}
AdBlocker.state.retryCount = 0;
} catch (error) {
if (AdBlocker.state.retryCount < AdBlocker.config.maxRetries) {
AdBlocker.state.retryCount++;
setTimeout(AdBlocker.skipAd, 800);
}
}
},
// Minimal CSS injection
addCss() {
if (document.querySelector('#yt-ab-styles') || !AdBlocker.config.enabled) return;
const style = document.createElement('style');
style.id = 'yt-ab-styles';
style.textContent = `${AdBlocker.selectors.ads},${AdBlocker.selectors.elements}{display:none!important;}`;
(document.head || document.documentElement).appendChild(style);
},
removeCss() {
document.querySelector('#yt-ab-styles')?.remove();
},
// Batched element removal
removeElements() {
if (!AdBlocker.config.enabled || AdBlocker.state.isYouTubeMusic) return;
// Use requestIdleCallback for non-blocking removal
const remove = () => {
const elements = document.querySelectorAll(AdBlocker.selectors.removal);
elements.forEach(el => el.closest('ytd-reel-video-renderer')?.remove());
};
if (window.requestIdleCallback) {
requestIdleCallback(remove, { timeout: 100 });
} else {
setTimeout(remove, 0);
}
},
// Optimized settings UI
addSettingsUI() {
const section = document.querySelector('.ytp-plus-settings-section[data-section="basic"]');
if (!section || section.querySelector('.ab-settings')) return;
const item = document.createElement('div');
item.className = 'ytp-plus-settings-item ab-settings';
item.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Ad Blocker</label>
<div class="ytp-plus-settings-item-description">Skip ads and remove ad elements automatically</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" ${AdBlocker.config.enabled ? 'checked' : ''}>
`;
section.appendChild(item);
item.querySelector('input').addEventListener('change', e => {
AdBlocker.config.enabled = e.target.checked;
AdBlocker.settings.save();
AdBlocker.config.enabled ? AdBlocker.addCss() : AdBlocker.removeCss();
});
},
// Streamlined initialization
init() {
if (AdBlocker.state.initialized) return;
AdBlocker.state.initialized = true;
AdBlocker.settings.load();
if (AdBlocker.config.enabled) {
AdBlocker.addCss();
AdBlocker.removeElements();
}
// Start optimized intervals
setInterval(AdBlocker.skipAd, AdBlocker.config.skipInterval);
setInterval(AdBlocker.removeElements, AdBlocker.config.removeInterval);
// Navigation handling
const handleNavigation = () => {
AdBlocker.state.isYouTubeShorts = location.pathname.startsWith('/shorts/');
AdBlocker.cache.lastCacheTime = 0; // Reset cache
};
// Override pushState for SPA navigation
const originalPushState = history.pushState;
history.pushState = function () {
const result = originalPushState.apply(this, arguments);
setTimeout(handleNavigation, 50);
return result;
};
// Settings modal integration
const settingsObserver = new MutationObserver(mutations => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.classList?.contains('ytp-plus-settings-modal')) {
setTimeout(AdBlocker.addSettingsUI, 50);
return;
}
}
}
});
settingsObserver.observe(document.body, { childList: true });
document.addEventListener('click', e => {
if (e.target.dataset?.section === 'basic') {
setTimeout(AdBlocker.addSettingsUI, 25);
}
}, { passive: true, capture: true });
// Initial skip attempt
if (AdBlocker.config.enabled) {
setTimeout(AdBlocker.skipAd, 200);
}
}
};
// Initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', AdBlocker.init, { once: true });
} else {
AdBlocker.init();
}
})();
// YouTube End Screen Remover
(function () {
'use strict';
// Optimized configuration
const CONFIG = {
enabled: true,
storageKey: 'youtube_endscreen_settings',
selectors: '.ytp-ce-element-show,.ytp-ce-element,.ytp-endscreen-element,.ytp-ce-covering-overlay,.ytp-cards-teaser,.ytp-cards-button,.iv-drawer,.video-annotations',
debounceMs: 32,
batchSize: 20
};
// Minimal state with better tracking
const state = {
observer: null,
styleEl: null,
isActive: false,
removeCount: 0,
lastCheck: 0
};
// High-performance utilities
const debounce = (fn, ms) => {
let id;
return (...args) => {
clearTimeout(id);
id = setTimeout(() => fn(...args), ms);
};
};
const fastRemove = (elements) => {
const len = Math.min(elements.length, CONFIG.batchSize);
for (let i = 0; i < len; i++) {
const el = elements[i];
if (el?.isConnected) {
el.style.cssText = 'display:none!important;visibility:hidden!important';
try { el.remove(); state.removeCount++; } catch (e) { }
}
}
};
// Settings with caching
const settings = {
load: () => {
try {
const data = localStorage.getItem(CONFIG.storageKey);
CONFIG.enabled = data ? JSON.parse(data).enabled ?? true : true;
} catch (e) { CONFIG.enabled = true; }
},
save: () => {
try {
localStorage.setItem(CONFIG.storageKey, JSON.stringify({ enabled: CONFIG.enabled }));
} catch (e) { }
settings.apply();
},
apply: () => CONFIG.enabled ? init() : cleanup()
};
// Optimized core functions
const injectCSS = () => {
if (state.styleEl || !CONFIG.enabled) return;
state.styleEl = document.createElement('style');
state.styleEl.textContent = `${CONFIG.selectors}{display:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important;transform:scale(0)!important}`;
(document.head || document.documentElement).appendChild(state.styleEl);
};
const removeEndScreens = () => {
if (!CONFIG.enabled) return;
const now = performance.now();
if (now - state.lastCheck < CONFIG.debounceMs) return;
state.lastCheck = now;
const elements = document.querySelectorAll(CONFIG.selectors);
if (elements.length) fastRemove(elements);
};
const setupWatcher = () => {
if (state.observer || !CONFIG.enabled) return;
const throttledRemove = debounce(removeEndScreens, CONFIG.debounceMs);
state.observer = new MutationObserver(mutations => {
let hasRelevantChanges = false;
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.nodeType === 1 && (node.className?.includes('ytp-') || node.querySelector?.('.ytp-ce-element'))) {
hasRelevantChanges = true;
break;
}
}
if (hasRelevantChanges) break;
}
if (hasRelevantChanges) throttledRemove();
});
const target = document.querySelector('#movie_player') || document.body;
state.observer.observe(target, {
childList: true,
subtree: true,
attributeFilter: ['class', 'style']
});
};
const cleanup = () => {
state.observer?.disconnect();
state.observer = null;
state.styleEl?.remove();
state.styleEl = null;
state.isActive = false;
};
const init = () => {
if (state.isActive || !CONFIG.enabled) return;
state.isActive = true;
injectCSS();
removeEndScreens();
setupWatcher();
};
// Streamlined settings UI
const addSettingsUI = () => {
const section = document.querySelector('.ytp-plus-settings-section[data-section="advanced"]');
if (!section || section.querySelector('.endscreen-settings')) return;
const container = document.createElement('div');
container.className = 'ytp-plus-settings-item endscreen-settings';
container.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Hide End Screens & Cards</label>
<div class="ytp-plus-settings-item-description">Remove end screen suggestions and info cards${state.removeCount ? ` (${state.removeCount} removed)` : ''}</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" ${CONFIG.enabled ? 'checked' : ''}>
`;
section.appendChild(container);
container.querySelector('input').addEventListener('change', e => {
CONFIG.enabled = e.target.checked;
settings.save();
}, { passive: true });
};
// Optimized navigation handler
const handlePageChange = debounce(() => {
if (location.pathname === '/watch') {
cleanup();
requestIdleCallback ? requestIdleCallback(init) : setTimeout(init, 1);
}
}, 50);
// Initialize
settings.load();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
init();
}
// Event listeners
document.addEventListener('yt-navigate-finish', handlePageChange, { passive: true });
// Settings modal integration
const settingsObserver = new MutationObserver(mutations => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.classList?.contains('ytp-plus-settings-modal')) {
setTimeout(addSettingsUI, 25);
return;
}
}
}
});
settingsObserver.observe(document.body, { childList: true });
document.addEventListener('click', e => {
if (e.target.dataset?.section === 'advanced') {
setTimeout(addSettingsUI, 10);
}
}, { passive: true, capture: true });
})();
// Commment Manager
(function () {
'use strict';
// Streamlined configuration
const CONFIG = {
selectors: {
deleteButtons: 'div[class^="VfPpkd-Bz112c-"]',
menuButton: '[aria-haspopup="menu"]'
},
classes: {
checkbox: 'comment-checkbox',
container: 'comment-controls-container'
},
debounceDelay: 100,
deleteDelay: 200,
enabled: true,
storageKey: 'youtube_comment_manager_settings'
};
// State management
const state = {
observer: null,
isProcessing: false
};
// Optimized settings
const settings = {
load: () => {
try {
const saved = localStorage.getItem(CONFIG.storageKey);
if (saved) CONFIG.enabled = JSON.parse(saved).enabled ?? true;
} catch (e) { }
},
save: () => {
try {
localStorage.setItem(CONFIG.storageKey, JSON.stringify({ enabled: CONFIG.enabled }));
} catch (e) { }
}
};
// Utility functions
const debounce = (func, wait) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
};
const $ = selector => document.querySelector(selector);
const $$ = selector => document.querySelectorAll(selector);
// Core functionality
const addCheckboxes = () => {
if (!CONFIG.enabled || state.isProcessing) return;
const deleteButtons = $$(CONFIG.selectors.deleteButtons);
deleteButtons.forEach(button => {
if (button.closest(CONFIG.selectors.menuButton) ||
button.parentNode.querySelector(`.${CONFIG.classes.checkbox}`)) return;
const commentElement = button.closest('[class*="comment"]') ||
button.closest('[role="article"]') ||
button.parentNode;
if (commentElement && !commentElement.hasAttribute('data-comment-text')) {
commentElement.setAttribute('data-comment-text', commentElement.textContent.toLowerCase());
}
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = CONFIG.classes.checkbox;
checkbox.style.cssText = 'appearance:none;width:20px;height:20px;border:2px solid rgba(221,221,221,0.5);border-radius:6px;cursor:pointer;transition:all 0.2s ease;background:rgba(0,0,0,0.9);position:relative;margin-right:8px;';
checkbox.addEventListener('change', updateDeleteButtonState);
checkbox.addEventListener('click', e => e.stopPropagation());
// Optimized positioning
const dateElement = commentElement.querySelector('[class*="date"],[class*="time"],time,[title*="20"],[aria-label*="ago"]');
if (dateElement) {
dateElement.style.cssText += 'position:relative;';
checkbox.style.cssText += 'position:absolute;right:-30px;top:0px;';
dateElement.appendChild(checkbox);
} else {
button.parentNode.insertBefore(checkbox, button);
}
});
};
const addControlButtons = () => {
if (!CONFIG.enabled || $(`.${CONFIG.classes.container}`)) return;
const deleteButtons = $$(CONFIG.selectors.deleteButtons);
if (!deleteButtons.length) return;
const container = deleteButtons[0].parentNode.parentNode;
const buttonsContainer = document.createElement('div');
buttonsContainer.className = CONFIG.classes.container;
buttonsContainer.style.cssText = 'position:fixed;top:50%;right:20px;transform:translateY(-50%);display:flex;flex-direction:column;gap:12px;z-index:9999;';
const buttonStyles = 'padding:16px 24px;border:none;border-radius:16px;cursor:pointer;font-weight:600;font-size:14px;transition:all 0.3s ease;min-width:180px;text-align:center;backdrop-filter:blur(15px);-webkit-backdrop-filter:blur(15px);';
// Delete All Button
const deleteAllButton = document.createElement('button');
deleteAllButton.textContent = 'Delete Selected';
deleteAllButton.className = 'delete-all-button';
deleteAllButton.disabled = true;
deleteAllButton.style.cssText = buttonStyles + 'background:rgba(244,67,54,0.2);color:#f44336;border:1px solid rgba(244,67,54,0.3);';
deleteAllButton.addEventListener('click', deleteSelectedComments);
// Select All Button
const selectAllButton = document.createElement('button');
selectAllButton.textContent = 'Select All';
selectAllButton.style.cssText = buttonStyles + 'background:rgba(33,150,243,0.2);color:#2196f3;border:1px solid rgba(33,150,243,0.3);';
selectAllButton.addEventListener('click', () => {
$$(`.${CONFIG.classes.checkbox}`).forEach(cb => cb.checked = true);
updateDeleteButtonState();
});
// Clear All Button
const clearAllButton = document.createElement('button');
clearAllButton.textContent = 'Clear All';
clearAllButton.style.cssText = buttonStyles + 'background:rgba(76,175,80,0.2);color:#4caf50;border:1px solid rgba(76,175,80,0.3);';
clearAllButton.addEventListener('click', () => {
$$(`.${CONFIG.classes.checkbox}`).forEach(cb => cb.checked = false);
updateDeleteButtonState();
});
buttonsContainer.append(deleteAllButton, selectAllButton, clearAllButton);
container.insertBefore(buttonsContainer, deleteButtons[0].parentNode);
};
const updateDeleteButtonState = () => {
const deleteAllButton = $('.delete-all-button');
if (!deleteAllButton) return;
const hasChecked = Array.from($$(`.${CONFIG.classes.checkbox}`)).some(cb => cb.checked);
deleteAllButton.disabled = !hasChecked;
deleteAllButton.style.opacity = hasChecked ? '1' : '0.6';
};
const deleteSelectedComments = () => {
const checkedBoxes = Array.from($$(`.${CONFIG.classes.checkbox}`)).filter(cb => cb.checked);
if (!checkedBoxes.length || !confirm(`Delete ${checkedBoxes.length} comment(s)?`)) return;
state.isProcessing = true;
checkedBoxes.forEach((checkbox, index) => {
setTimeout(() => {
const deleteButton = checkbox.nextElementSibling ||
checkbox.parentNode.querySelector(CONFIG.selectors.deleteButtons);
deleteButton?.click();
}, index * CONFIG.deleteDelay);
});
setTimeout(() => state.isProcessing = false, checkedBoxes.length * CONFIG.deleteDelay + 1000);
};
const cleanup = () => {
$$(`.${CONFIG.classes.checkbox}`).forEach(el => el.remove());
$(`.${CONFIG.classes.container}`)?.remove();
};
const initializeScript = () => {
if (CONFIG.enabled) {
addCheckboxes();
addControlButtons();
updateDeleteButtonState();
} else {
cleanup();
}
};
// Enhanced styles with better performance
const addStyles = () => {
if ($('#comment-delete-styles')) return;
const style = document.createElement('style');
style.id = 'comment-delete-styles';
style.textContent = `
.${CONFIG.classes.checkbox}:hover{border-color:rgba(33,150,243,0.7);background:rgba(245,245,245,0.3);transform:scale(1.1)}
.${CONFIG.classes.checkbox}:checked{background:rgba(33,150,243,0.3);border-color:rgba(33,150,243,0.7);box-shadow:0 4px 12px rgba(33,150,243,0.2)}
.${CONFIG.classes.checkbox}:checked::after{content:'✓';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#2196f3;font-size:14px;font-weight:bold}
.delete-all-button:hover:not(:disabled){background:rgba(244,67,54,0.3)!important;transform:translateY(-3px);box-shadow:0 8px 24px rgba(244,67,54,0.2)}
`;
document.head.appendChild(style);
};
// Settings integration
const addCommentManagerSettings = () => {
const advancedSection = $('.ytp-plus-settings-section[data-section="advanced"]');
if (!advancedSection || $('.comment-manager-settings-item')) return;
const settingsItem = document.createElement('div');
settingsItem.className = 'ytp-plus-settings-item comment-manager-settings-item';
settingsItem.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Comment Manager</label>
<div class="ytp-plus-settings-item-description">Add bulk delete functionality for managing comments on YouTube</div>
</div>
<button class="ytp-plus-button" id="open-comment-history-page" style="margin:0 0 0 30px;padding:12px 16px;font-size:13px;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
<polyline points="15,3 21,3 21,9"/>
<line x1="10" y1="14" x2="21" y2="3"/>
</svg>
</button>
`;
advancedSection.appendChild(settingsItem);
$('#open-comment-history-page').addEventListener('click', () => {
window.open('https://www.youtube.com/feed/history/comment_history', '_blank');
});
};
// Optimized initialization
const init = () => {
settings.load();
addStyles();
// Setup observer with throttling
state.observer?.disconnect();
state.observer = new MutationObserver(debounce(initializeScript, CONFIG.debounceDelay));
state.observer.observe(document.body, { childList: true, subtree: true });
// Initial setup
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeScript);
} else {
initializeScript();
}
// Settings modal integration
const settingsObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.classList?.contains('ytp-plus-settings-modal')) {
setTimeout(addCommentManagerSettings, 100);
return;
}
}
}
});
settingsObserver.observe(document.body, { childList: true, subtree: true });
document.addEventListener('click', e => {
if (e.target.dataset?.section === 'advanced') {
setTimeout(addCommentManagerSettings, 50);
}
}, { passive: true, capture: true });
};
init();
})();
// Playlist Enhancements (Liked & Watch Later)
(function () {
'use strict';
// --- Configuration ---
const CONFIG = {
enabled: true,
selectors: {
videoRenderer: 'ytd-playlist-video-renderer',
videoTitle: '#video-title',
playlistContainer: '#contents.ytd-playlist-video-list-renderer',
searchInsertTarget: 'ytd-feed-filter-chip-bar-renderer #chips',
searchContainer: '.playlist-search-container',
removeMenuButton: '#menu button',
removeMenuItem: 'ytd-menu-service-item-renderer',
removeMenuItemIconPath: 'M11 17H9V8h2v9zm4-9h-2v9h2V8zm4-4v1h-1v16H6V5H5V4h4V3h6v1h4zm-2 1H7v15h10V5z',
directRemoveButton: 'direct-remove-button'
},
classes: {
videoHidden: 'playlist-video-hidden',
initialized: 'playlist-enhanced'
},
debounceDelay: 200,
clickDelay: 150,
initTimeout: 10000,
retryDelay: 1000,
maxRetries: 5
};
// --- State ---
const state = {
observer: null,
navigationObserver: null,
isProcessingRemove: false,
currentQuery: '',
playlistListener: null,
initialized: false,
retryCount: 0,
currentUrl: location.href
};
// --- Utilities ---
const debounce = (func, wait) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
};
const isTargetPlaylistPage = () => {
const listId = new URLSearchParams(location.search).get('list');
return listId === 'LL' || listId === 'WL';
};
const waitForElement = (selector, timeout = 5000) => {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) return resolve(el);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => {
observer.disconnect();
reject(new Error(`Element not found: ${selector}`));
}, timeout);
});
};
// --- Core Functionality ---
const addStyles = () => {
if (document.getElementById('playlist-enh-styles')) return;
const style = document.createElement('style');
style.id = 'playlist-enh-styles';
style.textContent = `
html:not([dark]) .playlist-search-container {background-color: rgba(0,0,0,0.04);color: #222;border: 1px solid rgba(0,0,0,0.08);}
html:not([dark]) .playlist-search-container:not(.active):hover {background-color: rgba(0,0,0,0.08);}
html:not([dark]) .playlist-search-container.active {background-color: rgba(0,0,0,0.08);border-color: #f00;}
html:not([dark]) .playlist-search-input::placeholder {color: #606060;}
html:not([dark]) .playlist-search-clear {color: #606060;}
html:not([dark]) .${CONFIG.selectors.directRemoveButton} {background: rgba(255,0,0,0.08);color: #222;border: 1px solid rgba(255,0,0,0.18);}
html:not([dark]) .${CONFIG.selectors.directRemoveButton}:hover {background: rgba(255,0,0,0.18);}}
html[dark] .playlist-search-container {background-color: rgba(255,255,255,0.10);color: #fff;border: 1px solid rgba(255,255,255,0.10);}
html[dark] .playlist-search-container:not(.active):hover {background-color: rgba(255,255,255,0.15);}
html[dark] .playlist-search-container.active {background-color: rgba(255,255,255,0.15);border-color: #f00;}
html[dark] .playlist-search-input::placeholder {color: #aaa;}
html[dark] .playlist-search-clear {color: #aaa;}
html[dark] .${CONFIG.selectors.directRemoveButton} {background: rgba(255,0,0,0.10);color: #fff;border: 1px solid rgba(255,0,0,0.30);}
html[dark] .${CONFIG.selectors.directRemoveButton}:hover {background: rgba(255,0,0,0.30);}}
.playlist-search-container { display: inline-flex; align-items: center; vertical-align: middle; margin: 0 4px; height: 32px; border-radius: 8px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; }
.playlist-search-container:not(.active) { width: 44px; cursor: pointer; }
.playlist-search-container.active { width: 240px; }
.playlist-search-button { background: transparent; border: none; display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer; outline: none; width: 100%; height: 100%; }
.playlist-search-container.active .playlist-search-button { display: none; }
.playlist-search-input { flex-grow: 1; background: transparent; border: none; font-size: 14px; font-weight: 500; outline: none; font-family: inherit; width: 0; padding: 0; opacity: 0; transition: all 0.2s ease-in-out; }
.playlist-search-container.active .playlist-search-input { width: 100%; padding: 0 12px; opacity: 1; }
.playlist-search-clear { background: transparent; border: none; cursor: pointer; padding: 0 8px; font-size: 20px; line-height: 1; opacity: 0.7; display: none; }
.playlist-search-clear:hover { opacity: 1; }
.playlist-search-container.active .playlist-search-clear { display: block; }
.${CONFIG.selectors.directRemoveButton} { position: absolute; top: 5px; right: 5px; border-radius: 50%; width: 36px; height: 36px; cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transform: scale(0.8); transition: all 0.2s ease; z-index: 10; }
${CONFIG.selectors.videoRenderer}:hover .${CONFIG.selectors.directRemoveButton} { opacity: 1; visibility: visible; transform: scale(1); }
.${CONFIG.selectors.directRemoveButton}:hover { transform: scale(1.1); }
.${CONFIG.selectors.directRemoveButton} svg { pointer-events: none; }
.${CONFIG.classes.videoHidden} { display: none !important; }
.${CONFIG.classes.initialized} { }
`;
document.head.appendChild(style);
};
const addSearchUI = (target) => {
if (target.querySelector(CONFIG.selectors.searchContainer)) return;
const container = document.createElement('div');
container.className = 'playlist-search-container';
container.innerHTML = `
<button class="playlist-search-button" aria-label="Search playlist">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="20" viewBox="0 0 24 24" width="20" focusable="false" aria-hidden="true"><path d="M20.87,20.17l-5.59-5.59C16.35,13.35,17,11.75,17,10c0-3.87-3.13-7-7-7s-7,3.13-7,7s3.13,7,7,7c1.75,0,3.35-0.65,4.58-1.71l5.59,5.59L20.87,20.17z M10,16c-3.31,0-6-2.69-6-6s2.69-6,6-6s6,2.69,6,6S13.31,16,10,16z"></path></svg>
</button>
<input type="text" placeholder="Search playlist..." class="playlist-search-input">
<button class="playlist-search-clear" title="Clear search">×</button>
`;
const input = container.querySelector('input');
const button = container.querySelector('button.playlist-search-button');
const clearButton = container.querySelector('button.playlist-search-clear');
const debouncedFilter = debounce(filterVideos, CONFIG.debounceDelay);
input.addEventListener('input', (e) => {
state.currentQuery = e.target.value;
clearButton.style.display = state.currentQuery ? 'block' : 'none';
debouncedFilter();
});
clearButton.addEventListener('click', () => {
input.value = '';
input.dispatchEvent(new Event('input', { bubbles: true }));
input.focus();
});
button.addEventListener('click', (e) => {
if (!container.classList.contains('active')) {
e.stopPropagation();
container.classList.add('active');
input.focus();
}
});
input.addEventListener('blur', () => {
if (input.value === '') container.classList.remove('active');
});
target.appendChild(container);
};
const performDirectRemove = async (removeButton) => {
if (state.isProcessingRemove) return;
state.isProcessingRemove = true;
const videoRenderer = removeButton.closest(CONFIG.selectors.videoRenderer);
try {
if (!videoRenderer) throw new Error("Video renderer not found.");
const menuButton = videoRenderer.querySelector(CONFIG.selectors.removeMenuButton);
if (!menuButton) throw new Error("Menu button not found.");
menuButton.click();
await new Promise(resolve => setTimeout(resolve, CONFIG.clickDelay));
const removeItem = Array.from(document.querySelectorAll(CONFIG.selectors.removeMenuItem))
.find(item => {
const path = item.querySelector('path')?.getAttribute('d');
const text = item.textContent?.trim().toLowerCase();
return path === CONFIG.selectors.removeMenuItemIconPath || text?.startsWith('remove from');
});
if (removeItem) {
videoRenderer.style.transition = 'opacity 0.3s, transform 0.3s';
videoRenderer.style.opacity = '0';
videoRenderer.style.transform = 'translateX(-50px)';
removeItem.click();
} else {
document.body.click(); // Close menu if item not found
throw new Error("Remove menu item not found.");
}
} catch (error) {
console.error("Direct remove failed:", error);
if (videoRenderer) videoRenderer.style.cssText = ''; // Reset styles on failure
} finally {
state.isProcessingRemove = false;
}
};
const filterVideos = () => {
const videos = document.querySelectorAll(CONFIG.selectors.videoRenderer);
const query = state.currentQuery.toLowerCase().trim();
videos.forEach(video => {
const titleElement = video.querySelector(CONFIG.selectors.videoTitle);
const title = titleElement ? titleElement.textContent.toLowerCase() : '';
video.classList.toggle(CONFIG.classes.videoHidden, !title.includes(query));
});
};
const processVideoRenderer = (video) => {
// Add direct remove button
if (!video.querySelector(`.${CONFIG.selectors.directRemoveButton}`)) {
const button = document.createElement('button');
button.className = CONFIG.selectors.directRemoveButton;
button.title = 'Remove from playlist';
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 24 24" width="20" fill="currentColor"><path d="${CONFIG.selectors.removeMenuItemIconPath}"></path></svg>`;
video.style.position = 'relative'; // Ensure proper positioning
video.appendChild(button);
}
// Apply initial filter state
const titleElement = video.querySelector(CONFIG.selectors.videoTitle);
const title = titleElement ? titleElement.textContent.toLowerCase() : '';
video.classList.toggle(CONFIG.classes.videoHidden, !title.includes(state.currentQuery));
};
const setupListeners = (playlistEl) => {
// Mark as initialized
playlistEl.classList.add(CONFIG.classes.initialized);
// Disconnect old observer
state.observer?.disconnect();
// Setup mutation observer for new videos
state.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.matches(CONFIG.selectors.videoRenderer)) {
processVideoRenderer(node);
}
}
}
});
state.observer.observe(playlistEl, { childList: true, subtree: true });
// Setup delegated click listener for remove buttons
if (state.playlistListener) playlistEl.removeEventListener('click', state.playlistListener);
state.playlistListener = (event) => {
const removeButton = event.target.closest(`.${CONFIG.selectors.directRemoveButton}`);
if (removeButton) {
event.preventDefault();
event.stopPropagation();
performDirectRemove(removeButton);
}
};
playlistEl.addEventListener('click', state.playlistListener);
};
const cleanup = () => {
state.observer?.disconnect();
state.observer = null;
const playlistEl = document.querySelector(CONFIG.selectors.playlistContainer);
if (playlistEl && state.playlistListener) {
playlistEl.removeEventListener('click', state.playlistListener);
state.playlistListener = null;
playlistEl.classList.remove(CONFIG.classes.initialized);
}
document.querySelector(CONFIG.selectors.searchContainer)?.remove();
document.querySelectorAll(`.${CONFIG.selectors.directRemoveButton}`).forEach(btn => btn.remove());
document.querySelectorAll(`.${CONFIG.classes.videoHidden}`).forEach(v => v.classList.remove(CONFIG.classes.videoHidden));
state.currentQuery = '';
state.initialized = false;
state.retryCount = 0;
};
const init = async () => {
if (!CONFIG.enabled || !isTargetPlaylistPage()) {
cleanup();
return;
}
// Check if already initialized
const existingPlaylist = document.querySelector(`${CONFIG.selectors.playlistContainer}.${CONFIG.classes.initialized}`);
if (existingPlaylist && state.initialized) {
return;
}
try {
const searchTarget = await waitForElement(CONFIG.selectors.searchInsertTarget, CONFIG.initTimeout);
const playlistEl = await waitForElement(CONFIG.selectors.playlistContainer, CONFIG.initTimeout);
addStyles();
addSearchUI(searchTarget);
document.querySelectorAll(CONFIG.selectors.videoRenderer).forEach(processVideoRenderer);
setupListeners(playlistEl);
state.initialized = true;
state.retryCount = 0;
} catch (error) {
console.error("Playlist Enhancements initialization failed:", error);
if (state.retryCount < CONFIG.maxRetries) {
state.retryCount++;
setTimeout(init, CONFIG.retryDelay * state.retryCount);
}
}
};
const handleNavigation = debounce(() => {
const newUrl = location.href;
if (newUrl !== state.currentUrl) {
state.currentUrl = newUrl;
cleanup();
setTimeout(init, 300);
}
}, 100);
const setupNavigationObserver = () => {
// Enhanced navigation detection
state.navigationObserver?.disconnect();
state.navigationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.target.matches &&
(mutation.target.matches('ytd-app') || mutation.target.matches('ytd-page-manager'))) {
handleNavigation();
break;
}
}
});
const appElement = document.querySelector('ytd-app') || document.body;
state.navigationObserver.observe(appElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['page-subtype']
});
};
// --- Initialization and Navigation ---
const initializeModule = () => {
addStyles();
setupNavigationObserver();
// Handle page load events
document.addEventListener('yt-navigate-finish', handleNavigation);
window.addEventListener('popstate', handleNavigation);
// Override history methods for SPA navigation
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function (...args) {
const result = originalPushState.apply(this, args);
handleNavigation();
return result;
};
history.replaceState = function (...args) {
const result = originalReplaceState.apply(this, args);
handleNavigation();
return result;
};
// Initial setup
init();
};
// Start when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeModule);
} else {
initializeModule();
}
// Cleanup on page unload
window.addEventListener('beforeunload', cleanup);
})();
// viewer
(function () {
"use strict";
// Glassmorphism styles for thumbnail viewer
const css = `
:root {
--thumbnail-btn-bg-light: rgba(255,255,255,0.85);
--thumbnail-btn-bg-dark: rgba(0,0,0,0.7);
--thumbnail-btn-hover-bg-light: rgba(255,255,255,1);
--thumbnail-btn-hover-bg-dark: rgba(0,0,0,0.9);
--thumbnail-btn-color-light: #222;
--thumbnail-btn-color-dark: #fff;
--thumbnail-modal-bg-light: rgba(255,255,255,0.95);
--thumbnail-modal-bg-dark: rgba(34,34,34,0.85);
--thumbnail-modal-title-light: #222;
--thumbnail-modal-title-dark: #fff;
--thumbnail-modal-btn-bg-light: rgba(0,0,0,0.08);
--thumbnail-modal-btn-bg-dark: rgba(255,255,255,0.08);
--thumbnail-modal-btn-hover-bg-light: rgba(0,0,0,0.18);
--thumbnail-modal-btn-hover-bg-dark: rgba(255,255,255,0.18);
--thumbnail-modal-btn-color-light: #222;
--thumbnail-modal-btn-color-dark: #fff;
--thumbnail-modal-btn-hover-color-light: #ff4444;
--thumbnail-modal-btn-hover-color-dark: #ff4444;
--thumbnail-glass-blur: blur(18px) saturate(180%);
--thumbnail-glass-shadow: 0 8px 32px rgba(0,0,0,.2);
--thumbnail-glass-border: rgba(255,255,255,.2);
}
html[dark], body[dark] {
--thumbnail-btn-bg: var(--thumbnail-btn-bg-dark);
--thumbnail-btn-hover-bg: var(--thumbnail-btn-hover-bg-dark);
--thumbnail-btn-color: var(--thumbnail-btn-color-dark);
--thumbnail-modal-bg: var(--thumbnail-modal-bg-dark);
--thumbnail-modal-title: var(--thumbnail-modal-title-dark);
--thumbnail-modal-btn-bg: var(--thumbnail-modal-btn-bg-dark);
--thumbnail-modal-btn-hover-bg: var(--thumbnail-modal-btn-hover-bg-dark);
--thumbnail-modal-btn-color: var(--thumbnail-modal-btn-color-dark);
--thumbnail-modal-btn-hover-color: var(--thumbnail-modal-btn-hover-color-dark);
}
html:not([dark]) {
--thumbnail-btn-bg: var(--thumbnail-btn-bg-light);
--thumbnail-btn-hover-bg: var(--thumbnail-btn-hover-bg-light);
--thumbnail-btn-color: var(--thumbnail-btn-color-light);
--thumbnail-modal-bg: var(--thumbnail-modal-bg-light);
--thumbnail-modal-title: var(--thumbnail-modal-title-light);
--thumbnail-modal-btn-bg: var(--thumbnail-modal-btn-bg-light);
--thumbnail-modal-btn-hover-bg: var(--thumbnail-modal-btn-hover-bg-light);
--thumbnail-modal-btn-color: var(--thumbnail-modal-btn-color-light);
--thumbnail-modal-btn-hover-color: var(--thumbnail-modal-btn-hover-color-light);
}
.thumbnail-overlay-container {
position: absolute;
bottom: 8px;
left: 8px;
z-index: 9999;
opacity: 0;
transition: opacity 0.2s ease;
}
.thumbnail-overlay-button {
width: 28px;
height: 28px;
background: var(--thumbnail-btn-bg);
border: none;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--thumbnail-btn-color);
position: relative;
box-shadow: var(--thumbnail-glass-shadow);
backdrop-filter: var(--thumbnail-glass-blur);
-webkit-backdrop-filter: var(--thumbnail-glass-blur);
border: 1px solid var(--thumbnail-glass-border);
}
.thumbnail-overlay-button:hover {
background: var(--thumbnail-btn-hover-bg);
}
.thumbnail-overlay-button svg {
width: 18px;
height: 18px;
fill: currentColor;
}
.thumbnail-dropdown {
position: absolute;
bottom: 100%;
left: 0;
background: var(--thumbnail-btn-hover-bg);
border-radius: 8px;
padding: 4px;
margin-bottom: 4px;
display: none;
flex-direction: column;
min-width: 140px;
box-shadow: var(--thumbnail-glass-shadow);
z-index: 10000;
backdrop-filter: var(--thumbnail-glass-blur);
-webkit-backdrop-filter: var(--thumbnail-glass-blur);
border: 1px solid var(--thumbnail-glass-border);
}
.thumbnail-dropdown.show {
display: flex !important;
}
.thumbnail-dropdown-item {
background: none;
border: none;
color: var(--thumbnail-btn-color);
padding: 8px 12px;
cursor: pointer;
border-radius: 4px;
font-size: 12px;
text-align: left;
white-space: nowrap;
transition: background-color 0.2s ease;
}
.thumbnail-dropdown-item:hover {
background: rgba(255,255,255,0.1);
}
.yt-lockup-view-model-wiz__content-image:hover .thumbnail-overlay-container {
opacity: 1;
}
.yt-lockup-view-model-wiz__content-image {
position: relative;
}
.shortsLockupViewModelHostEndpoint:hover .thumbnail-overlay-container {
opacity: 1;
}
.shortsLockupViewModelHostEndpoint {
position: relative;
}
ytd-thumbnail:hover .thumbnail-overlay-container {
opacity: 1;
}
ytd-thumbnail {
position: relative;
}
ytm-shorts-lockup-view-model:hover .thumbnail-overlay-container {
opacity: 1;
}
ytm-shorts-lockup-view-model {
position: relative;
}
.thumbnailPreview-button {
position: absolute;
bottom: 10px;
left: 5px;
background-color: var(--thumbnail-btn-bg);
color: var(--thumbnail-btn-color);
border: none;
border-radius: 6px;
padding: 3px;
font-size: 18px;
cursor: pointer;
z-index: 2000;
opacity: 0;
transition: opacity 0.3s;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--thumbnail-glass-shadow);
backdrop-filter: var(--thumbnail-glass-blur);
-webkit-backdrop-filter: var(--thumbnail-glass-blur);
border: 1px solid var(--thumbnail-glass-border);
}
.thumbnailPreview-container {
position: relative;
}
.thumbnailPreview-container:hover .thumbnailPreview-button {
opacity: 1;
}
/* Modal styles */
.thumbnail-modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
animation: fadeInModal 0.2s;
backdrop-filter: var(--thumbnail-glass-blur);
-webkit-backdrop-filter: var(--thumbnail-glass-blur);
}
.thumbnail-modal-content {
background: var(--thumbnail-modal-bg);
border-radius: 18px;
box-shadow: var(--thumbnail-glass-shadow);
max-width: 90vw;
max-height: 90vh;
overflow: auto;
position: relative;
padding: 24px 24px 16px 24px;
display: flex;
flex-direction: column;
align-items: center;
animation: scaleInModal 0.2s;
border: 1px solid var(--thumbnail-glass-border);
backdrop-filter: var(--thumbnail-glass-blur);
-webkit-backdrop-filter: var(--thumbnail-glass-blur);
}
.thumbnail-modal-close {
position: absolute;
top: 12px;
right: 18px;
background: rgba(0,0,0,0.2);
color: #fff;
border: none;
font-size: 28px;
line-height: 1;
width: 36px;
height: 36px;
border-radius: 50%;
cursor: pointer;
transition: background 0.2s;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.thumbnail-modal-close:hover {
background: rgba(0,0,0,0.4);
color: #ff4444;
}
.thumbnail-modal-img {
max-width: 80vw;
max-height: 70vh;
border-radius: 12px;
margin-bottom: 12px;
box-shadow: var(--thumbnail-glass-shadow);
background: #222;
border: 1px solid var(--thumbnail-glass-border);
}
.thumbnail-modal-options {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 8px;
justify-content: center;
}
.thumbnail-modal-option-btn {
background: var(--thumbnail-modal-btn-bg);
color: var(--thumbnail-modal-btn-color);
border: none;
border-radius: 8px;
padding: 8px 18px;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
margin-bottom: 6px;
box-shadow: var(--thumbnail-glass-shadow);
backdrop-filter: var(--thumbnail-glass-blur);
-webkit-backdrop-filter: var(--thumbnail-glass-blur);
border: 1px solid var(--thumbnail-glass-border);
}
.thumbnail-modal-option-btn:hover {
background: var(--thumbnail-modal-btn-hover-bg);
color: var(--thumbnail-modal-btn-hover-color);
}
.thumbnail-modal-title {
font-size: 18px;
font-weight: 600;
color: var(--thumbnail-modal-title);
margin-bottom: 10px;
text-align: center;
text-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
@keyframes fadeInModal {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleInModal {
from { transform: scale(0.95); }
to { transform: scale(1); }
}
`;
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
function extractVideoId(url) {
const regularMatch = url.match(/[?&]v=([^&]+)/);
if (regularMatch) {
return regularMatch[1];
}
const shortsMatch = url.match(/\/shorts\/([^?&]+)/);
if (shortsMatch) {
return shortsMatch[1];
}
return null;
}
function openImageModal(url, options, videoId, isShorts) {
document.querySelectorAll('.thumbnail-modal-overlay').forEach(m => m.remove());
const overlay = document.createElement('div');
overlay.className = 'thumbnail-modal-overlay';
const content = document.createElement('div');
content.className = 'thumbnail-modal-content';
const closeBtn = document.createElement('button');
closeBtn.className = 'thumbnail-modal-close';
closeBtn.innerHTML = '×';
closeBtn.title = 'Close';
closeBtn.onclick = () => overlay.remove();
const title = document.createElement('div');
title.className = 'thumbnail-modal-title';
title.textContent = isShorts ? 'Shorts Thumbnail Preview' : 'Video Thumbnail Preview';
const img = document.createElement('img');
img.className = 'thumbnail-modal-img';
img.src = url;
img.alt = 'Thumbnail Preview';
img.title = 'Click to open in new tab';
img.style.cursor = 'pointer';
img.onclick = () => window.open(img.src, '_blank');
const optionsDiv = document.createElement('div');
optionsDiv.className = 'thumbnail-modal-options';
options.forEach(opt => {
const btn = document.createElement('button');
btn.className = 'thumbnail-modal-option-btn';
btn.textContent = opt.name;
btn.onclick = () => {
img.src = `https://i.ytimg.com/vi/${videoId}/${opt.filename}`;
};
optionsDiv.appendChild(btn);
});
content.append(closeBtn, title, img, optionsDiv);
overlay.appendChild(content);
overlay.onclick = e => {
if (e.target === overlay) overlay.remove();
};
document.addEventListener('keydown', function escHandler(e) {
if (e.key === 'Escape') {
overlay.remove();
document.removeEventListener('keydown', escHandler, true);
}
}, true);
document.body.appendChild(overlay);
}
function createSVGElement(pathD) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("width", "1em");
svg.setAttribute("height", "1em");
svg.setAttribute("viewBox", "0 0 24 24");
path.setAttribute("fill", "currentColor");
path.setAttribute("d", pathD);
svg.appendChild(path);
return svg;
}
const defaultIconPath =
"M18 20H4V6h9V4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-9h-2zm-7.79-3.17l-1.96-2.36L5.5 18h11l-3.54-4.71zM20 4V1h-2v3h-3c.01.01 0 2 0 2h3v2.99c.01.01 2 0 2 0V6h3V4";
const hoverIconPath =
"M19 7v2.99s-1.99.01-2 0V7h-3s.01-1.99 0-2h3V2h2v3h3v2zm-3 4V8h-3V5H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-8zM5 19l3-4l2 3l3-4l4 5z";
function createOverlayButton(videoId, isShorts = false) {
const container = document.createElement("div");
container.className = "thumbnail-overlay-container";
const button = document.createElement("button");
button.className = "thumbnail-overlay-button";
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", defaultIconPath);
svg.appendChild(path);
button.appendChild(svg);
let thumbnailOptions;
if (isShorts) {
thumbnailOptions = [
{ name: "Default", filename: "oardefault.jpg" },
{ name: "Alternative 1", filename: "oar1.jpg" },
{ name: "Alternative 2", filename: "oar2.jpg" },
{ name: "Alternative 3", filename: "oar3.jpg" },
];
} else {
thumbnailOptions = [
{ name: "Default", filename: "default.jpg" },
{ name: "Medium", filename: "mqdefault.jpg" },
{ name: "High", filename: "hqdefault.jpg" },
{ name: "Standard", filename: "sddefault.jpg" },
{ name: "Max Res", filename: "maxresdefault.jpg" },
];
}
button.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
openImageModal(
`https://i.ytimg.com/vi/${videoId}/${thumbnailOptions[0].filename}`,
thumbnailOptions,
videoId,
isShorts
);
});
container.appendChild(button);
return container;
}
function addButtonToElement(element, getFullSizeUrl) {
if (!element.closest(".thumbnailPreview-container")) {
const container = document.createElement("div");
container.className = "thumbnailPreview-container";
element.parentNode.insertBefore(container, element);
container.appendChild(element);
const button = document.createElement("button");
button.className = "thumbnailPreview-button";
const defaultIcon = createSVGElement(defaultIconPath);
button.appendChild(defaultIcon);
button.addEventListener("mouseenter", () => {
button.innerHTML = "";
button.appendChild(createSVGElement(hoverIconPath));
});
button.addEventListener("mouseleave", () => {
button.innerHTML = "";
button.appendChild(createSVGElement(defaultIconPath));
});
button.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
const url = getFullSizeUrl(element.src);
if (url) {
openImageModal(url, [
{ name: "Original", filename: "" }
], "", false);
}
});
container.appendChild(button);
}
}
function isWatchPage() {
const url = new URL(window.location.href);
return url.pathname === "/watch" && url.searchParams.has("v");
}
function processAvatars() {
const avatars = document.querySelectorAll("yt-avatar-shape img, yt-img-shadow#avatar img");
avatars.forEach((img) => {
if (!img.closest(".thumbnailPreview-container")) {
addButtonToElement(img, (src) => src.replace(/=s\d+-c-k-c0x00ffffff-no-rj.*/, "=s0"));
if (isWatchPage()) {
const button = img.closest(".thumbnailPreview-container").querySelector(".thumbnailPreview-button");
if (button) {
button.style.display = "none";
}
}
}
});
}
function processChannelBanners() {
const banners = document.querySelectorAll("yt-image-banner-view-model img");
banners.forEach((img) => {
if (!img.closest(".thumbnailPreview-container")) {
addButtonToElement(img, (src) => src.replace(/=w\d+-.*/, "=s0"));
}
});
}
function cleanupDuplicateButtons() {
const shortsWithMultipleButtons = document.querySelectorAll("ytm-shorts-lockup-view-model");
shortsWithMultipleButtons.forEach((shortsContainer) => {
const buttons = shortsContainer.querySelectorAll(".thumbnail-overlay-container");
if (buttons.length > 1) {
const thumbnailContainer = shortsContainer.querySelector(".shortsLockupViewModelHostThumbnailContainer");
const preferredButton = thumbnailContainer
? thumbnailContainer.querySelector(".thumbnail-overlay-container")
: buttons[0];
buttons.forEach((button) => {
if (button !== preferredButton) {
button.remove();
}
});
}
});
}
function addOverlayButtons() {
const sidebarThumbnails = document.querySelectorAll(".yt-lockup-view-model-wiz__content-image:not([data-overlay-added])");
sidebarThumbnails.forEach((container) => {
const href = container.getAttribute("href");
if (href) {
const videoId = extractVideoId(href);
if (videoId) {
const overlayButton = createOverlayButton(videoId, false);
container.appendChild(overlayButton);
container.setAttribute("data-overlay-added", "true");
}
}
});
const channelThumbnails = document.querySelectorAll("ytd-thumbnail:not([data-overlay-added])");
channelThumbnails.forEach((thumbnail) => {
const link = thumbnail.querySelector('a[href*="/watch?v="]');
if (link) {
const href = link.getAttribute("href");
const videoId = extractVideoId(href);
if (videoId) {
const overlayButton = createOverlayButton(videoId, false);
thumbnail.appendChild(overlayButton);
thumbnail.setAttribute("data-overlay-added", "true");
}
}
});
const allShortsContainers = document.querySelectorAll("ytm-shorts-lockup-view-model:not([data-overlay-added])");
allShortsContainers.forEach((shortsContainer) => {
const link = shortsContainer.querySelector('a[href*="/shorts/"]');
if (link) {
const href = link.getAttribute("href");
const videoId = extractVideoId(href);
if (videoId) {
const overlayButton = createOverlayButton(videoId, true);
const thumbnailContainer = shortsContainer.querySelector(".shortsLockupViewModelHostThumbnailContainer");
if (thumbnailContainer) {
thumbnailContainer.appendChild(overlayButton);
} else {
shortsContainer.appendChild(overlayButton);
}
shortsContainer.setAttribute("data-overlay-added", "true");
}
}
});
const sidebarShorts = document.querySelectorAll(".shortsLockupViewModelHostEndpoint:not([data-overlay-added])");
sidebarShorts.forEach((container) => {
if (container.closest("ytm-shorts-lockup-view-model")) {
return;
}
const href = container.getAttribute("href");
if (href) {
const videoId = extractVideoId(href);
if (videoId) {
const overlayButton = createOverlayButton(videoId, true);
container.appendChild(overlayButton);
container.setAttribute("data-overlay-added", "true");
}
}
});
processAvatars();
processChannelBanners();
cleanupDuplicateButtons();
}
function observeChanges() {
const observer = new MutationObserver(function (mutations) {
let shouldCheck = false;
mutations.forEach(function (mutation) {
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (
node.querySelector &&
(node.querySelector(".yt-lockup-view-model-wiz__content-image") ||
node.classList.contains("yt-lockup-view-model-wiz__content-image") ||
node.querySelector(".shortsLockupViewModelHostEndpoint") ||
node.classList.contains("shortsLockupViewModelHostEndpoint") ||
node.querySelector("ytd-thumbnail") ||
node.classList.contains("ytd-thumbnail") ||
node.querySelector("ytm-shorts-lockup-view-model") ||
node.classList.contains("ytm-shorts-lockup-view-model") ||
node.querySelector("yt-avatar-shape") ||
node.classList.contains("yt-avatar-shape") ||
node.querySelector("yt-image-banner-view-model") ||
node.classList.contains("yt-image-banner-view-model"))
) {
shouldCheck = true;
}
}
});
}
});
if (shouldCheck) {
setTimeout(addOverlayButtons, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
}
function setupUrlChangeDetection() {
let currentUrl = location.href;
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function () {
originalPushState.apply(history, arguments);
setTimeout(() => {
if (location.href !== currentUrl) {
currentUrl = location.href;
setTimeout(addOverlayButtons, 500);
}
}, 100);
};
history.replaceState = function () {
originalReplaceState.apply(history, arguments);
setTimeout(() => {
if (location.href !== currentUrl) {
currentUrl = location.href;
setTimeout(addOverlayButtons, 500);
}
}, 100);
};
window.addEventListener("popstate", function () {
setTimeout(() => {
if (location.href !== currentUrl) {
currentUrl = location.href;
setTimeout(addOverlayButtons, 300);
}
}, 100);
});
setInterval(function () {
if (location.href !== currentUrl) {
currentUrl = location.href;
setTimeout(addOverlayButtons, 300);
}
}, 500);
document.addEventListener("yt-navigate-start", function () {
setTimeout(addOverlayButtons, 1000);
});
document.addEventListener("yt-navigate-finish", function () {
setTimeout(addOverlayButtons, 500);
});
}
function observePageChanges() {
const contentObserver = new MutationObserver((mutations) => {
let shouldProcessRegular = false;
mutations.forEach((mutation) => {
if (mutation.addedNodes.length > 0) {
shouldProcessRegular = true;
}
});
if (shouldProcessRegular) {
processAvatars();
processChannelBanners();
}
});
const panelObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (
mutation.type === "childList" &&
(mutation.target.id === "secondary" ||
mutation.target.id === "secondary-inner")
) {
// Removed thumbnailPreview-custom-image insertion
}
}
});
contentObserver.observe(document.body, {
childList: true,
subtree: true,
});
const observeSecondary = () => {
const secondary = document.getElementById("secondary");
if (secondary) {
panelObserver.observe(secondary, {
childList: true,
subtree: true,
});
} else {
setTimeout(observeSecondary, 1000);
}
};
observeSecondary();
}
function init() {
addOverlayButtons();
observeChanges();
observePageChanges();
setupUrlChangeDetection();
window.addEventListener("yt-navigate-finish", () => {
setTimeout(addOverlayButtons, 1000);
});
setTimeout(addOverlayButtons, 2000);
setTimeout(addOverlayButtons, 5000);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
// Stats button and menu
(function () {
'use strict';
// Glassmorphism styles for stats button and menu
const styles = `
.videoStats {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 8px;
background: rgba(255,255,255,0.15);
box-shadow: 0 8px 32px rgba(0,0,0,.18);
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
border: 1px solid rgba(255,255,255,.18);
transition: background 0.2s;
}
html[dark] .videoStats {
background: rgba(34,34,34,0.7);
border: 1px solid rgba(255,255,255,.18);
}
html:not([dark]) .videoStats {
background: rgba(255,255,255,0.15);
border: 1px solid rgba(0,0,0,.08);
}
.videoStats:hover {
background: rgba(255,255,255,0.22);
}
.videoStats svg {
width: 18px;
height: 18px;
fill: var(--yt-spec-text-primary, #030303);
}
html[dark] .videoStats svg {
fill: #fff;
}
html:not([dark]) .videoStats svg {
fill: #222;
}
.shortsStats {
display: flex;
align-items: center;
justify-content: center;
margin-top: 16px;
margin-bottom: 16px;
width: 48px;
height: 48px;
border-radius: 50%;
cursor: pointer;
background: rgba(255,255,255,0.15);
box-shadow: 0 8px 32px rgba(0,0,0,.18);
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
border: 1px solid rgba(255,255,255,.18);
transition: background 0.3s;
}
html[dark] .shortsStats {
background: rgba(34,34,34,0.7);
border: 1px solid rgba(255,255,255,.18);
}
html:not([dark]) .shortsStats {
background: rgba(255,255,255,0.15);
border: 1px solid rgba(0,0,0,.08);
}
.shortsStats:hover {
background: rgba(255,255,255,0.22);
}
.shortsStats svg {
width: 24px;
height: 24px;
fill: #222;
}
html[dark] .shortsStats svg {
fill: #fff;
}
html:not([dark]) .shortsStats svg {
fill: #222;
}
.stats-menu-container {
position: relative;
display: inline-block;
}
.stats-horizontal-menu {
position: absolute;
display: flex;
left: 100%;
top: 0;
height: 100%;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.2s linear;
z-index: 100;
}
.stats-menu-container:hover .stats-horizontal-menu {
visibility: visible;
opacity: 1;
}
.stats-menu-button {
margin-left: 8px;
white-space: nowrap;
}
/* Modal styles */
.stats-modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
animation: fadeInModal 0.2s;
backdrop-filter: blur(18px) saturate(180%);
-webkit-backdrop-filter: blur(18px) saturate(180%);
}
.stats-modal-content {
background: rgba(34,34,34,0.95);
border-radius: 18px;
box-shadow: 0 8px 32px rgba(0,0,0,.2);
max-width: 90vw;
max-height: 90vh;
overflow: auto;
position: relative;
padding: 24px 24px 16px 24px;
display: flex;
flex-direction: column;
align-items: center;
animation: scaleInModal 0.2s;
border: 1px solid rgba(255,255,255,.2);
backdrop-filter: blur(18px) saturate(180%);
-webkit-backdrop-filter: blur(18px) saturate(180%);
}
html[dark] .stats-modal-content {
background: rgba(34,34,34,0.95);
}
html:not([dark]) .stats-modal-content {
background: #fff;
color: #222;
}
.stats-modal-close {
position: absolute;
top: 12px;
right: 18px;
background: rgba(0,0,0,0.2);
color: #fff;
border: none;
font-size: 28px;
line-height: 1;
width: 36px;
height: 36px;
border-radius: 50%;
cursor: pointer;
transition: background 0.2s;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.stats-modal-close:hover {
background: rgba(0,0,0,0.4);
color: #ff4444;
}
.stats-modal-iframe {
width: 80vw;
height: 70vh;
border-radius: 12px;
margin-bottom: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,.2);
background: #222;
border: 1px solid rgba(255,255,255,.2);
}
.stats-modal-title {
font-size: 18px;
font-weight: 600;
color: #fff;
margin-bottom: 10px;
text-align: center;
text-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
html:not([dark]) .stats-modal-title {
color: #222;
}
@keyframes fadeInModal {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleInModal {
from { transform: scale(0.95); }
to { transform: scale(1); }
}
`;
// Settings state
const SETTINGS_KEY = 'youtube_stats_button_enabled';
let statsButtonEnabled = localStorage.getItem(SETTINGS_KEY) !== 'false';
let previousUrl = location.href;
let isChecking = false;
let channelFeatures = {
hasStreams: false,
hasShorts: false
};
function addStyles() {
if (!document.querySelector('#youtube-enhancer-styles')) {
const styleElement = document.createElement('style');
styleElement.id = 'youtube-enhancer-styles';
styleElement.textContent = styles;
document.head.appendChild(styleElement);
}
}
function getCurrentVideoUrl() {
const url = window.location.href;
const urlParams = new URLSearchParams(window.location.search);
const videoId = urlParams.get('v');
if (videoId) {
return `https://www.youtube.com/watch?v=${videoId}`;
}
const shortsMatch = url.match(/\/shorts\/([^?]+)/);
if (shortsMatch) {
return `https://www.youtube.com/shorts/${shortsMatch[1]}`;
}
return null;
}
function getChannelIdentifier() {
const url = window.location.href;
let identifier = '';
if (url.includes('/channel/')) {
identifier = url.split('/channel/')[1].split('/')[0];
} else if (url.includes('/@')) {
identifier = url.split('/@')[1].split('/')[0];
}
return identifier;
}
async function checkChannelTabs(url) {
if (isChecking) return;
isChecking = true;
try {
const response = await fetch(url, {
credentials: 'same-origin'
});
if (!response.ok) {
isChecking = false;
return;
}
const html = await response.text();
const match = html.match(/var ytInitialData = (.+?);<\/script>/);
if (!match || !match[1]) {
isChecking = false;
return;
}
const data = JSON.parse(match[1]);
const tabs = data?.contents?.twoColumnBrowseResultsRenderer?.tabs || [];
let hasStreams = false;
let hasShorts = false;
tabs.forEach(tab => {
const tabUrl = tab?.tabRenderer?.endpoint?.commandMetadata?.webCommandMetadata?.url;
if (tabUrl) {
if (/\/streams$/.test(tabUrl)) hasStreams = true;
if (/\/shorts$/.test(tabUrl)) hasShorts = true;
}
});
channelFeatures = {
hasStreams: hasStreams,
hasShorts: hasShorts
};
const existingMenu = document.querySelector('.stats-menu-container');
if (existingMenu) {
existingMenu.remove();
createStatsMenu();
}
} catch (e) {
} finally {
isChecking = false;
}
}
function isChannelPage(url) {
return url.includes('youtube.com/') &&
(url.includes('/channel/') || url.includes('/@')) &&
!url.includes('/video/') &&
!url.includes('/watch');
}
function checkUrlChange() {
const currentUrl = location.href;
if (currentUrl !== previousUrl) {
previousUrl = currentUrl;
if (isChannelPage(currentUrl)) {
setTimeout(() => checkChannelTabs(currentUrl), 500);
}
}
}
function createStatsIcon(isShorts = false) {
const icon = document.createElement('div');
icon.className = isShorts ? 'shortsStats' : 'videoStats';
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 512 512");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "M500 89c13.8-11 16-31.2 5-45s-31.2-16-45-5L319.4 151.5 211.2 70.4c-11.7-8.8-27.8-8.5-39.2 .6L12 199c-13.8 11-16 31.2-5 45s31.2 16 45 5L192.6 136.5l108.2 81.1c11.7 8.8 27.8 8.5 39.2-.6L500 89zM160 256l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32zM32 352l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32zm288-64c-17.7 0-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32zm96-32l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32z");
svg.appendChild(path);
icon.appendChild(svg);
icon.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
const videoUrl = getCurrentVideoUrl();
if (videoUrl) {
openStatsModal(`https://stats.afkarxyz.fun/?directVideo=${encodeURIComponent(videoUrl)}`, "Video Stats");
}
});
return icon;
}
function insertIconForRegularVideo() {
if (!statsButtonEnabled) return;
const targetSelector = '#owner';
const target = document.querySelector(targetSelector);
if (target && !document.querySelector('.videoStats')) {
const statsIcon = createStatsIcon();
target.appendChild(statsIcon);
}
}
function insertIconForShorts() {
if (!statsButtonEnabled) return false;
const likeButtonContainer = document.querySelector('ytd-reel-video-renderer[is-active] #like-button');
if (likeButtonContainer && !document.querySelector('.shortsStats')) {
const iconDiv = createStatsIcon(true);
likeButtonContainer.parentNode.insertBefore(iconDiv, likeButtonContainer);
return true;
}
return false;
}
function createButton(text, svgPath, viewBox, className, onClick) {
const buttonViewModel = document.createElement('button-view-model');
buttonViewModel.className = `yt-spec-button-view-model ${className}-view-model`;
const button = document.createElement('button');
button.className = `yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment ${className}-button`;
button.setAttribute('aria-disabled', 'false');
button.setAttribute('aria-label', text);
button.style.display = 'flex';
button.style.alignItems = 'center';
button.style.justifyContent = 'center';
button.style.gap = '8px';
button.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
onClick();
});
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", viewBox);
svg.style.width = "20px";
svg.style.height = "20px";
svg.style.fill = "currentColor";
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", svgPath);
svg.appendChild(path);
const buttonText = document.createElement('div');
buttonText.className = `yt-spec-button-shape-next__button-text-content ${className}-text`;
buttonText.textContent = text;
buttonText.style.display = 'flex';
buttonText.style.alignItems = 'center';
const touchFeedback = document.createElement('yt-touch-feedback-shape');
touchFeedback.style.borderRadius = 'inherit';
const touchFeedbackDiv = document.createElement('div');
touchFeedbackDiv.className = 'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response';
touchFeedbackDiv.setAttribute('aria-hidden', 'true');
const strokeDiv = document.createElement('div');
strokeDiv.className = 'yt-spec-touch-feedback-shape__stroke';
const fillDiv = document.createElement('div');
fillDiv.className = 'yt-spec-touch-feedback-shape__fill';
touchFeedbackDiv.appendChild(strokeDiv);
touchFeedbackDiv.appendChild(fillDiv);
touchFeedback.appendChild(touchFeedbackDiv);
button.appendChild(svg);
button.appendChild(buttonText);
button.appendChild(touchFeedback);
buttonViewModel.appendChild(button);
return buttonViewModel;
}
function openStatsModal(url, titleText) {
document.querySelectorAll('.stats-modal-overlay').forEach(m => m.remove());
const overlay = document.createElement('div');
overlay.className = 'stats-modal-overlay';
const content = document.createElement('div');
content.className = 'stats-modal-content';
const closeBtn = document.createElement('button');
closeBtn.className = 'stats-modal-close';
closeBtn.innerHTML = '×';
closeBtn.title = 'Close';
closeBtn.onclick = () => overlay.remove();
const title = document.createElement('div');
title.className = 'stats-modal-title';
title.textContent = titleText || 'Stats';
const iframe = document.createElement('iframe');
iframe.className = 'stats-modal-iframe';
iframe.src = url;
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allowfullscreen', 'true');
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
iframe.style.background = '#222';
content.append(closeBtn, title, iframe);
overlay.appendChild(content);
overlay.onclick = e => {
if (e.target === overlay) overlay.remove();
};
document.addEventListener('keydown', function escHandler(e) {
if (e.key === 'Escape') {
overlay.remove();
document.removeEventListener('keydown', escHandler, true);
}
}, true);
document.body.appendChild(overlay);
}
function createStatsMenu() {
if (!statsButtonEnabled) return;
if (document.querySelector('.stats-menu-container')) {
return;
}
const containerDiv = document.createElement('div');
containerDiv.className = 'yt-flexible-actions-view-model-wiz__action stats-menu-container';
const mainButtonViewModel = document.createElement('button-view-model');
mainButtonViewModel.className = 'yt-spec-button-view-model main-stats-view-model';
const mainButton = document.createElement('button');
mainButton.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--outline yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m yt-spec-button-shape-next--enable-backdrop-filter-experiment main-stats-button';
mainButton.setAttribute('aria-disabled', 'false');
mainButton.setAttribute('aria-label', 'Stats');
mainButton.style.display = 'flex';
mainButton.style.alignItems = 'center';
mainButton.style.justifyContent = 'center';
mainButton.style.gap = '8px';
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 512 512");
svg.style.width = "20px";
svg.style.height = "20px";
svg.style.fill = "currentColor";
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", "M500 89c13.8-11 16-31.2 5-45s-31.2-16-45-5L319.4 151.5 211.2 70.4c-11.7-8.8-27.8-8.5-39.2 .6L12 199c-13.8 11-16 31.2-5 45s31.2 16 45 5L192.6 136.5l108.2 81.1c11.7 8.8 27.8 8.5 39.2-.6L500 89zM160 256l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32zM32 352l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32zm288-64c-17.7 0-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32zm96-32l0 192c0 17.7 14.3 32 32 32s32-14.3 32-32l0-192c0-17.7-14.3-32-32-32s-32 14.3-32 32z");
svg.appendChild(path);
const buttonText = document.createElement('div');
buttonText.className = 'yt-spec-button-shape-next__button-text-content main-stats-text';
buttonText.textContent = 'Stats';
buttonText.style.display = 'flex';
buttonText.style.alignItems = 'center';
const touchFeedback = document.createElement('yt-touch-feedback-shape');
touchFeedback.style.borderRadius = 'inherit';
const touchFeedbackDiv = document.createElement('div');
touchFeedbackDiv.className = 'yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response';
touchFeedbackDiv.setAttribute('aria-hidden', 'true');
const strokeDiv = document.createElement('div');
strokeDiv.className = 'yt-spec-touch-feedback-shape__stroke';
const fillDiv = document.createElement('div');
fillDiv.className = 'yt-spec-touch-feedback-shape__fill';
touchFeedbackDiv.appendChild(strokeDiv);
touchFeedbackDiv.appendChild(fillDiv);
touchFeedback.appendChild(touchFeedbackDiv);
mainButton.appendChild(svg);
mainButton.appendChild(buttonText);
mainButton.appendChild(touchFeedback);
mainButtonViewModel.appendChild(mainButton);
containerDiv.appendChild(mainButtonViewModel);
const horizontalMenu = document.createElement('div');
horizontalMenu.className = 'stats-horizontal-menu';
const channelButtonContainer = document.createElement('div');
channelButtonContainer.className = 'stats-menu-button channel-stats-container';
const channelButton = createButton(
'Channel',
"M64 48c-8.8 0-16 7.2-16 16l0 288c0 8.8 7.2 16 16 16l512 0c8.8 0 16-7.2 16-16l0-288c0-8.8-7.2-16-16-16L64 48zM0 64C0 28.7 28.7 0 64 0L576 0c35.3 0 64 28.7 64 64l0 288c0 35.3-28.7 64-64 64L64 416c-35.3 0-64-28.7-64-64L0 64zM120 464l400 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-400 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z",
"0 0 640 512",
'channel-stats',
() => {
const channelId = getChannelIdentifier();
if (channelId) {
openStatsModal(`https://stats.afkarxyz.fun/?directChannel=${channelId}`, "Channel Stats");
}
}
);
channelButtonContainer.appendChild(channelButton);
horizontalMenu.appendChild(channelButtonContainer);
if (channelFeatures.hasStreams) {
const liveButtonContainer = document.createElement('div');
liveButtonContainer.className = 'stats-menu-button live-stats-container';
const liveButton = createButton(
'Live',
"M99.8 69.4c10.2 8.4 11.6 23.6 3.2 33.8C68.6 144.7 48 197.9 48 256s20.6 111.3 55 152.8c8.4 10.2 7 25.3-3.2 33.8s-25.3 7-33.8-3.2C24.8 389.6 0 325.7 0 256S24.8 122.4 66 72.6c8.4-10.2 23.6-11.6 33.8-3.2zm376.5 0c10.2-8.4 25.3-7 33.8 3.2c41.2 49.8 66 113.8 66 183.4s-24.8 133.6-66 183.4c-8.4 10.2-23.6 11.6-33.8 3.2s-11.6-23.6-3.2-33.8c34.3-41.5 55-94.7 55-152.8s-20.6-111.3-55-152.8c-8.4-10.2-7-25.3 3.2-33.8zM248 256a40 40 0 1 1 80 0 40 40 0 1 1 -80 0zm-61.1-78.5C170 199.2 160 226.4 160 256s10 56.8 26.9 78.5c8.1 10.5 6.3 25.5-4.2 33.7s-25.5 6.3-33.7-4.2c-23.2-29.8-37-67.3-37-108s13.8-78.2 37-108c8.1-10.5 23.2-12.3 33.7-4.2s12.3 23.2 4.2 33.7zM427 148c23.2 29.8 37 67.3 37 108s-13.8 78.2-37 108c-8.1 10.5-23.2 12.3-33.7 4.2s-12.3-23.2-4.2-33.7C406 312.8 416 285.6 416 256s-10-56.8-26.9-78.5c-8.1-10.5-6.3-25.5 4.2-33.7s25.5-6.3 33.7 4.2z",
"0 0 576 512",
'live-stats',
() => {
const channelId = getChannelIdentifier();
if (channelId) {
openStatsModal(`https://stats.afkarxyz.fun/?directStream=${channelId}`, "Live Stats");
}
}
);
liveButtonContainer.appendChild(liveButton);
horizontalMenu.appendChild(liveButtonContainer);
}
if (channelFeatures.hasShorts) {
const shortsButtonContainer = document.createElement('div');
shortsButtonContainer.className = 'stats-menu-button shorts-stats-container';
const shortsButton = createButton(
'Shorts',
"M80 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16l224 0c8.8 0 16-7.2 16-16l0-384c0-8.8-7.2-16-16-16L80 48zM16 64C16 28.7 44.7 0 80 0L304 0c35.3 0 64 28.7 64 64l0 384c0 35.3-28.7 64-64 64L80 512c-35.3 0-64-28.7-64-64L16 64zM160 400l64 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-64 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z",
"0 0 384 512",
'shorts-stats',
() => {
const channelId = getChannelIdentifier();
if (channelId) {
openStatsModal(`https://stats.afkarxyz.fun/?directShorts=${channelId}`, "Shorts Stats");
}
}
);
shortsButtonContainer.appendChild(shortsButton);
horizontalMenu.appendChild(shortsButtonContainer);
}
containerDiv.appendChild(horizontalMenu);
const joinButton = document.querySelector('.yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)');
if (joinButton) {
joinButton.parentNode.appendChild(containerDiv);
} else {
const buttonContainer = document.querySelector('#subscribe-button + #buttons');
if (buttonContainer) {
buttonContainer.appendChild(containerDiv);
}
}
return containerDiv;
}
function checkAndAddMenu() {
if (!statsButtonEnabled) return;
const joinButton = document.querySelector('.yt-flexible-actions-view-model-wiz__action:not(.stats-menu-container)');
const statsMenu = document.querySelector('.stats-menu-container');
if (joinButton && !statsMenu) {
createStatsMenu();
}
}
function checkAndInsertIcon() {
if (!statsButtonEnabled) return;
const isShorts = window.location.pathname.includes('/shorts/');
if (isShorts) {
const shortsObserver = new MutationObserver((_mutations, observer) => {
if (insertIconForShorts()) {
observer.disconnect();
}
});
const shortsContainer = document.querySelector('ytd-shorts');
if (shortsContainer) {
shortsObserver.observe(shortsContainer, {
childList: true,
subtree: true
});
insertIconForShorts();
}
} else if (getCurrentVideoUrl()) {
insertIconForRegularVideo();
}
}
function addSettingsUI() {
const section = document.querySelector('.ytp-plus-settings-section[data-section="experimental"]');
if (!section || section.querySelector('.stats-button-settings-item')) return;
const item = document.createElement('div');
item.className = 'ytp-plus-settings-item stats-button-settings-item';
item.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Statistics Button</label>
<div class="ytp-plus-settings-item-description">Show statistics button on videos and channel menu for quick access to statistics</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" ${statsButtonEnabled ? 'checked' : ''}>
`;
section.appendChild(item);
item.querySelector('input').addEventListener('change', e => {
statsButtonEnabled = e.target.checked;
localStorage.setItem(SETTINGS_KEY, statsButtonEnabled ? 'true' : 'false');
// Remove all stats buttons and menus
document.querySelectorAll('.videoStats,.shortsStats,.stats-menu-container').forEach(el => el.remove());
if (statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
}
});
}
// Observe settings modal for experimental section
const settingsObserver = new MutationObserver(mutations => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.classList?.contains('ytp-plus-settings-modal')) {
setTimeout(addSettingsUI, 50);
}
}
}
if (document.querySelector('.ytp-plus-settings-nav-item[data-section="experimental"].active')) {
setTimeout(addSettingsUI, 50);
}
});
settingsObserver.observe(document.body, { childList: true, subtree: true });
document.addEventListener('click', e => {
if (e.target.classList?.contains('ytp-plus-settings-nav-item') &&
e.target.dataset.section === 'experimental') {
setTimeout(addSettingsUI, 50);
}
}, true);
function init() {
addStyles();
if (statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
}
history.pushState = (function (f) {
return function () {
const result = f.apply(this, arguments);
checkUrlChange();
return result;
};
})(history.pushState);
history.replaceState = (function (f) {
return function () {
const result = f.apply(this, arguments);
checkUrlChange();
return result;
};
})(history.replaceState);
window.addEventListener('popstate', checkUrlChange);
if (isChannelPage(location.href)) {
checkChannelTabs(location.href);
}
}
const observer = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.type === 'childList') {
if (statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.addEventListener('yt-navigate-finish', () => {
if (statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
if (isChannelPage(location.href)) {
checkChannelTabs(location.href);
}
}
});
document.addEventListener('yt-action', function (event) {
if (event.detail && event.detail.actionName === 'yt-reload-continuation-items-command') {
if (statsButtonEnabled) {
checkAndInsertIcon();
checkAndAddMenu();
}
}
});
})();
// count
(function () {
'use strict';
// Enhanced configuration with better defaults
const CONFIG = {
OPTIONS: ['subscribers', 'views', 'videos'],
FONT_LINK: "https://fonts.googleapis.com/css2?family=Rubik:wght@400;700&display=swap",
STATS_API_URL: 'https://api.livecounts.io/youtube-live-subscriber-counter/stats/',
DEFAULT_UPDATE_INTERVAL: 2000,
DEFAULT_OVERLAY_OPACITY: 0.75,
MAX_RETRIES: 3,
CACHE_DURATION: 300000, // 5 minutes
DEBOUNCE_DELAY: 100,
STORAGE_KEY: 'youtube_channel_stats_settings'
};
// Global state management
const state = {
overlay: null,
isUpdating: false,
intervalId: null,
currentChannelName: null,
enabled: localStorage.getItem(CONFIG.STORAGE_KEY) !== 'false',
updateInterval: parseInt(localStorage.getItem('youtubeEnhancerInterval')) || CONFIG.DEFAULT_UPDATE_INTERVAL,
overlayOpacity: parseFloat(localStorage.getItem('youtubeEnhancerOpacity')) || CONFIG.DEFAULT_OVERLAY_OPACITY,
lastSuccessfulStats: new Map(),
previousStats: new Map(),
previousUrl: location.href,
isChecking: false
};
// Utility functions
const utils = {
log: (message, ...args) => {
console.log(`[YouTube Enhancer] ${message}`, ...args);
},
warn: (message, ...args) => {
console.warn(`[YouTube Enhancer] ${message}`, ...args);
},
error: (message, ...args) => {
console.error(`[YouTube Enhancer] ${message}`, ...args);
},
debounce: (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
};
const OPTIONS = CONFIG.OPTIONS;
const FONT_LINK = CONFIG.FONT_LINK;
const STATS_API_URL = CONFIG.STATS_API_URL;
const DEFAULT_UPDATE_INTERVAL = CONFIG.DEFAULT_UPDATE_INTERVAL;
const DEFAULT_OVERLAY_OPACITY = CONFIG.DEFAULT_OVERLAY_OPACITY;
let { overlay, isUpdating, intervalId, currentChannelName, updateInterval, overlayOpacity,
lastSuccessfulStats, previousStats, previousUrl, isChecking } = state;
async function fetchChannel(url) {
if (state.isChecking) return null;
state.isChecking = true;
try {
const response = await fetch(url, {
credentials: 'same-origin'
});
if (!response.ok) return null;
const html = await response.text();
const match = html.match(/var ytInitialData = (.+?);<\/script>/);
return match && match[1] ? JSON.parse(match[1]) : null;
} catch (error) {
utils.warn('Failed to fetch channel data:', error);
return null;
} finally {
state.isChecking = false;
}
}
async function getChannelInfo(url) {
const data = await fetchChannel(url);
if (!data) return null;
try {
const channelName = data?.metadata?.channelMetadataRenderer?.title || "Unknown";
const channelId = data?.metadata?.channelMetadataRenderer?.externalId || null;
return { channelName, channelId };
} catch (e) {
return null;
}
}
function isChannelPageUrl(url) {
return url.includes('youtube.com/') &&
(url.includes('/channel/') || url.includes('/@')) &&
!url.includes('/video/') &&
!url.includes('/watch');
}
function checkUrlChange() {
const currentUrl = location.href;
if (currentUrl !== state.previousUrl) {
state.previousUrl = currentUrl;
if (isChannelPageUrl(currentUrl)) {
setTimeout(() => getChannelInfo(currentUrl), 500);
}
}
}
history.pushState = (function (f) {
return function () {
f.apply(this, arguments);
checkUrlChange();
};
})(history.pushState);
history.replaceState = (function (f) {
return function () {
f.apply(this, arguments);
checkUrlChange();
};
})(history.replaceState);
window.addEventListener('popstate', checkUrlChange);
setInterval(checkUrlChange, 1000);
function init() {
try {
utils.log('Initializing YouTube Enhancer v1.6');
loadFonts();
initializeLocalStorage();
addStyles();
if (state.enabled) {
observePageChanges();
addNavigationListener();
if (isChannelPageUrl(location.href)) {
getChannelInfo(location.href);
}
}
utils.log('YouTube Enhancer initialized successfully');
} catch (error) {
utils.error('Failed to initialize YouTube Enhancer:', error);
}
}
function loadFonts() {
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = FONT_LINK;
document.head.appendChild(fontLink);
}
function initializeLocalStorage() {
OPTIONS.forEach(option => {
if (localStorage.getItem(`show-${option}`) === null) {
localStorage.setItem(`show-${option}`, 'true');
}
});
}
function addStyles() {
const style = document.createElement('style');
style.textContent = `
.channel-banner-overlay{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:12px;z-index:10;display:flex;justify-content:space-around;align-items:center;color:#fff;font-family:var(--stats-font-family,'Rubik',sans-serif);font-size:var(--stats-font-size,24px);box-sizing:border-box;transition:background-color .3s ease;backdrop-filter:blur(2px)}
.settings-button{position:absolute;top:8px;right:8px;width:24px;height:24px;cursor:pointer;z-index:2;transition:transform .2s;opacity:.7}
.settings-button:hover{transform:scale(1.1);opacity:1}
.settings-menu{position:absolute;top:35px;right:8px;background:rgba(0,0,0,.95);padding:12px;border-radius:8px;z-index:10;display:none;backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.1);min-width:320px}
.settings-menu.show{display:block}
.stat-container{display:flex;flex-direction:column;align-items:center;justify-content:center;visibility:hidden;width:33%;height:100%;padding:0 1rem}
.number-container{display:flex;align-items:center;justify-content:center;font-weight:700;min-height:3rem}
.label-container{display:flex;align-items:center;margin-top:.5rem;font-size:1.2rem;opacity:.9}
.label-container svg{width:1.5rem;height:1.5rem;margin-right:.5rem}
.difference{font-size:1.8rem;height:2rem;margin-bottom:.5rem;transition:opacity .3s}
.spinner-container{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center}
.loading-spinner{animation:spin 1s linear infinite}
@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
@media(max-width:768px){.channel-banner-overlay{flex-direction:column;padding:8px;min-height:160px}.settings-menu{width:280px;right:4px}}
.setting-group{margin-bottom:12px}
.setting-group:last-child{margin-bottom:0}
.setting-group label{display:block;margin-bottom:4px;font-weight:600;color:#fff;font-size:14px}
.setting-group input[type="range"]{width:100%;margin:4px 0}
.setting-group input[type="checkbox"]{margin-right:8px}
.setting-value{color:#aaa;font-size:12px;margin-top:2px}
`;
document.head.appendChild(style);
}
function createSettingsButton() {
const button = document.createElement('div');
button.className = 'settings-button';
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("viewBox", "0 0 512 512");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("fill", "white");
path.setAttribute("d", "M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z");
svg.appendChild(path);
button.appendChild(svg);
return button;
}
function createSettingsMenu() {
const menu = document.createElement('div');
menu.className = 'settings-menu';
menu.style.gap = '15px';
menu.style.width = '360px';
const displaySection = createDisplaySection();
const controlsSection = createControlsSection();
menu.appendChild(displaySection);
menu.appendChild(controlsSection);
return menu;
}
function createDisplaySection() {
const displaySection = document.createElement('div');
displaySection.style.flex = '1';
const displayLabel = document.createElement('label');
displayLabel.textContent = 'Display Options';
displayLabel.style.marginBottom = '10px';
displayLabel.style.display = 'block';
displayLabel.style.fontSize = '16px';
displayLabel.style.fontWeight = 'bold';
displaySection.appendChild(displayLabel);
OPTIONS.forEach(option => {
const checkboxContainer = document.createElement('div');
checkboxContainer.style.display = 'flex';
checkboxContainer.style.alignItems = 'center';
checkboxContainer.style.marginTop = '5px';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `show-${option}`;
checkbox.checked = localStorage.getItem(`show-${option}`) !== 'false';
checkbox.style.marginRight = '8px';
checkbox.style.cursor = 'pointer';
const checkboxLabel = document.createElement('label');
checkboxLabel.htmlFor = `show-${option}`;
checkboxLabel.textContent = option.charAt(0).toUpperCase() + option.slice(1);
checkboxLabel.style.cursor = 'pointer';
checkboxLabel.style.color = 'white';
checkboxLabel.style.fontSize = '14px';
checkbox.addEventListener('change', () => {
localStorage.setItem(`show-${option}`, checkbox.checked);
updateDisplayState();
});
checkboxContainer.appendChild(checkbox);
checkboxContainer.appendChild(checkboxLabel);
displaySection.appendChild(checkboxContainer);
});
return displaySection;
}
function createControlsSection() {
const controlsSection = document.createElement('div');
controlsSection.style.flex = '1';
// Font family selector
const fontLabel = document.createElement('label');
fontLabel.textContent = 'Font Family';
fontLabel.style.display = 'block';
fontLabel.style.marginBottom = '5px';
fontLabel.style.fontSize = '16px';
fontLabel.style.fontWeight = 'bold';
const fontSelect = document.createElement('select');
fontSelect.className = 'font-family-select';
fontSelect.style.width = '100%';
fontSelect.style.marginBottom = '10px';
const fonts = [
{ name: 'Rubik', value: 'Rubik, sans-serif' },
{ name: 'Impact', value: 'Impact, Charcoal, sans-serif' },
{ name: 'Verdana', value: 'Verdana, Geneva, sans-serif' },
{ name: 'Tahoma', value: 'Tahoma, Geneva, sans-serif' },
];
const savedFont = localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif';
fonts.forEach(f => {
const opt = document.createElement('option');
opt.value = f.value;
opt.textContent = f.name;
if (f.value === savedFont) opt.selected = true;
fontSelect.appendChild(opt);
});
fontSelect.addEventListener('change', (e) => {
localStorage.setItem('youtubeEnhancerFontFamily', e.target.value);
if (state.overlay) {
// Only update .subscribers-number, .views-number, .videos-number
state.overlay.querySelectorAll('.subscribers-number,.views-number,.videos-number').forEach(el => {
el.style.fontFamily = e.target.value;
});
}
});
// Font size slider
const fontSizeLabel = document.createElement('label');
fontSizeLabel.textContent = 'Font Size';
fontSizeLabel.style.display = 'block';
fontSizeLabel.style.marginBottom = '5px';
fontSizeLabel.style.fontSize = '16px';
fontSizeLabel.style.fontWeight = 'bold';
const fontSizeSlider = document.createElement('input');
fontSizeSlider.type = 'range';
fontSizeSlider.min = '16';
fontSizeSlider.max = '72';
fontSizeSlider.value = localStorage.getItem('youtubeEnhancerFontSize') || '24';
fontSizeSlider.step = '1';
fontSizeSlider.className = 'font-size-slider';
const fontSizeValue = document.createElement('div');
fontSizeValue.className = 'font-size-value';
fontSizeValue.textContent = `${fontSizeSlider.value}px`;
fontSizeValue.style.fontSize = '14px';
fontSizeValue.style.marginBottom = '15px';
fontSizeSlider.addEventListener('input', (e) => {
fontSizeValue.textContent = `${e.target.value}px`;
localStorage.setItem('youtubeEnhancerFontSize', e.target.value);
if (state.overlay) {
// Only update .subscribers-number, .views-number, .videos-number
state.overlay.querySelectorAll('.subscribers-number,.views-number,.videos-number').forEach(el => {
el.style.fontSize = `${e.target.value}px`;
});
}
});
// ...existing code...
const intervalLabel = document.createElement('label');
intervalLabel.textContent = 'Update Interval';
intervalLabel.style.display = 'block';
intervalLabel.style.marginBottom = '5px';
intervalLabel.style.fontSize = '16px';
intervalLabel.style.fontWeight = 'bold';
const intervalSlider = document.createElement('input');
intervalSlider.type = 'range';
intervalSlider.min = '2';
intervalSlider.max = '10';
intervalSlider.value = state.updateInterval / 1000;
intervalSlider.step = '1';
intervalSlider.className = 'interval-slider';
const intervalValue = document.createElement('div');
intervalValue.className = 'interval-value';
intervalValue.textContent = `${intervalSlider.value}s`;
intervalValue.style.marginBottom = '15px';
intervalValue.style.fontSize = '14px';
intervalSlider.addEventListener('input', (e) => {
const newInterval = parseInt(e.target.value) * 1000;
intervalValue.textContent = `${e.target.value}s`;
state.updateInterval = newInterval;
localStorage.setItem('youtubeEnhancerInterval', newInterval);
if (state.intervalId) {
clearInterval(state.intervalId);
state.intervalId = setInterval(() => {
updateOverlayContent(state.overlay, state.currentChannelName);
}, newInterval);
}
});
const opacityLabel = document.createElement('label');
opacityLabel.textContent = 'Background Opacity';
opacityLabel.style.display = 'block';
opacityLabel.style.marginBottom = '5px';
opacityLabel.style.fontSize = '16px';
opacityLabel.style.fontWeight = 'bold';
const opacitySlider = document.createElement('input');
opacitySlider.type = 'range';
opacitySlider.min = '50';
opacitySlider.max = '90';
opacitySlider.value = state.overlayOpacity * 100;
opacitySlider.step = '5';
opacitySlider.className = 'opacity-slider';
const opacityValue = document.createElement('div');
opacityValue.className = 'opacity-value';
opacityValue.textContent = `${opacitySlider.value}%`;
opacityValue.style.fontSize = '14px';
opacitySlider.addEventListener('input', (e) => {
const newOpacity = parseInt(e.target.value) / 100;
opacityValue.textContent = `${e.target.value}%`;
state.overlayOpacity = newOpacity;
localStorage.setItem('youtubeEnhancerOpacity', newOpacity);
if (state.overlay) {
state.overlay.style.backgroundColor = `rgba(0, 0, 0, ${newOpacity})`;
}
});
controlsSection.appendChild(fontLabel);
controlsSection.appendChild(fontSelect);
controlsSection.appendChild(fontSizeLabel);
controlsSection.appendChild(fontSizeSlider);
controlsSection.appendChild(fontSizeValue);
controlsSection.appendChild(intervalLabel);
controlsSection.appendChild(intervalSlider);
controlsSection.appendChild(intervalValue);
controlsSection.appendChild(opacityLabel);
controlsSection.appendChild(opacitySlider);
controlsSection.appendChild(opacityValue);
return controlsSection;
}
function createSpinner() {
const spinnerContainer = document.createElement('div');
spinnerContainer.style.position = 'absolute';
spinnerContainer.style.top = '0';
spinnerContainer.style.left = '0';
spinnerContainer.style.width = '100%';
spinnerContainer.style.height = '100%';
spinnerContainer.style.display = 'flex';
spinnerContainer.style.justifyContent = 'center';
spinnerContainer.style.alignItems = 'center';
spinnerContainer.classList.add('spinner-container');
const spinner = document.createElementNS("http://www.w3.org/2000/svg", "svg");
spinner.setAttribute("viewBox", "0 0 512 512");
spinner.setAttribute("width", "64");
spinner.setAttribute("height", "64");
spinner.classList.add('loading-spinner');
spinner.style.animation = "spin 1s linear infinite";
const secondaryPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
secondaryPath.setAttribute("d", "M0 256C0 114.9 114.1 .5 255.1 0C237.9 .5 224 14.6 224 32c0 17.7 14.3 32 32 32C150 64 64 150 64 256s86 192 192 192c69.7 0 130.7-37.1 164.5-92.6c-3 6.6-3.3 14.8-1 22.2c1.2 3.7 3 7.2 5.4 10.3c1.2 1.5 2.6 3 4.1 4.3c.8 .7 1.6 1.3 2.4 1.9c.4 .3 .8 .6 1.3 .9s.9 .6 1.3 .8c5 2.9 10.6 4.3 16 4.3c11 0 21.8-5.7 27.7-16c-44.3 76.5-127 128-221.7 128C114.6 512 0 397.4 0 256z");
secondaryPath.style.opacity = "0.4";
secondaryPath.style.fill = "white";
const primaryPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
primaryPath.setAttribute("d",
"M224 32c0-17.7 14.3-32 32-32C397.4 0 512 114.6 512 256c0 46.6-12.5 90.4-34.3 128c-8.8 15.3-28.4 20.5-43.7 11.7s-20.5-28.4-11.7-43.7c16.3-28.2 25.7-61 25.7-96c0-106-86-192-192-192c-17.7 0-32-14.3-32-32z");
primaryPath.style.fill = "white";
spinner.appendChild(secondaryPath);
spinner.appendChild(primaryPath);
spinnerContainer.appendChild(spinner);
return spinnerContainer;
}
function createSVGIcon(path) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 640 512");
svg.setAttribute("width", "2rem");
svg.setAttribute("height", "2rem");
svg.style.marginRight = "0.5rem";
svg.style.display = "none";
const svgPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
svgPath.setAttribute("d", path);
svgPath.setAttribute("fill", "white");
svg.appendChild(svgPath);
return svg;
}
function createStatContainer(className, iconPath) {
const container = document.createElement('div');
Object.assign(container.style, {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
visibility: 'hidden',
width: '33%',
height: '100%',
padding: '0 1rem'
});
const numberContainer = document.createElement('div');
Object.assign(numberContainer.style, {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
});
const differenceElement = document.createElement('div');
differenceElement.classList.add(`${className}-difference`);
Object.assign(differenceElement.style, {
fontSize: '2.5rem',
height: '2.5rem',
marginBottom: '1rem'
});
const digitContainer = createNumberContainer();
digitContainer.classList.add(`${className}-number`);
Object.assign(digitContainer.style, {
fontSize: (localStorage.getItem('youtubeEnhancerFontSize') || '24') + 'px',
fontWeight: 'bold',
lineHeight: '1',
height: '4rem',
fontFamily: localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif',
letterSpacing: '0.025em'
});
numberContainer.appendChild(differenceElement);
numberContainer.appendChild(digitContainer);
const labelContainer = document.createElement('div');
Object.assign(labelContainer.style, {
display: 'flex',
alignItems: 'center',
marginTop: '0.5rem'
});
const icon = createSVGIcon(iconPath);
Object.assign(icon.style, {
width: '2rem',
height: '2rem',
marginRight: '0.75rem'
});
const labelElement = document.createElement('div');
labelElement.classList.add(`${className}-label`);
labelElement.style.fontSize = '2rem';
labelContainer.appendChild(icon);
labelContainer.appendChild(labelElement);
container.appendChild(numberContainer);
container.appendChild(labelContainer);
return container;
}
function createOverlay(bannerElement) {
clearExistingOverlay();
if (!bannerElement) return null;
const overlay = document.createElement('div');
overlay.classList.add('channel-banner-overlay');
Object.assign(overlay.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
backgroundColor: `rgba(0, 0, 0, ${state.overlayOpacity})`,
borderRadius: '15px',
zIndex: '10',
display: 'flex',
justifyContent: 'space-around',
alignItems: 'center',
color: 'white',
fontFamily: localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif',
fontSize: (localStorage.getItem('youtubeEnhancerFontSize') || '24') + 'px',
boxSizing: 'border-box',
transition: 'background-color 0.3s ease'
});
// Accessibility attributes
overlay.setAttribute('role', 'region');
overlay.setAttribute('aria-label', 'YouTube Channel Statistics Overlay');
overlay.setAttribute('tabindex', '-1');
// Responsive design for mobile
if (window.innerWidth <= 768) {
overlay.style.flexDirection = 'column';
overlay.style.padding = '10px';
overlay.style.minHeight = '200px';
}
const settingsButton = createSettingsButton();
settingsButton.setAttribute('tabindex', '0');
settingsButton.setAttribute('aria-label', 'Open settings menu');
settingsButton.setAttribute('role', 'button');
const settingsMenu = createSettingsMenu();
settingsMenu.setAttribute('aria-label', 'Statistics display settings');
settingsMenu.setAttribute('role', 'dialog');
overlay.appendChild(settingsButton);
overlay.appendChild(settingsMenu);
// Enhanced event handling with keyboard support
const toggleMenu = (show) => {
settingsMenu.classList.toggle('show', show);
settingsButton.setAttribute('aria-expanded', show);
if (show) {
settingsMenu.focus();
}
};
settingsButton.addEventListener('click', (e) => {
e.stopPropagation();
toggleMenu(!settingsMenu.classList.contains('show'));
});
settingsButton.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleMenu(!settingsMenu.classList.contains('show'));
}
});
// Close menu when clicking outside or pressing escape
document.addEventListener('click', (e) => {
if (!settingsMenu.contains(e.target) && !settingsButton.contains(e.target)) {
toggleMenu(false);
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && settingsMenu.classList.contains('show')) {
toggleMenu(false);
settingsButton.focus();
}
});
const spinner = createSpinner();
overlay.appendChild(spinner);
const subscribersElement = createStatContainer('subscribers', "M144 160c-44.2 0-80-35.8-80-80S99.8 0 144 0s80 35.8 80 80s-35.8 80-80 80zm368 0c-44.2 0-80-35.8-80-80s35.8-80 80-80s80 35.8 80 80s-35.8 80-80 80zM0 298.7C0 239.8 47.8 192 106.7 192h42.7c15.9 0 31 3.5 44.6 9.7c-1.3 7.2-1.9 14.7-1.9 22.3c0 38.2 16.8 72.5 43.3 96c-.2 0-.4 0-.7 0H21.3C9.6 320 0 310.4 0 298.7zM405.3 320c-.2 0-.4 0-.7 0c26.6-23.5 43.3-57.8 43.3-96c0-7.6-.7-15-1.9-22.3c13.6-6.3 28.7-9.7 44.6-9.7h42.7C592.2 192 640 239.8 640 298.7c0 11.8-9.6 21.3-21.3 21.3H405.3zM416 224c0 53-43 96-96 96s-96-43-96-96s43-96 96-96s96 43 96 96zM128 485.3C128 411.7 187.7 352 261.3 352H378.7C452.3 352 512 411.7 512 485.3c0 14.7-11.9 26.7-26.7 26.7H154.7c-14.7 0-26.7-11.9-26.7-26.7z");
const viewsElement = createStatContainer('views', "M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z");
const videosElement = createStatContainer('videos', "M0 128C0 92.7 28.7 64 64 64H320c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128zM559.1 99.8c10.4 5.6 16.9 16.4 16.9 28.2V384c0 11.8-6.5 22.6-16.9 28.2s-23 5-32.9-1.6l-96-64L416 337.1V320 192 174.9l14.2-9.5 96-64c9.8-6.5 22.4-7.2 32.9-1.6z");
overlay.appendChild(subscribersElement);
overlay.appendChild(viewsElement);
overlay.appendChild(videosElement);
bannerElement.appendChild(overlay);
updateDisplayState();
return overlay;
}
function fetchWithGM(url, headers = {}) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: headers,
onload: function (response) {
if (response.status === 200) {
resolve(JSON.parse(response.responseText));
} else {
reject(new Error(`Failed to fetch: ${response.status}`));
}
},
onerror: function (error) {
reject(error);
},
});
});
}
async function fetchChannelId(channelName) {
// Try meta tag first
const metaTag = document.querySelector('meta[itemprop="channelId"]');
if (metaTag && metaTag.content) return metaTag.content;
// Try URL pattern
const urlMatch = window.location.href.match(/channel\/(UC[\w-]+)/);
if (urlMatch && urlMatch[1]) return urlMatch[1];
// Try ytInitialData
const channelInfo = await getChannelInfo(window.location.href);
if (channelInfo && channelInfo.channelId) return channelInfo.channelId;
throw new Error('Could not determine channel ID');
}
async function fetchChannelStats(channelId) {
try {
let retries = CONFIG.MAX_RETRIES;
let lastError;
while (retries > 0) {
try {
const stats = await fetchWithGM(
`${STATS_API_URL}${channelId}`,
{
origin: "https://livecounts.io",
referer: "https://livecounts.io/",
}
);
// Validate response structure
if (!stats || typeof stats.followerCount === 'undefined') {
throw new Error('Invalid stats response structure');
}
// Cache successful response
state.lastSuccessfulStats.set(channelId, {
...stats,
timestamp: Date.now()
});
return stats;
} catch (e) {
lastError = e;
retries--;
if (retries > 0) {
// Exponential backoff for retries
await new Promise(resolve => setTimeout(resolve, 1000 * (CONFIG.MAX_RETRIES - retries + 1)));
}
}
}
// Try to use cached data if available and recent (within 5 minutes)
if (state.lastSuccessfulStats.has(channelId)) {
const cached = state.lastSuccessfulStats.get(channelId);
const isRecent = Date.now() - cached.timestamp < CONFIG.CACHE_DURATION;
if (isRecent) {
utils.log('Using cached stats for channel:', channelId);
return cached;
}
}
// Fallback: try to extract subscriber count from page
const fallbackStats = {
followerCount: 0,
bottomOdos: [0, 0],
error: true,
timestamp: Date.now()
};
// Try multiple selectors for subscriber count
const subCountSelectors = [
'#subscriber-count',
'.yt-subscription-button-subscriber-count-branded-horizontal',
'[id*="subscriber"]',
'.ytd-subscribe-button-renderer'
];
for (const selector of subCountSelectors) {
const subCountElem = document.querySelector(selector);
if (subCountElem) {
const subText = subCountElem.textContent || subCountElem.innerText || '';
const subMatch = subText.match(/[\d,\.]+[KMB]?/);
if (subMatch) {
let count = subMatch[0].replace(/,/g, '');
// Handle K, M, B suffixes
if (count.includes('K')) {
count = parseFloat(count) * 1000;
} else if (count.includes('M')) {
count = parseFloat(count) * 1000000;
} else if (count.includes('B')) {
count = parseFloat(count) * 1000000000;
}
fallbackStats.followerCount = Math.floor(count);
utils.log('Extracted fallback subscriber count:', fallbackStats.followerCount);
break;
}
}
}
return fallbackStats;
} catch (error) {
utils.error('Failed to fetch channel stats:', error);
return {
followerCount: 0,
bottomOdos: [0, 0],
error: true,
timestamp: Date.now()
};
}
}
function clearExistingOverlay() {
const existingOverlay = document.querySelector('.channel-banner-overlay');
if (existingOverlay) {
existingOverlay.remove();
}
if (state.intervalId) {
clearInterval(state.intervalId);
state.intervalId = null;
}
state.lastSuccessfulStats.clear();
state.previousStats.clear();
state.isUpdating = false;
state.overlay = null;
utils.log('Cleared existing overlay');
}
function createDigitElement() {
const digit = document.createElement('span');
Object.assign(digit.style, {
display: 'inline-block',
width: '0.6em',
textAlign: 'center',
marginRight: '0.025em',
marginLeft: '0.025em'
});
return digit;
}
function createCommaElement() {
const comma = document.createElement('span');
comma.textContent = ',';
Object.assign(comma.style, {
display: 'inline-block',
width: '0.3em',
textAlign: 'center'
});
return comma;
}
function createNumberContainer() {
const container = document.createElement('div');
Object.assign(container.style, {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
letterSpacing: '0.025em'
});
return container;
}
function updateDigits(container, newValue) {
const newValueStr = newValue.toString();
const digits = [];
for (let i = newValueStr.length - 1; i >= 0; i -= 3) {
const start = Math.max(0, i - 2);
digits.unshift(newValueStr.slice(start, i + 1));
}
while (container.firstChild) {
container.removeChild(container.firstChild);
}
let digitIndex = 0;
for (let i = 0; i < digits.length; i++) {
const group = digits[i];
for (let j = 0; j < group.length; j++) {
const digitElement = createDigitElement();
digitElement.textContent = group[j];
container.appendChild(digitElement);
digitIndex++;
}
if (i < digits.length - 1) {
container.appendChild(createCommaElement());
}
}
let elementIndex = 0;
for (let i = 0; i < digits.length; i++) {
const group = digits[i];
for (let j = 0; j < group.length; j++) {
const digitElement = container.children[elementIndex];
const newDigit = parseInt(group[j]);
const currentDigit = parseInt(digitElement.textContent || '0');
if (currentDigit !== newDigit) {
animateDigit(digitElement, currentDigit, newDigit);
}
elementIndex++;
}
if (i < digits.length - 1) {
elementIndex++;
}
}
}
function animateDigit(element, start, end) {
const duration = 1000;
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
const current = Math.round(start + (end - start) * easeOutQuart);
element.textContent = current;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
function showContent(overlay) {
const spinnerContainer = overlay.querySelector('.spinner-container');
if (spinnerContainer) {
spinnerContainer.remove();
}
const containers = overlay.querySelectorAll('div[style*="visibility: hidden"]');
containers.forEach(container => {
container.style.visibility = 'visible';
});
const icons = overlay.querySelectorAll('svg[style*="display: none"]');
icons.forEach(icon => {
icon.style.display = 'block';
});
}
function updateDifferenceElement(element, currentValue, previousValue) {
if (!previousValue) return;
const difference = currentValue - previousValue;
if (difference === 0) {
element.textContent = '';
return;
}
const sign = difference > 0 ? '+' : '';
element.textContent = `${sign}${difference.toLocaleString()}`;
element.style.color = difference > 0 ? '#1ed760' : '#f3727f';
setTimeout(() => {
element.textContent = '';
}, 1000);
}
function updateDisplayState() {
const overlay = document.querySelector('.channel-banner-overlay');
if (!overlay) return;
const statContainers = overlay.querySelectorAll('div[style*="width"]');
if (!statContainers.length) return;
let visibleCount = 0;
const visibleContainers = [];
statContainers.forEach(container => {
const numberContainer = container.querySelector('[class$="-number"]');
if (!numberContainer) return;
const type = numberContainer.className.replace('-number', '');
const isVisible = localStorage.getItem(`show-${type}`) !== 'false';
if (isVisible) {
container.style.display = 'flex';
visibleCount++;
visibleContainers.push(container);
} else {
container.style.display = 'none';
}
});
visibleContainers.forEach(container => {
container.style.width = '';
container.style.margin = '';
switch (visibleCount) {
case 1:
container.style.width = '100%';
break;
case 2:
container.style.width = '50%';
break;
case 3:
container.style.width = '33.33%';
break;
default:
container.style.display = 'none';
}
});
// Only update font size and font family for .subscribers-number, .views-number, .videos-number
const fontSize = localStorage.getItem('youtubeEnhancerFontSize') || '24';
const fontFamily = localStorage.getItem('youtubeEnhancerFontFamily') || 'Rubik, sans-serif';
overlay.querySelectorAll('.subscribers-number,.views-number,.videos-number').forEach(el => {
el.style.fontSize = `${fontSize}px`;
el.style.fontFamily = fontFamily;
});
overlay.style.display = 'flex';
}
async function updateOverlayContent(overlay, channelName) {
if (state.isUpdating || channelName !== state.currentChannelName) return;
state.isUpdating = true;
try {
const channelId = await fetchChannelId(channelName);
const stats = await fetchChannelStats(channelId);
// Check if channel changed during async operations
if (channelName !== state.currentChannelName) {
state.isUpdating = false;
return;
}
if (stats.error) {
const containers = overlay.querySelectorAll('[class$="-number"]');
containers.forEach(container => {
if (container.classList.contains('subscribers-number') && stats.followerCount > 0) {
updateDigits(container, stats.followerCount);
} else {
container.textContent = '---';
}
});
utils.warn('Using fallback stats due to API error');
return;
}
const updateElement = (className, value, label) => {
const numberContainer = overlay.querySelector(`.${className}-number`);
const differenceElement = overlay.querySelector(`.${className}-difference`);
const labelElement = overlay.querySelector(`.${className}-label`);
if (numberContainer) {
updateDigits(numberContainer, value);
}
if (differenceElement && state.previousStats.has(channelId)) {
const previousValue = className === 'subscribers' ?
state.previousStats.get(channelId).followerCount :
state.previousStats.get(channelId).bottomOdos[className === 'views' ? 0 : 1];
updateDifferenceElement(differenceElement, value, previousValue);
}
if (labelElement) {
labelElement.textContent = label;
}
};
updateElement('subscribers', stats.followerCount, 'Subscribers');
updateElement('views', stats.bottomOdos[0], 'Views');
updateElement('videos', stats.bottomOdos[1], 'Videos');
if (!state.previousStats.has(channelId)) {
showContent(overlay);
utils.log('Displayed initial stats for channel:', channelName);
}
state.previousStats.set(channelId, stats);
} catch (error) {
utils.error('Failed to update overlay content:', error);
const containers = overlay.querySelectorAll('[class$="-number"]');
containers.forEach(container => {
container.textContent = '---';
});
} finally {
state.isUpdating = false;
}
}
// Add settings UI to experimental section
function addSettingsUI() {
const section = document.querySelector('.ytp-plus-settings-section[data-section="experimental"]');
if (!section || section.querySelector('.count-settings-item')) return;
const item = document.createElement('div');
item.className = 'ytp-plus-settings-item count-settings-item';
item.innerHTML = `
<div>
<label class="ytp-plus-settings-item-label">Channel Stats</label>
<div class="ytp-plus-settings-item-description">Show live subscriber/views/videos overlay on channel banner</div>
</div>
<input type="checkbox" class="ytp-plus-settings-checkbox" ${state.enabled ? 'checked' : ''}>
`;
section.appendChild(item);
item.querySelector('input').addEventListener('change', e => {
state.enabled = e.target.checked;
localStorage.setItem(CONFIG.STORAGE_KEY, state.enabled ? 'true' : 'false');
if (!state.enabled) {
clearExistingOverlay();
} else {
observePageChanges();
addNavigationListener();
setTimeout(() => {
const bannerElement = document.getElementById('page-header-banner-sizer');
if (bannerElement && isChannelPage()) {
addOverlay(bannerElement);
}
}, 100);
}
});
}
// Observe settings modal for experimental section
const settingsObserver = new MutationObserver(mutations => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.classList?.contains('ytp-plus-settings-modal')) {
setTimeout(addSettingsUI, 100);
return;
}
}
}
if (document.querySelector('.ytp-plus-settings-nav-item[data-section="experimental"].active')) {
setTimeout(addSettingsUI, 50);
}
});
settingsObserver.observe(document.body, { childList: true, subtree: true });
document.addEventListener('click', e => {
if (e.target.classList?.contains('ytp-plus-settings-nav-item') &&
e.target.dataset.section === 'experimental') {
setTimeout(addSettingsUI, 50);
}
}, true);
function addOverlay(bannerElement) {
// Improved channel name extraction with better URL parsing
let channelName = null;
const pathname = window.location.pathname;
if (pathname.startsWith('/@')) {
channelName = pathname.split('/')[1].replace('@', '');
} else if (pathname.startsWith('/channel/')) {
channelName = pathname.split('/')[2];
} else if (pathname.startsWith('/c/')) {
channelName = pathname.split('/')[2];
} else if (pathname.startsWith('/user/')) {
channelName = pathname.split('/')[2];
}
// Skip if no valid channel name or already processing the same channel
if (!channelName || (channelName === state.currentChannelName && state.overlay)) {
return;
}
// Ensure banner element is properly positioned
if (bannerElement && !bannerElement.style.position) {
bannerElement.style.position = 'relative';
}
state.currentChannelName = channelName;
state.overlay = createOverlay(bannerElement);
if (state.overlay) {
// Clear existing interval
if (state.intervalId) {
clearInterval(state.intervalId);
state.intervalId = null;
}
// Debounced update function for better performance
let lastUpdateTime = 0;
const debouncedUpdate = () => {
const now = Date.now();
if (now - lastUpdateTime >= state.updateInterval - 100) {
updateOverlayContent(state.overlay, channelName);
lastUpdateTime = now;
}
};
// Set up interval with debouncing
state.intervalId = setInterval(debouncedUpdate, state.updateInterval);
// Initial update
updateOverlayContent(state.overlay, channelName);
utils.log('Added overlay for channel:', channelName);
}
}
function isChannelPage() {
return window.location.pathname.startsWith("/@") ||
window.location.pathname.startsWith("/channel/") ||
window.location.pathname.startsWith("/c/");
}
function observePageChanges() {
if (!state.enabled) return;
// More robust banner detection with multiple fallback selectors
const observer = new MutationObserver((mutations) => {
// Throttle observations for better performance
clearTimeout(observer._timeout);
observer._timeout = setTimeout(() => {
let bannerElement = document.getElementById('page-header-banner-sizer');
// Try alternative selectors if main one fails
if (!bannerElement) {
const alternatives = [
'[id*="banner"]',
'.ytd-c4-tabbed-header-renderer',
'#channel-header',
'.channel-header'
];
for (const selector of alternatives) {
bannerElement = document.querySelector(selector);
if (bannerElement) break;
}
}
if (bannerElement && isChannelPage()) {
// Ensure banner has proper positioning
if (bannerElement.style.position !== 'relative') {
bannerElement.style.position = 'relative';
}
addOverlay(bannerElement);
} else if (!isChannelPage()) {
// Clean up when not on channel page
clearExistingOverlay();
state.currentChannelName = null;
}
}, 100); // Small delay to batch rapid changes
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false // Reduce observation scope for performance
});
// Store timeout reference for cleanup
observer._timeout = null;
return observer;
}
function addNavigationListener() {
if (!state.enabled) return;
window.addEventListener("yt-navigate-finish", () => {
if (!isChannelPage()) {
clearExistingOverlay();
state.currentChannelName = null;
utils.log('Navigated away from channel page');
} else {
const bannerElement = document.getElementById('page-header-banner-sizer');
if (bannerElement) {
addOverlay(bannerElement);
utils.log('Navigated to channel page');
}
}
});
}
init();
})();
})();