// ==UserScript== // @name Tabview Youtube // @name:en Tabview Youtube // @description Make comments and lists into tabs for YouTube Videos // @description:en Make comments and lists into tabs for YouTube Videos // @name:ja Tabview Youtube // @description:ja YouTube動画のコメントやリストなどをタブに作成します // @name:ko Tabview Youtube // @description:ko YouTube 동영상의 댓글 및 목록을 탭으로 만듭니다 // @name:zh-TW Tabview Youtube // @name:zh-HK Tabview Youtube // @name:zh-CN Tabview Youtube // @description:zh-TW 把Youtube Videos中的評論及影片清單製作成Tabs // @description:zh-HK 把Youtube Videos中的評論及影片清單製作成Tabs // @description:zh-CN 把Youtube Videos中的评论及视频列表制作成Tabs // @name:ru Tabview Youtube // @description:ru Сделайте описание, комментарии и список видео в виде вкладок для видео на YouTube // @name:af Tabview Youtube // @name:az Tabview Youtube // @name:id Tabview Youtube // @name:ms Tabview Youtube // @name:bs Tabview Youtube // @name:ca Tabview Youtube // @name:cs Tabview Youtube // @name:da Tabview Youtube // @name:de Tabview Youtube // @name:et Tabview Youtube // @name:es Tabview Youtube // @name:eu Tabview Youtube // @name:fr Tabview Youtube // @name:gl Tabview Youtube // @name:hr Tabview Youtube // @name:zu Tabview Youtube // @name:is Tabview Youtube // @name:it Tabview Youtube // @name:sw Tabview Youtube // @name:lv Tabview Youtube // @name:lt Tabview Youtube // @name:hu Tabview Youtube // @name:nl Tabview Youtube // @name:uz Tabview Youtube // @name:pl Tabview Youtube // @name:pt Tabview Youtube // @name:pt-BR Tabview Youtube // @name:ro Tabview Youtube // @name:sq Tabview Youtube // @name:sk Tabview Youtube // @name:sl Tabview Youtube // @name:sr Tabview Youtube // @name:fi Tabview Youtube // @name:sv Tabview Youtube // @name:vi Tabview Youtube // @name:tr Tabview Youtube // @name:be Tabview Youtube // @name:bg Tabview Youtube // @name:ky Tabview Youtube // @name:kk Tabview Youtube // @name:mk Tabview Youtube // @name:mn Tabview Youtube // @name:uk Tabview Youtube // @name:el Tabview Youtube // @name:hy Tabview Youtube // @name:ur Tabview Youtube // @name:ar Tabview Youtube // @name:fa Tabview Youtube // @name:ne Tabview Youtube // @name:mr Tabview Youtube // @name:hi Tabview Youtube // @name:as Tabview Youtube // @name:bn Tabview Youtube // @name:pa Tabview Youtube // @name:gu Tabview Youtube // @name:or Tabview Youtube // @name:ta Tabview Youtube // @name:te Tabview Youtube // @name:kn Tabview Youtube // @name:ml Tabview Youtube // @name:si Tabview Youtube // @name:th Tabview Youtube // @name:lo Tabview Youtube // @name:my Tabview Youtube // @name:ka Tabview Youtube // @name:am Tabview Youtube // @name:km Tabview Youtube // @description:af Maak kommentaar en lyste as oortjies vir YouTube-video's // @description:az YouTube Videoları üçün şərhləri və siyahıları tablara çevirin // @description:id Ubah komentar dan daftar menjadi tab untuk Video YouTube // @description:ms Ubah komen dan senarai menjadi tab untuk Video YouTube // @description:bs Pretvorite komentare i liste u kartice za YouTube videozapise // @description:ca Converteix comentaris i llistes en pestanyes per a Vídeos de YouTube // @description:cs Převeďte komentáře a seznamy na karty pro YouTube videa // @description:da Lav kommentarer og lister til faner for YouTube-videoer // @description:de Machen Sie Kommentare und Listen zu Tabs für YouTube-Videos // @description:et Muutke kommentaarid ja loendid YouTube'i videote jaoks kaartideks // @description:es Convierte los comentarios y listas en pestañas para los Videos de YouTube // @description:eu Egin iruzkinak eta zerrendak YouTube Bideoetarako fitxetan // @description:fr Transformez les commentaires et les listes en onglets pour les vidéos YouTube // @description:gl Converte comentarios e listas en lapelas para Vídeos de YouTube // @description:hr Pretvorite komentare i popise u kartice za YouTube videe // @description:zu Yenza ukubhala phansi kanye nemingcele ukuba yiithebhu kuVidiyo ze-YouTube // @description:is Breyttu athugasemdum og listum í flipa fyrir YouTube myndbönd // @description:it Trasforma commenti e liste in schede per i Video di YouTube // @description:sw Geuza maoni na orodha kuwa vichupo kwa Video za YouTube // @description:lv Pārveidojiet komentārus un sarakstus cilnēs YouTube video // @description:lt Paverčia komentarus ir sąrašus skirtukais YouTube vaizdo įrašams // @description:hu Alakítsa át a megjegyzéseket és listákat fülekké a YouTube videókhoz // @description:nl Maak van reacties en lijsten tabs voor YouTube-video's // @description:uz YouTube videolar uchun sharhlar va ro'yxatlarni ichki oynalar qiling // @description:pl Przekształć komentarze i listy w karty dla filmów na YouTube // @description:pt Transforme comentários e listas em abas para Vídeos do YouTube // @description:pt-BR Transforme comentários e listas em abas para Vídeos do YouTube // @description:ro Transformă comentariile și listele în file pentru Videoclipuri YouTube // @description:sq Kthe komentet dhe listat në skeda për Videot në YouTube // @description:sk Premente komentáre a zoznamy na karty pre YouTube videá // @description:sl Pretvori komentarje in sezname v zavihke za YouTube videe // @description:sr Pretvorite komentare i liste u kartice za YouTube videe // @description:fi Muuta kommentit ja luettelot välilehdiksi YouTube-videoille // @description:sv Gör kommentarer och listor till flikar för YouTube-videor // @description:vi Chuyển đổi bình luận và danh sách thành tab cho Video YouTube // @description:tr Yorumları ve listeleri YouTube Videoları için sekmelere dönüştürün // @description:be Пераўтварыце каментарыі і спісы ў закладкі для відэа на YouTube // @description:bg Превърнете коментарите и списъците в раздели за видеоклипове в YouTube // @description:ky YouTube видеолору үчүн эскертүүлөрдү жана тизмелерди табдыктарга айлантырыңыз // @description:kk Пікірлер мен тізімдерді YouTube видеолары үшін қоймақтарға айналдырыңыз // @description:mk Претворете ги коментарите и листите во јазичиња за Видеа на YouTube // @description:mn YouTube видео дэх сэтгэгдлүүд болон жагсаалтыг табчууд болгоно уу // @description:uk Зробіть коментарі та списки у вкладки для відео на YouTube // @description:el Μετατρέψτε τα σχόλια και τις λίστες σε καρτέλες για τα βίντεο του YouTube // @description:hy Վերածեք մեկնաբանությունները և ցուցակները YouTube տեսանյութերի ներդիրների // @description:ur YouTube ویڈیوز کے لئے تبصرے اور فہرستوں کو ٹیب میں تبدیل کریں // @description:ar قم بتحويل التعليقات والقوائم إلى علامات تبويب لفيديوهات YouTube // @description:fa نظرات و فهرست ها را به زبانه ها برای ویدیوهای YouTube تبدیل کنید // @description:ne YouTube भिडियोहरूका लागि प्रतिक्रिया र सूचीहरूलाई ट्याबहरूमा परिवर्तन गर्नुहोस् // @description:mr YouTube व्हिडिओसाठी टिप्पण्या आणि यादीतबांमध्ये करा // @description:hi YouTube वीडियो के लिए टिप्पणियाँ और सूचियों को टैब में बदलें // @description:as YouTube ভিডিঅ'ৰ বাবে মন্তব্য আৰু তালিকাসমূহ টেবলত পৰিণত কৰক // @description:bn YouTube ভিডিওর জন্য মন্তব্য এবং তালিকা ট্যাবে পরিণত করুন // @description:pa ਯੂਟਿਊਬ ਵੀਡੀਓਜ਼ ਲਈ ਟਿੱਪਣੀਆਂ ਅਤੇ ਸੂਚੀਆਂ ਨੂੰ ਟੈਬਾਂ ਵਿੱਚ ਬਦਲੋ // @description:gu YouTube વિડિઓ માટે ટિપ્પણીઓ અને યાદીઓ ટૅબમાં બદલો // @description:or YouTube ଭିଡିଓ ପାଇଁ ମନ୍ତବ୍ୟ ଏବଂ ତାଲିକାଗୁଡ଼ିକ ଟ୍ୟାବମାନେ ପରିବର୍ତ୍ତନ କରନ୍ତୁ // @description:ta YouTube வீடியோக்கான கருத்துக்கள் மற்றும் பட்டியல்களை தாவல்களாக மாற்றவும் // @description:te YouTube వీడియోల కోసం వ్యాఖ్యలు మరియు జాబితాలను ట్యాబ్లలుగా మార్చండి // @description:kn YouTube ವೀಡಿಯೊಗಳಿಗಾಗಿ ಟಿಪ್ಪಣಿಗಳನ್ನು ಮತ್ತು ಪಟ್ಟಿಗಳನ್ನು ಟ್ಯಾಬ್‌ಗಳಾಗಿ ಮಾಡಿ // @description:ml YouTube വീഡിയോകൾക്കായി അഭിപ്രായങ്ങളും പട്ടികകളും ടാബുകളായി മാറ്റുക // @description:si YouTube වීඩියෝ සඳහා අදහස් සහ ලැයිස්තු ටැබ් කරන්න // @description:th ทำให้ความคิดเห็นและรายการเป็นแท็บสำหรับวิดีโอ YouTube // @description:lo ປ່ຽນຄວາມເຫັນຂອງຄົນເບິ່ງແລະລາຍການເປັນແຖບສໍາລັບວິດີໂອ YouTube // @description:my YouTube ဗီဒီယိုများအတွက် မှတ်ချက်များနှင့် စာရင်းများကို Tabs အဖြစ် ပြောင်းပါ // @description:ka გადაიყვანეთ კომენტარები და სიები ჩანართებში YouTube ვიდეოებისთვის // @description:am አስተያየቶችን እና ዝርዝሮችን YouTube ቪዲዮዎች ለትርጓሜዎች ውስጥ ያስተካክሉ // @description:km បង្កើតមតិយោបល់និងបញ្ជីទៅជាផ្ទាំងសម្រាប់វីដេអូ YouTube // @version 4.73.34 // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/c5d47768f1398e1fa14acd5464180a278a0b07de/css/style_content.css // @resource chatCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/c5d47768f1398e1fa14acd5464180a278a0b07de/css/style_chat.css // @resource controlCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/c5d47768f1398e1fa14acd5464180a278a0b07de/css/style_control.css // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/c5d47768f1398e1fa14acd5464180a278a0b07de/js/injection_script_1.js // @require https://greasyfork.org/scripts/465421-vanilla-js-dialog/code/Vanilla%20JS%20Dialog.js?version=1188332 // @namespace http://tampermonkey.net/ // @author CY Fung // @license MIT // @supportURL https://github.com/cyfung1031/Tabview-Youtube // @run-at document-start // @match https://www.youtube.com/* // @exclude /^https?://\w+\.youtube\.com\/live_chat.*$/ // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/ // @icon https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/main/images/icon128p.png // @compatible edge Edge [Blink] >= 79; Tampermonkey (Beta) / Violentmonkey // @compatible chrome Chrome >= 54; Tampermonkey (Beta) / Violentmonkey // @compatible firefox FireFox / Waterfox (Classic) >= 55; Tampermonkey / Violentmonkey // @compatible opera Opera >= 41; Tampermonkey (Beta) / Violentmonkey // @compatible safari Safari >= 12.1 // @grant GM_getResourceText // @grant GM.getResourceText // @grant GM_registerMenuCommand // @noframes // @downloadURL https://update.greasyfork.icu/scripts/428651/Tabview%20Youtube.user.js // @updateURL https://update.greasyfork.icu/scripts/428651/Tabview%20Youtube.meta.js // ==/UserScript== /* MIT License Copyright (c) 2021-2024 cyfung1031 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* jshint esversion:8 */ function main(){ 'use strict'; // MIT License // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js 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 } if (typeof window === 'object') { function script3278() { 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; } } } }); // ---- << this.overscrollConfig HACK >> ----- // 2024.04.19 - Playlist in Single Column Mode cannot be scrolled correctly. /* ;function gZb(a, b) { b = void 0 === b ? !0 : b; a.addEventListener("wheel", hZb); a.overscrollConfig = { cooldown: b } } function iZb(a) { a.overscrollConfig = void 0; a.removeEventListener("wheel", hZb) } function hZb(a) { var b = a.deltaY , c = a.target , d = null; if (window.Polymer && window.Polymer.Element) { if (c = a.path || a.composedPath && a.composedPath()) { c = g(c); for (var e = c.next(); !e.done && (e = e.value, !jZb(e, b)); e = c.next()) if (e.overscrollConfig) { d = e; break } } } else for (; c && !jZb(c, b); ) { if (c.overscrollConfig) { d = c; break } c = c.parentElement } d && (b = d.overscrollConfig, b.cooldown ? (d = a.deltaY, c = b.lastDeltaY || 0, b.lastDeltaY = d, e = b.lastStopped || 0, c && e && 0 < c == 0 < d ? Math.abs(c) >= Math.abs(d) ? (d = e + 1200, c = !1) : (d = e + 600, c = !0) : (d = Date.now() + 600, c = !0), d > Date.now() && (a.preventDefault(), c && (b.lastStopped = Date.now()))) : a.preventDefault()) } */ const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0); // const indr = o => insp(o).$ || o.$ || 0; // let [YouTube JS Engine Tamer] to hack the addEventListener first new Promise(resolve => { document.addEventListener('yt-action', resolve, { once: true, capture: true }); }).then(() => { if (typeof EventTarget.prototype.addEventListener52178 !== 'function' && typeof EventTarget.prototype.addEventListener === 'function') { EventTarget.prototype.addEventListener52178 = EventTarget.prototype.addEventListener EventTarget.prototype.addEventListener = function (type, callback, option = void 0) { // M-tabview-youtube.52178 if (type === 'wheel' && !option && typeof callback === 'function' && this.overscrollConfigDisable === void 0) { try { this.overscrollConfigDisable = true; delete this.overscrollConfig; Object.defineProperty(this, 'overscrollConfig', { get() { return undefined }, set(nv) { return true }, configurable: true, enumerable: false }); const cnt = insp(this); if (cnt !== this) { delete cnt.overscrollConfig; Object.defineProperty(cnt, 'overscrollConfig', { get() { return undefined }, set(nv) { return true }, configurable: true, enumerable: false }); } } catch (e) { } } return this.addEventListener52178(type, callback, option); } // Object.defineProperty( HTMLElement.prototype, 'overscrollConfig' , {get(){return undefined}, set(nv){return true}, configurable: true, enumerable: false}) } }); // ---- << this.overscrollConfig HACK >> ----- } let mbutton = document.createElement('button'); mbutton.setAttribute('onclick', `(${script3278})()`); mbutton.click(); } -(function (__CONTEXT__) { 'use strict'; let __Promise__; try { __Promise__ = (async () => { })().constructor; // due to YouTube's Promise Hack } catch (e) { throw 'Please update your browser to use Tabview Youtube.'; } const fxOperator = (proto, propertyName) => { let propertyDescriptorGetter = null; try { propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get; } catch (e) { } return typeof propertyDescriptorGetter === 'function' ? (e) => propertyDescriptorGetter.call(e) : (e) => e[propertyName]; }; const fxAPI = (proto, propertyName) => { const methodFunc = proto[propertyName]; return typeof methodFunc === 'function' ? (e, ...args) => methodFunc.apply(e, args) : (e, ...args) => e[propertyName](...args); }; const nodeParent = fxOperator(Node.prototype, 'parentNode'); const nodeFirstChild = fxOperator(Node.prototype, 'firstChild'); const nodeNextSibling = fxOperator(Node.prototype, 'nextSibling'); const nodePrevSibling = fxOperator(Node.prototype, 'previousSibling'); // const elementQS = fxAPI(Element.prototype, 'querySelector'); // const elementQSA = fxAPI(Element.prototype, 'querySelectorAll'); const elementNextSibling = fxOperator(Element.prototype, 'nextElementSibling'); // const elementPrevSibling = fxOperator(Element.prototype, 'previousElementSibling'); const docFragmentCreate = Document.prototype.createDocumentFragment; const docFragmentAppend = DocumentFragment.prototype.append; // const docFragmentPrepend = DocumentFragment.prototype.prepend; /** @type {PromiseConstructor} */ const Promise = __Promise__; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve. const { requestAnimationFrame, cancelAnimationFrame } = __CONTEXT__; let _rafPromise = null; const getRAFPromise = () => _rafPromise || (_rafPromise = new Promise(resolve => { requestAnimationFrame(hRes => { _rafPromise = null; resolve(hRes); }); })); function inIframe() { try { return window.self !== window.top; } catch (e) { return true; } } if (inIframe()) return; if (document.documentElement && document.documentElement.hasAttribute('plugin-tabview-youtube')) { console.warn('Multiple instances of Tabview Youtube is attached. [0x7F01]') return; } const createPipeline = () => { let pipelineMutex = Promise.resolve(); const pipelineExecution = fn => { return new Promise((resolve, reject) => { pipelineMutex = pipelineMutex.then(async () => { let res; try { res = await fn(); } catch (e) { console.log('error_F1', e); reject(e); } resolve(res); }).catch(console.warn); }); }; return pipelineExecution; }; const iframePipeline = createPipeline(); /* const observablePromise = (proc, timeoutPromise) => { let promise = null; return { obtain() { if (!promise) { promise = new Promise(resolve => { let mo = null; const f = () => { let t = proc(); if (t) { mo.disconnect(); mo.takeRecords(); mo = null; resolve(t); } } mo = new MutationObserver(f); mo.observe(document, { subtree: true, childList: true }) f(); timeoutPromise && timeoutPromise.then(() => { resolve(null) }); }); } return promise } } } */ //if (!$) return; /** * SVG resources: *
Icons made by Smashicons from www.flaticon.com
*/ const scriptVersionForExternal = '2022/12/04'; const isMyScriptInChromeRuntime = () => typeof GM === 'undefined' && typeof ((((window || 0).chrome || 0).runtime || 0).getURL) === 'function'; const isGMAvailable = () => typeof GM !== 'undefined' && !isMyScriptInChromeRuntime(); // https://yqnn.github.io/svg-path-editor/ // https://vecta.io/nano const svgComments = ``.trim(); const svgVideos = ``.trim(); const svgInfo = ``.trim(); const svgPlayList = ``.trim(); const svgDiag1 = ``; const svgDiag2 = ``; const REMOVE_DUPLICATE_INFO = true; const REMOVE_DUPLICATE_META_RECOMMENDATION = true; /* https://www.youtube.com/watch?v=kGihxscQCPE */ const MINIVIEW_BROWSER_ENABLE = true; const DEBUG_LOG = false; /* youtube page = Init::browse yt-page-data-fetched data-changed... yt-page-data-updated yt-navigate-finish data-changed... yt-watch-comments-ready = browse -> watch yt-player-updated yt-navigate yt-navigate-start yt-page-type-changed yt-player-updated yt-page-data-fetched yt-navigate-finish data-changed... yt-page-data-updated data-changed... yt-watch-comments-ready data-changed... = watch -> watch = click video on meta panel // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo yt-navigate yt-navigate-start data-changed yt-player-updated yt-page-data-fetched yt-navigate-finish data-changed... yt-page-data-updated data-changed... yt-watch-comments-ready data-changed... = watch -> browse (miniview) yt-navigate-cache yt-page-data-fetched yt-page-type-changed yt-page-data-updated yt-navigate-finish = browse (miniview) -> watch (Restore) yt-navigate-cache yt-page-data-fetched yt-navigate-finish yt-page-type-changed yt-page-data-updated data-changed... yt-watch-comments-ready = watch -> search (miniview) yt-navigate yt-navigate-start data-changed yt-page-data-fetched yt-page-type-changed data-changed yt-page-data-updated yt-navigate-finish data-changed... = Init::search yt-page-data-fetched data-changed yt-page-data-updated yt-navigate-finish data-changed... yt-watch-comments-ready = Init::watch yt-page-data-fetched yt-navigate-finish data-changed... yt-page-data-updated data-changed... yt-watch-comments-ready yt-player-updated data-changed... = watch -> watch (history back) yt-player-updated yt-page-data-fetched yt-navigate-finish data-changed... yt-page-data-updated data-changed... yt-watch-comments-ready = watch -> click video time // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo yt-navigate */ function generateRandomID() { return Math.floor(Math.random() * 982451653 + 982451653).toString(36); } function generateRandomTimedID() { return `${generateRandomID()}-${Date.now().toString(36)}`; } const instanceId = generateRandomTimedID(); const LAYOUT_VAILD = 1; const LAYOUT_TWO_COLUMNS = 2; const LAYOUT_THEATER = 4; const LAYOUT_FULLSCREEN = 8; const LAYOUT_CHATROOM = 16; const LAYOUT_CHATROOM_COLLAPSED = 32; const LAYOUT_TAB_EXPANDED = 64; const LAYOUT_ENGAGEMENT_PANEL_EXPANDED = 128; const LAYOUT_CHATROOM_EXPANDED = 256; const LAYOUT_DONATION_SHELF_EXPANDED = 512; const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const showMessages_IframeLoaded = false; // typeof GM === 'undefined'; const nullFunc = function () { }; let scriptEnable = false; let comments_loader = 0; // for comment count (might omit) let cmTime = 0; const mTime = Date.now() - 152000000; //let lastScrollFetch = 0; //let lastOffsetTop = 0; let mtf_forceCheckLiveVideo_disable = 0; let tabsUiScript_setclick = false; let pageFetchedDataVideoId = null; // integer; for comment checking let pageType = null; // pageType = 'watch', 'browse', 'playlist', ... let chatroomDetails = null; let switchTabActivity_lastTab = null; let lstTab = null; let storeLastPanel = null; // WeakRef let mtf_chatBlockQ = null; // for chat layout status change let enableHoverSliderDetection = false; // for hover slider let firstLoadStatus = 2 | 8; // for page init let m_last_count = ''; // for comment count let sVideosITO = null; /** @type {WeakRef | null} */ let ytdFlexy = null; // WeakRef const Q = {} const SETTING_DEFAULT_TAB_0 = "#tab-videos" const settings = { defaultTab: SETTING_DEFAULT_TAB_0 }; const STORE_VERSION = 1; const STORE_key = 'userscript-tabview-settings'; const key_default_tab = 'my-default-tab'; let hiddenTabsByUserCSS = 0; let defaultTabByUserCSS = 0; let setupDefaultTabBtnSetting = null; let isCommentsTabBtnHidden = false; let fetchCounts = { base: null, new: null, fetched: false, count: 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 getGMT = () => { let m = new Date('2023-01-01T00:00:00Z'); return m.getDate() === 1 ? `+${m.getHours()}` : `-${24 - m.getHours()}`; }; function durationInfoTS(durationInfo) { /** * @type {{ hrs: number, mins: number, seconds: number }} */ let _durationInfo = durationInfo return _durationInfo } function formatDateReqTS(req) { /** * @type {{ bd1: KDate | undefined, bd2: KDate | undefined, isSameDay: number | undefined, durationInfo: object | undefined, formatDates: object | undefined }} */ let _req = req return _req } function liveDurationLocaleEN(durationInfo) { const { hrs, mins, seconds } = durationInfoTS(durationInfo) let ret = [] ret.push(`Length:`) if (hrs > 0) ret.push(`${hrs} ${hrs === 1 ? 'hour' : 'hours'}`) if (mins > 0) ret.push(`${mins} ${mins === 1 ? 'minute' : 'minutes'}`) if (seconds !== null) ret.push(`${seconds} ${seconds === 1 ? 'second' : 'seconds'}`) return ret.join(' ') } /* liveDurationLocaleEN by ChatGPT @ 2023.05.11 */ function liveDurationLocaleJP(durationInfo) { const { hrs, mins, seconds } = durationInfoTS(durationInfo); let ret = []; ret.push(`長さ:`); if (hrs > 0) ret.push(`${hrs}時間`); if (mins > 0) ret.push(`${mins}分`); if (seconds !== null) ret.push(`${seconds}秒`); return ret.join(''); } function formatDateResultEN(type, req) { const { bd1, bd2, durationInfo, formatDates } = formatDateReqTS(req) switch (type) { case 0x200: return [ `The livestream was in ${bd1.lokStringDateEN()} from ${bd1.lokStringTime()} to ${bd2.lokStringTime()}. [GMT${getGMT()}]`, liveDurationLocaleEN(durationInfo) ].join('\n'); case 0x210: return [ `The livestream was from ${bd1.lokStringDateEN()} ${bd1.lokStringTime()} to ${bd2.lokStringDateEN()} ${bd2.lokStringTime()}. [GMT${getGMT()}]`, liveDurationLocaleEN(durationInfo) ].join('\n'); case 0x300: return `The livestream started at ${bd1.lokStringTime()} [GMT${getGMT()}] in ${bd1.lokStringDateEN()}.`; case 0x600: return `The video was uploaded in ${formatDates.uploadDate} and published in ${formatDates.publishDate}.`; case 0x610: return `The video was uploaded in ${formatDates.uploadDate}.`; case 0x700: return `The video was published in ${formatDates.publishDate}.`; } return ''; } /* formatDateResultJP by ChatGPT @ 2023.05.11 */ function formatDateResultJP(type, req) { const { bd1, bd2, durationInfo, formatDates } = formatDateReqTS(req); switch (type) { case 0x200: return [ `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から開始し、${bd2.lokStringTime()}まで続きました。[GMT${getGMT()}]`, liveDurationLocaleJP(durationInfo) ].join('\n'); case 0x210: return [ `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から${bd2.lokStringDateJP()}の${bd2.lokStringTime()}まで行われました。[GMT${getGMT()}]`, liveDurationLocaleJP(durationInfo) ].join('\n'); case 0x300: return `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から開始しました。[GMT${getGMT()}]`; case 0x600: return `この動画は${formatDates.uploadDate}にアップロードされ、${formatDates.publishDate}に公開されました。`; case 0x610: return `この動画は${formatDates.uploadDate}にアップロードされました。`; case 0x700: return `この動画は${formatDates.publishDate}に公開されました。`; } return ''; } function getFormatDateResultFunc() { switch (getLang()) { case 'jp': return formatDateResultJP; case 'en': default: return formatDateResultEN; } } // const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER', 'YTD-MENU-RENDERER'] let globalHook_symbols = []; let globalHook_hashs = {}; let singleColumnScrolling_dt = 0; let isStickyHeaderEnabled = false; let isMiniviewForStickyHeadEnabled = false; let theater_mode_changed_dt = 0; let detailsTriggerReset = false; let isMakeHeaderFloatCalled = false; let _viTimeNum = 203; let _updateTimeAccum = 0; /** @type {WeakMap} */ let loadedCommentsDT = new WeakMap(); // for weakref variable management const es = { get ytdFlexy() { /** @type { HTMLElement | null } */ let res = kRef(ytdFlexy); return res; }, get storeLastPanel() { /** @type { HTMLElement | null } */ let res = kRef(storeLastPanel); return res; } } const _console = new Proxy(console, { get(target, prop, receiver) { if (!DEBUG_LOG && prop === 'log') { return nullFunc } return Reflect.get(...arguments) } }); let generalLog901 = !DEBUG_LOG ? 0 : (evt) => { _console.log(901, evt.type) } const isPassiveArgSupport = (typeof IntersectionObserver === 'function'); // https://caniuse.com/?search=observer // https://caniuse.com/?search=addEventListener%20passive const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false; const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true; /** @type { (str: string) => (HTMLElement | null) } */ const _querySelector = HTMLElement.prototype.__shady_native_querySelector || HTMLElement.prototype.querySelector; // nodeType==1 // since 2022/07/12 /** @type { (str: string) => (NodeList) } */ const _querySelectorAll = HTMLElement.prototype.__shady_native_querySelectorAll || HTMLElement.prototype.querySelectorAll; // nodeType==1 // since 2022/07/12 const closestDOM = HTMLElement.prototype.closest; //const elementRemove = HTMLElement.prototype.remove; //const elementContains = HTMLElement.prototype.contains; // since 2022/07/12 const querySelectorFromAnchorX = function (parent, childSelector) { if (!(parent instanceof HTMLElement)) return null; return _querySelector.call(parent, childSelector) || null; } const closestDOMX = function (child, parentSelector) { if (!(child instanceof HTMLElement)) return null; return closestDOM.call(child, parentSelector) || null; } const { elementAppend, _setAttribute, _insertBefore } = (() => { let elementAppend = HTMLElement.prototype.appendChild; try { elementAppend = ShadyDOM.nativeMethods.appendChild || elementAppend; } catch (e) { } let _setAttribute = Element.prototype.setAttribute; try { _setAttribute = ShadyDOM.nativeMethods.setAttribute || _setAttribute; } catch (e) { } let _insertBefore = Node.prototype.insertBefore; try { _insertBefore = ShadyDOM.nativeMethods.insertBefore || _insertBefore; } catch (e) { } return { elementAppend, _setAttribute, _insertBefore }; })(); const getDMHelper = () => { let _dm = document.getElementById('d-m'); if (!_dm) { _dm = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); _dm.id = 'd-m'; _insertBefore.call(document.documentElement, _dm, document.documentElement.firstChild); } const dm = _dm; dm._setAttribute = _setAttribute; let j = 0; let attributeName_; while (dm.hasAttribute(attributeName_ = `dm-${Math.floor(Math.random() * 314159265359 + 314159265359).toString(36)}`)) { // none } const attributeName = attributeName_; let qr = null; const mo = new MutationObserver(() => { if (qr !== null) { if (j > 8) j = 0; qr = (qr(), null); } }); mo.observe(document, { childList: true, subtree: true, attributes: true }); return (resolve) => { if (!qr) dm._setAttribute(attributeName, ++j); return qr = resolve; // return qr = afInterupter = resolve; }; }; const dmPN = getDMHelper(); let _dmPromise = null; const getDMPromise = () => { return (_dmPromise || (_dmPromise = (new Promise(dmPN)).then(() => { _dmPromise = null; }))) }; /** * * @param {number} f bit flag * @param {number} w bit flag (with) * @param {number} wo bit flag (without) * @returns */ const fT = function (f, w, wo) { return (f & (w | wo)) === w } /* 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); // function prettyElm(/** @type {Element} */ elm) { // if (!elm || !elm.nodeName) return null; // const eId = elm.id || null; // const eClsName = elm.className || null; // return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim(); // } // function extractTextContent(/** @type {Node} */ elm) { // return elm.textContent.replace(/\s+/g, '').replace(/[^\da-zA-Z\u4E00-\u9FFF\u00C0-\u00FF\u00C0-\u02AF\u1E00-\u1EFF\u0590-\u05FF\u0400-\u052F\u0E00-\u0E7F\u0600-\u06FF\u0750-\u077F\u1100-\u11FF\u3130-\u318F\uAC00-\uD7AF\u3040-\u30FF\u31F0-\u31FF]/g, '') // } function addScript(/** @type {string} */ scriptText) { const scriptNode = document.createElement('script'); scriptNode.type = 'text/javascript'; scriptNode.textContent = scriptText; try { document.documentElement.appendChild(scriptNode); } catch (e) { console.log('addScript Error', e) } return scriptNode; } function addScriptByURL(/** @type {string} */ scriptURL) { const scriptNode = document.createElement('script'); scriptNode.type = 'text/javascript'; scriptNode.src = scriptURL; try { document.documentElement.appendChild(scriptNode); } catch (e) { console.log('addScriptByURL Error', e) } return scriptNode; } // function addStyle(/** @type {string} */ styleText, /** @type {HTMLElement | Document} */ container) { // const styleNode = document.createElement('style'); // //styleNode.type = 'text/css'; // styleNode.textContent = styleText; // (container || document.documentElement).appendChild(styleNode); // return styleNode; // } /* yt-action yt-add-element-to-app yt-autonav-pause-blur yt-autonav-pause-focus yt-autonav-pause-guide-closed yt-autonav-pause-guide-opened yt-autonav-pause-player yt-autonav-pause-player-ended yt-autonav-pause-scroll yt-autoplay-on-changed yt-close-tou-form yt-consent-bump-display-changed yt-focus-searchbox yt-get-context-provider yt-guide-close yt-guide-hover yt-guide-toggle yt-history-load yt-history-pop yt-load-invalidation-continuation yt-load-next-continuation yt-load-reload-continuation yt-load-tou-form yt-masthead-height-changed yt-navigate yt-navigate-cache yt-navigate-error yt-navigate-finish yt-navigate-redirect yt-navigate-set-page-offset yt-navigate-start yt-next-continuation-data-updated yt-open-hotkey-dialog yt-open-tou-form-loading-state yt-page-data-fetched yt-page-data-updated yt-page-data-will-update yt-page-manager-navigate-start yt-page-navigate-start yt-page-type-changed yt-player-attached yt-player-detached yt-player-released yt-player-requested yt-player-updated yt-popup-canceled yt-popup-closed yt-popup-opened yt-preconnect-urls yt-register-action yt-report-form-closed yt-report-form-opened yt-request-panel-mode-change yt-retrieve-location yt-service-request-completed yt-service-request-error yt-service-request-sent yt-set-theater-mode-enabled yt-show-survey yt-subscription-changed yt-swatch-changed yt-theater-mode-allowed yt-unregister-action yt-update-title yt-update-unseen-notification-count yt-viewport-scanned yt-visibility-refresh */ // _console.log(38489) class ControllerIDInner { constructor() { this.q = 0; } set(v) { this.q = v } valueOf() { return this.q; } inc() { if (++this.q > 1e9) this.q = 9; return this.q; } } const ControllerID = () => { return new ControllerIDInner(); } const psId = ControllerID(); psId.auto = false; 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_; } } }; })(); const tabsInsertedPromise = new PromiseExternal(); let chatController = { allowChatControl: true }; class Deferred { constructor() { this.reset(); } debounce(f, controllerId) { let g = f; if (controllerId) { const tid = controllerId.auto === false ? controllerId.valueOf() : controllerId.inc(); g = () => (tid === controllerId.valueOf() ? f() : 0); } return this.promise.then().then(g).catch(console.warn); // avoid promise.then.then.then ... } d() { return this.promise.then().catch(console.warn); } reset() { this.resolved = false; this.promise = new PromiseExternal(); } resolve() { if (this.resolved !== false) return false; this.resolved = true; this.promise.resolve(...arguments); return true; } } class Mutex { constructor() { this.p = Promise.resolve() } lockWith(f) { this.p = this.p.then(() => { return new Promise(f).catch(console.warn) }) } } const rfKey = Symbol(); const ffReflection = (o, c) => { // see https://github.com/erosman/support/issues/526 if (o.constructor !== c) { const a = c[rfKey] || (c[rfKey] = Object.keys(Object.getOwnPropertyDescriptors(c.prototype))); for (const k of a) { o[k] = c.prototype[k]; } } } /* FireMonkey unable to extend MutationObserver correctly */ class AttributeMutationObserver extends MutationObserver { constructor(flist) { super((mutations, observer) => { for (const mutation of mutations) { if (mutation.type === 'attributes') { this.checker(mutation.target, mutation.attributeName) } } }) this.flist = flist; this.res = {} ffReflection(this, AttributeMutationObserver); } takeRecords() { super.takeRecords(); } disconnect() { this._target = null; super.disconnect(); } observe(/** @type {Node} */ target) { if (this._target) return; //console.log(123124, target) this._target = mWeakRef(target); //console.log(123125, kRef(this._target)) const options = { attributes: true, attributeFilter: Object.keys(this.flist), //attributeFilter: [ "status", "username" ], attributeOldValue: true } super.observe(target, options) } checker(/** @type {Node} */ target,/** @type {string} */ attributeName) { let nv = target.getAttribute(attributeName); if (this.res[attributeName] !== nv) { this.res[attributeName] = nv let f = this.flist[attributeName]; if (f) f(attributeName, nv); } } check(delay = 0) { setTimeout(() => { let target = kRef(this._target) if (target !== null) { for (const key of Object.keys(this.flist)) { this.checker(target, key) } } else { console.log('target is null') //disconnected?? } target = null; }, delay) } } class KDate extends Date { constructor(...args) { super(...args) this.dayBack = false } browserSupported() { } lokStringDateEN() { const d = this let y = d.getFullYear() let m = d.getMonth() + 1 let date = d.getDate() let sy = y < 1000 ? (`0000${y}`).slice(-4) : '' + y let sm = m < 10 ? '0' + m : '' + m let sd = date < 10 ? '0' + date : '' + date return `${sy}.${sm}.${sd}` } lokStringDateJP() { const d = this let y = d.getFullYear() let m = d.getMonth() + 1 let date = d.getDate() let sy = y < 1000 ? (`0000${y}`).slice(-4) : '' + y let sm = m < 10 ? '0' + m : '' + m let sd = date < 10 ? '0' + date : '' + date return `${sy}/${sm}/${sd}` } lokStringTime() { const d = this let h = d.getHours() let m = d.getMinutes() const k = this.dayBack if (k) h += 24 let sh = h < 10 ? '0' + h : '' + h let sm = m < 10 ? '0' + m : '' + m return `${sh}:${sm}` } } console.assert('browserSupported' in (new KDate()), { error: "0x87FF", errorMsg: "Your userscript manager is not supported. FireMonkey is not recommended." } ); if (!('browserSupported' in (new KDate()))) return; let chromeCSSFiles = null; try { const o = [{ id: 'tabview-css-content', path: 'css/style_content.css' }, { id: 'tabview-css-chat', path: 'css/style_chat.css' }, { id: 'tabview-css-control', path: 'css/style_control.css' }]; for (const e of o) e.url = chrome.runtime.getURL(e.path) || ''; chromeCSSFiles = o; } catch (e) { } if (chromeCSSFiles) { const target = document.head || document.documentElement; for (const { path, url, id } of chromeCSSFiles) { if (url && typeof url === 'string' && url.length > 4) { const link = document.createElement('link'); link.id = id; link.rel = 'stylesheet'; link.type = 'text/css'; link.href = url; target.appendChild(link); } } chromeCSSFiles = null; } /* function fixTheaterChat1() { let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat:not([collapsed])') if (incorrectChat) { scriptletDeferred.debounce(()=>{ incorrectChat.dispatchEvent(new CustomEvent("collapsed-true")); }); } } function fixTheaterChat2() { let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat[collapsed]') if (incorrectChat) { scriptletDeferred.debounce(()=>{ incorrectChat.dispatchEvent(new CustomEvent("collapsed-false")); }); } } */ function fixTheaterChat1A() { let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat:not([collapsed])') if (incorrectChat) { ytBtnCollapseChat(); } } function fixTheaterChat2A() { let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat[collapsed]') if (incorrectChat) { ytBtnExpandChat(); } } function check1885() { if (chatController.ytlstmTheaterMode) { if (document.fullscreenElement || document.querySelector('ytd-watch-flexy[fullscreen]')) { chatController.allowChatControl = true; chatController.ytlstmTheaterMode = false; return; } } if (chatController.allowChatControl) { if (document.body.hasAttribute('data-ytlstm-theater-mode')) { chatController.allowChatControl = false; chatController.ytlstmTheaterMode = true; } } else { if (!document.body.hasAttribute('data-ytlstm-theater-mode')) { chatController.allowChatControl = true; chatController.ytlstmTheaterMode = false; } } } function check1886() { let cssContentNode = document.getElementById('tabview-css-content') if (!cssContentNode) return; let cssChatNode = document.getElementById('tabview-css-chat') if (!cssChatNode) return; if (cssContentNode.disabled === false) { if (chatController.ytlstmTheaterMode) { cssContentNode.disabled = true; cssChatNode.disabled = true; let t = document.querySelector('.youtube-genius-lyrics-found-hide-btn'); if (t) t.click(); ytBtnCloseEngagementPanels(); return 1; } } else if (cssContentNode.disabled === true) { if (!chatController.ytlstmTheaterMode) { cssContentNode.disabled = false; cssChatNode.disabled = false; return 2; } } } function dnsPrefetch() { function linker(link, rel, href, _as) { return new Promise(resolve => { if (!link) link = document.createElement('link'); link.rel = rel; if (_as) link.setAttribute('as', _as); link.onload = function () { resolve({ link: this, success: true }) this.remove(); }; link.onerror = function () { resolve({ link: this, success: false }); this.remove(); }; link.href = href; (document.head || document.documentElement).appendChild(link); link = null; }); } new Promise(resolve => { if (document.documentElement) { resolve(); } else { let mo = new MutationObserver(() => { if (mo !== null && document.documentElement) { mo.takeRecords(); mo.disconnect(); mo = null; resolve(); } }); mo.observe(document, { childList: true, subtree: false }) } }).then(() => { const hosts = [ 'https://i.ytimg.com', 'https://www.youtube.com', 'https://rr2---sn-5n5ip-ioqd.googlevideo.com', 'https://googlevideo.com', 'https://accounts.youtube.com', 'https://jnn-pa.googleapis.com', 'https://googleapis.com', 'https://fonts.gstatic.com', 'https://www.gstatic.com', 'https://yt3.ggpht.com', 'https://yt4.ggpht.com' ] for (const h of hosts) { linker(null, 'preconnect', h); } }); } dnsPrefetch(); const tabsDeferred = new Deferred(); tabsDeferred.resolve(); const discardableFn = function (f) { // logic to be reviewed; unstable if (!scriptEnable && tabsDeferred.resolved) { } // ignore all before first yt-navigate-finished else tabsDeferred.debounce(f, psId); } let layoutStatusMutex = new Mutex(); let sliderMutex = new Mutex(); const renderDeferred = new Deferred(); //pageRendered let pageRendered = 0; let renderIdentifier = ControllerID(); renderIdentifier.auto = false; const scriptletDeferred = new Deferred(); function scriptInjector(script_id, url_chrome, response_id) { let res = { script_id: script_id, inject: function () { let res = this, script_id = this.script_id; if (!document.querySelector(`script#${script_id}`)) { if (res.runtime_url) { addScriptByURL(res.runtime_url).id = script_id; } else { addScript(`${res.injection_script}`).id = script_id; } } } } res.script_id = script_id; if (isMyScriptInChromeRuntime()) { res.runtime_url = window.chrome.runtime.getURL(url_chrome) } else { res.injection_script = GM_getResourceText(response_id); } return res; } const script_inject_js1 = scriptInjector( 'userscript-tabview-injection-1', 'js/injection_script_1.js', "injectionJS1"); let scriptReady = false; scriptletDeferred.debounce(() => { scriptReady = true; }); const sendToPageScriptPendings = new Map(); async function sendToPageScript(element, id, ...args) { if (!scriptReady) { const tid = (sendToPageScriptPendings.get(id) || 0) + 1 sendToPageScriptPendings.set(id, tid); await scriptletDeferred.d(); const cid = sendToPageScriptPendings.get(id); if (tid !== cid) return; } element.dispatchEvent(new CustomEvent("tabview-page-script", { detail: { id: id, args: args.length ? args : null } })); } function nonCryptoRandStr(/** @type {number} */ n) { const result = new Array(n); const baseStr = nonCryptoRandStr_base; const bLen = baseStr.length; for (let i = 0; i < n; i++) { let t = null do { t = baseStr.charAt(Math.floor(Math.random() * bLen)); } while (i === 0 && 10 - t > 0) result[i] = t; } return result.join(''); } const uidMAP = new Map(); function uidGEN(s) { let uid = uidMAP.get(s); if (!uid) { const uidStore = ObserverRegister.uidStore; do { uid = nonCryptoRandStr(5); } while (uidStore[uid]) uidMAP.set(s, uid); } return uid; } /** * Class definition * @property {string} propName - propriety description * ... */ class ObserverRegister { constructor(/** @type {()=>MutationObserver | IntersectionObserver} */ observerCreator) { let uid = null; const uidStore = ObserverRegister.uidStore; do { uid = nonCryptoRandStr(5); } while (uidStore[uid]) uidStore[uid] = true; /** * uid is the unique string for each observer * @type {string} * @public */ this.uid = uid; /** * observerCreator is a function to create the observer * @type {Function} * @public */ this.observerCreator = observerCreator /** * observer is the actual observer object * @type {MutationObserver | IntersectionObserver} * @public */ this.observer = null; this.bindCount = 0; ffReflection(this, ObserverRegister); } bindElement(/** @type {HTMLElement} */ elm, ...args) { if (elm.hasAttribute(`o3r-${this.uid}`)) return false; elm.setAttribute(`o3r-${this.uid}`, '') this.bindCount++; if (this.observer === null) { this.observer = this.observerCreator(); } this.observer.observe(elm, ...args) return true } clear(/** @type {boolean} */ flag) { if (this.observer !== null) { //const uidStore = ObserverRegister.uidStore; if (flag === true) { this.observer.takeRecords(); this.observer.disconnect(); } this.observer = null; this.bindCount = 0; for (const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`) //uidStore[this.uid]=false; //this.uid = null; } } } /** * 'uidStore' is the static store of strings used. * @static */ ObserverRegister.uidStore = {}; // backward compatible with FireFox 55. const mtoObservationDetails = new ObserverRegister(() => { return new IntersectionObserver(ito_details, { root: null, rootMargin: "0px" }) }); const mtoFlexyAttr = new ObserverRegister(() => { return new MutationObserver(mtf_attrFlexy) }); const mtoBodyAttr = new ObserverRegister(() => { return new MutationObserver(mtf_attrBody) }); const mtoVisibility_EngagementPanel = new ObserverRegister(() => { return new MutationObserver(FP.mtf_attrEngagementPanel) }); const mtoVisibility_Playlist = new ObserverRegister(() => { return new AttributeMutationObserver({ "hidden": FP.mtf_attrPlaylist }) }) const sa_playlist = mtoVisibility_Playlist.uid; const mtoVisibility_Comments = new ObserverRegister(() => { return new AttributeMutationObserver({ "hidden": FP.mtf_attrComments }) }) const sa_comments = mtoVisibility_Comments.uid; const mtoVisibility_Chatroom = new ObserverRegister(() => { return new AttributeMutationObserver({ "collapsed": FP.mtf_attrChatroom }) }) // const sa_chatroom = mtoVisibility_Chatroom.uid; function isDOMVisible(/** @type {HTMLElement} */ elem) { // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); } function isNonEmptyString(s) { return typeof s == 'string' && s.length > 0; } async function dispatchWindowResize() { // for youtube to detect layout resize for adjusting Player tools return window.dispatchEvent(new Event('resize')); } async function dispatchCommentRowResize() { if (pageType !== "watch") return; const ytdFlexyElm = es.ytdFlexy; if (!ytdFlexyElm) return; if (ytdFlexyElm.getAttribute('tyt-tab') !== '#tab-comments') return; sendToPageScript(document, 'tabview-resize-comments-rows'); } function enterPIP(video, errorHandler) { // ignore audio return new Promise(resolve => { if (video && typeof video.requestPictureInPicture === 'function' && typeof document.exitPictureInPicture === 'function') { if (isVideoPlaying(video) && document.pictureInPictureElement === null) { video.requestPictureInPicture().then(res => { resolve(true); }).catch((e) => { if (errorHandler === undefined) console.warn(e); else if (typeof errorHandler == 'function') errorHandler(e); resolve(false); }); } else { resolve(false); } } else { resolve(null); } }) } function exitPIP() { if (document.pictureInPictureElement !== null && typeof document.exitPictureInPicture === 'function') { document.exitPictureInPicture().then(res => { }).catch(console.warn) } } function setToggleBtnTxt() { if (chatroomDetails && typeof chatroomDetails.txt_expand === 'string' && typeof chatroomDetails.txt_collapse === 'string') { // toggle button (show-hide-button) // _console.log(124234, 'c=== ') let chat = document.querySelector('ytd-live-chat-frame#chat'); if (!chat) return; let txt = _querySelector.call(chat, 'span.yt-core-attributed-string[role="text"]'); let c = (txt || 0).textContent; if (typeof c === 'string' && c.length > 2) { if (chat.hasAttribute('collapsed')) { // _console.log(124234, 'collapsed show expand ', chatroomDetails.txt_expand) if (c !== chatroomDetails.txt_expand) { txt.textContent = chatroomDetails.txt_expand; } } else { // _console.log(124234, 'not collapsed show collapse ', chatroomDetails.txt_collapse) if (c !== chatroomDetails.txt_collapse) { txt.textContent = chatroomDetails.txt_collapse; } } } } else if (chatroomDetails && typeof chatroomDetails.txt_expand === 'string') { // show button only let chat = document.querySelector('ytd-live-chat-frame#chat'); if (!chat) return; let txt = _querySelector.call(chat, 'span.yt-core-attributed-string[role="text"]'); let c = (txt || 0).textContent; if (typeof c === 'string' && c.length > 2) { if (chat.hasAttribute('collapsed')) { // _console.log(124234, 'collapsed show expand ', chatroomDetails.txt_expand) if (c !== chatroomDetails.txt_expand) { txt.textContent = chatroomDetails.txt_expand; } } } } } function handlerTabExpanderClick() { scriptletDeferred.debounce(() => { async function b() { let h1 = document.documentElement.clientHeight; let h2 = (document.querySelector('#right-tabs') || 0).clientHeight; await Promise.resolve(0); if (h1 > 300 && h2 > 300) { let ratio = h2 / h1; // positive below 1.0 return ratio; } return 0; } async function a() { let secondary = document.querySelector('#secondary.ytd-watch-flexy'); if (secondary) { if (!secondary.classList.contains('tabview-hover-slider-enable')) { let secondaryInner = _querySelector.call(secondary, '#secondary-inner.ytd-watch-flexy'); if (secondaryInner) { if (!secondary.classList.contains('tabview-hover-slider')) { // without hover //let rect = secondary.getBoundingClientRect(); //let rectI = secondaryInner.getBoundingClientRect(); secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`) } let ratio = await b(); if (ratio > 0.0 && ratio <= 1.0) { secondaryInner.style.setProperty('--ytd-watch-flexy-sidebar-width-d', `${Math.round(100 * ratio * 10) / 10}vw`); secondary.classList.add('tabview-hover-slider'); secondary.classList.add('tabview-hover-slider-enable'); let video = document.querySelector('#player video'); // ignore audio enterPIP(video); } } } else { secondary.dispatchEvent(new CustomEvent("tabview-hover-slider-restore")); //console.log(1994) } // no animation event triggered for hover -> enable dispatchCommentRowResize(); } } a(); }) } let global_columns_end_ito = null; function setupHoverSlider(secondary, columns) { if (!secondary || !columns) return; let attrName = `o4r-${uidGEN('tabview-hover-slider-restore')}`; if (secondary.hasAttribute(attrName)) return; secondary.setAttribute(attrName, ''); let elmB = document.querySelector('tabview-view-secondary-xpander'); if (!elmB) { elmB = document.createElement('tabview-view-secondary-xpander'); prependTo(elmB, secondary); } let elmA = document.querySelector('tabview-view-columns-endpos'); if (elmA) elmA.remove(); elmA = document.createElement('tabview-view-columns-endpos'); let itoA = new IntersectionObserver((entries) => { let t = null; let w = enableHoverSliderDetection for (const entry of entries) { if (entry.rootBounds === null) continue; let bcr = entry.boundingClientRect; let rb = entry.rootBounds; t = !entry.isIntersecting && (bcr.left > rb.right) && (rb.left <= 0); // if entries.length>1 (unlikely); take the last intersecting // supplement cond 1. ensure the col element is in the right side // supplement cond 2. ensure column is wide enough for overflow checking // it can also avoid if the layout change happened but attribute not yet changed during the intersection observation } let columns = document.querySelector('#columns.style-scope.ytd-watch-flexy'); if (columns) columns.classList.toggle('tyt-column-overflow', t); if (w !== t && t !== null) { // t can be true when the layout enters single column mode enableHoverSliderDetection = t; } //console.log(entries, enableHoverSliderDetection, t) }) elementAppend.call(columns, elmA); // append to dom first before observe if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) { //to trigger observation at the time layout being changed itoA.observe(elmA); } global_columns_end_ito = itoA; secondary.addEventListener('tabview-hover-slider-restore', function (evt) { let secondary = evt.target; if (!secondary.classList.contains('tabview-hover-slider-enable')) return; let secondaryInner = _querySelector.call(secondary, '#secondary-inner.ytd-watch-flexy') if (!secondaryInner) return; if (secondary.classList.contains('tabview-hover-slider-hover')) { Promise.resolve(0).then(() => { secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d'); }).then(() => { secondary.classList.remove('tabview-hover-slider-enable') exitPIP(); }) } else { let secondary = evt.target; secondary.classList.remove('tabview-hover-slider') secondary.classList.remove('tabview-hover-slider-enable') secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d'); secondaryInner.style.removeProperty('--tabview-slider-right') exitPIP(); } getDMPromise().then(() => { updateFloatingSlider() }); }, false); } function addTabExpander(tabContent) { if (!tabContent) return null; let id = tabContent.id; if (!id || typeof id !== 'string') return null; if (_querySelector.call(tabContent, `#${id} > tabview-view-tab-expander`)) return false; let elm = document.createElement('tabview-view-tab-expander') prependTo(elm, tabContent); elm.innerHTML = createHTML(`
${svgElm(16, 16, 12, 12, svgDiag1, 'svg-expand')}${svgElm(16, 16, 12, 12, svgDiag2, 'svg-collapse')}
`); elm.addEventListener('click', handlerTabExpanderClick, false); return true; } function getColumnOverflowWidth() { let screenWidth = document.documentElement.getBoundingClientRect().width; let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos'); if (posElm1) { let offset = posElm1.getBoundingClientRect().x - screenWidth; return offset } return null } function getSecondaryInnerRight() { let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos'); let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander'); if (posElm1 && posElm2) { let offset = posElm1.getBoundingClientRect().x - posElm2.getBoundingClientRect().right; return offset } return null } const setFloatingSliderOffset = (secondaryInner) => { let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos'); let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander'); if (posElm1 && posElm2) { let offset = getColumnOverflowWidth(); let k = 1.0 if (offset >= 125) { k = 1.0 } else if (offset >= 75) { k = 1.0; } else if (offset >= 25) { k = 0.25; } else { k = 0.0 } secondaryInner.style.setProperty('--tabview-slider-offset-k2', `${k}`); secondaryInner.style.setProperty('--tabview-slider-offset', `${offset}px`) // unnecessary let oriWidth = posElm2.getBoundingClientRect().width; secondaryInner.style.setProperty('--tabview-slider-ow', `${oriWidth}px`) let s1 = 'var(--ytd-watch-flexy-sidebar-width-d)'; // new width let s2 = `var(--tabview-slider-ow)`; // ori width - youtube changing the code -> not reliable to use css prop. let s3 = `${offset}px`; // how many px wider than the page secondaryInner.style.setProperty('--tabview-slider-offset-actual', `calc(${s1} - ${s2} + ${s3})`) } } async function updateFloatingSlider_A(secondaryInner) { // [is-extra-wide-video_] await getDMPromise(); // time allowed for dom changes and value change of enableHoverSliderDetection let secondary = nodeParent(secondaryInner); if (!secondary) return; if (secondary.classList.contains('tabview-hover-slider-enable')) { return; } if (!secondary.matches('#columns.ytd-watch-flexy #primary.ytd-watch-flexy ~ #secondary.ytd-watch-flexy')) { return; } const bool = enableHoverSliderDetection === true; const hasClassHover = secondary.classList.contains('tabview-hover-slider-hover') === true; if (bool || hasClassHover) { } else { return; } await Promise.resolve(0); secondary.classList.add('tabview-hover-final') if (hasClassHover && !bool) { secondaryInner.style.removeProperty('--tabview-slider-right') secondaryInner.style.removeProperty('--tabview-slider-offset') } else { if (!hasClassHover) { secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`) } setFloatingSliderOffset(secondaryInner); } if (bool ^ hasClassHover) { secondary.classList.toggle('tabview-hover-slider', bool) secondary.classList.toggle('tabview-hover-slider-hover', bool) } await Promise.resolve(0); setTimeout(() => { secondary.classList.remove('tabview-hover-final') }, 350) } function updateFloatingSlider() { let secondaryInner = document.querySelector('ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy') if (!secondaryInner) return; let secondary = nodeParent(secondaryInner); if (!secondary) return; if (secondary.classList.contains('tabview-hover-slider-enable')) { return; } let t = document.documentElement.clientWidth; //integer sliderMutex.lockWith(unlock => { let v = document.documentElement.clientWidth; //integer if (t === v && secondaryInner.matches('body ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')) { updateFloatingSlider_A(secondaryInner).then(unlock); } else { unlock(); } }) } // iframe loadprocess interupted when expanded -> collasped async function checkChatIframeOnExpanded() { const iframe = document.getElementById('chatframe'); if (!(iframe instanceof HTMLIFrameElement)) return; if (((iframeLoadStatusWM.get(iframe) || 0) % 2) === 1) return; await iframePipeline(() => { }); if (((iframeLoadStatusWM.get(iframe) || 0) % 2) === 1) return; let iframeLocation; try { iframeLocation = iframe.contentDocument.location; } catch (e) { } if (!iframeLocation) return; if (iframeLocation.href.includes('youtube.com')) { await iframePipeline(async () => { if (((iframeLoadStatusWM.get(iframe) || 0) % 2) === 1) return; await iframeLoadHookHandlerPromise.then(); if (iframe.isConnected === true && iframe.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')) { await iframeLoadProcess(iframe); } }); } // console.log(1421, iframeLocation.href); } const canScrollIntoViewWithOptions = (() => { const element = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); let i = 0; try { element.scrollIntoView({ get behavior() { i++ }, get block() { i++ } }); } catch (e) { } return i === 2; })(); // Chrome >= 61, Edge >= 79, Firefox >= 36, Opera >=48, Safari >=14 /** @param {Element} elm */ const stableScroll = async (elm, options) => { const f = (p) => `${p.height}|${p.width}|${p.left}|${p.top}` let i = 0; while (i++ < 4) { const p = elm.getBoundingClientRect(); const ps = f(p); const rr = new Promise(resolve => { document.addEventListener('scroll', resolve, { capture: true, passive: true, once: true }) }); elm.scrollIntoView(options); await rr; await new Promise(resolve => { let io = new IntersectionObserver(() => { resolve(); io.disconnect(); io = null; }); io.observe(elm) }); const q = elm.getBoundingClientRect(); const qs = f(q); if (ps === qs) break; } } const fullScreenTabScrollIntoView = canScrollIntoViewWithOptions ? () => { const scrollElement = document.querySelector('ytd-app[scrolling]') const b = scrollElement && isFullScreen() && isWideScreenWithTwoColumns() && (isChatExpand() || isTabExpanded() || isEngagementPanelExpanded() || isDonationShelfExpanded()); if (!b) return; // single column view; click button; scroll to tab content area 100% const rightTabs = document.querySelector('#right-tabs'); const pTop = rightTabs.getBoundingClientRect().top - scrollElement.getBoundingClientRect().top if (rightTabs && pTop > 0) { const secondaryInner = rightTabs.closest('#secondary-inner'); if (secondaryInner && secondaryInner.getBoundingClientRect().height < document.documentElement.clientHeight && document.documentElement.clientHeight > 80) { stableScroll(secondaryInner, { behavior: "instant", block: "end", inline: "nearest" }); } } } : null; function setToActiveTab(defaultTab) { if (isTheater() && isWideScreenWithTwoColumns()) return; const jElm = document.querySelector(`a[tyt-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) || document.querySelector(`a[tyt-tab-content="${(defaultTab || settings.defaultTab)}"]:not(.tab-btn-hidden)`) || document.querySelector(`a[tyt-tab-content="${(SETTING_DEFAULT_TAB_0)}"]:not(.tab-btn-hidden)`) || document.querySelector("a[tyt-tab-content]:not(.tab-btn-hidden)") || null; switchTabActivity(jElm); return !!jElm; } let enableLivePopupCheck = false function layoutStatusChanged(/** @type {number} */ old_layoutStatus, /** @type {number} */ new_layoutStatus) { if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === 0) makeHeaderFloat(); //if (old_layoutStatus === new_layoutStatus) return; const cssElm = es.ytdFlexy; if (!cssElm) return; const BF_TWOCOL_N_THEATER = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER let new_isExpandedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_EXPANDED) let new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM) let new_isTabExpanded = !!(new_layoutStatus & LAYOUT_TAB_EXPANDED); let new_isFullScreen = !!(new_layoutStatus & LAYOUT_FULLSCREEN); let new_isExpandedEPanel = !!(new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPANDED); let new_isExpandedDonationShelf = !!(new_layoutStatus & LAYOUT_DONATION_SHELF_EXPANDED); function showTabOrChat() { layoutStatusMutex.lockWith(unlock => { if (lstTab.lastPanel == '#chatroom') { if (new_isTabExpanded) switchTabActivity(null) if (!new_isExpandedChat) ytBtnExpandChat(); } else if (lstTab.lastPanel && lstTab.lastPanel.indexOf('#engagement-panel-') == 0) { if (new_isTabExpanded) switchTabActivity(null) if (!new_isExpandedEPanel) ytBtnOpenEngagementPanel(lstTab.lastPanel); } else if (lstTab.lastPanel == '#donation-shelf') { if (new_isTabExpanded) switchTabActivity(null) if (!new_isExpandedDonationShelf) openDonationShelf(); } else { if (new_isExpandedChat) ytBtnCollapseChat() if (!new_isTabExpanded) setToActiveTab(); } getDMPromise().then(unlock); }) } function hideTabAndChat() { layoutStatusMutex.lockWith(unlock => { if (new_isTabExpanded) switchTabActivity(null) if (new_isExpandedChat) ytBtnCollapseChat() if (new_isExpandedEPanel) ytBtnCloseEngagementPanels(); if (new_isExpandedDonationShelf) closeDonationShelf(); getDMPromise().then(unlock); }) } const statusCollapsedFalse = !!(new_layoutStatus & (LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED)) const statusCollapsedTrue = !statusCollapsedFalse let changes = (old_layoutStatus & LAYOUT_VAILD) ? old_layoutStatus ^ new_layoutStatus : 0; let chat_collapsed_changed = !!(changes & LAYOUT_CHATROOM_COLLAPSED) let chat_expanded_changed = !!(changes & LAYOUT_CHATROOM_EXPANDED) let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED) let theater_mode_changed = !!(changes & LAYOUT_THEATER) let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS) let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN) let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPANDED) let ds_expanded_changed = !!(changes & LAYOUT_DONATION_SHELF_EXPANDED) // _console.log(8221, 1, chat_collapsed_changed, chat_expanded_changed, tab_expanded_changed, theater_mode_changed, column_mode_changed, fullscreen_mode_changed, epanel_expanded_changed) //console.log(169, 1, chat_collapsed_changed, tab_expanded_changed) //console.log(169, 2, new_isExpandedChat, new_isCollapsedChat, new_isTabExpanded) let BF_LayoutCh_Panel = (changes & (LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED)) let tab_change = BF_LayoutCh_Panel; let isChatOrTabExpandTriggering = !!((new_layoutStatus) & BF_LayoutCh_Panel); let isChatOrTabCollaspeTriggering = !!((~new_layoutStatus) & BF_LayoutCh_Panel); const moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandedEPanel + new_isExpandedDonationShelf) > 1 const base_twoCol_NoTheather_chatExpand_a = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED const base_twoCol_NoTheather_chatExpand_b = LAYOUT_TWO_COLUMNS | 0 | LAYOUT_CHATROOM | 0 // two column; not theater; tab collapse; chat expand; ep expand const IF_01a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED; const IF_01b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_ENGAGEMENT_PANEL_EXPANDED; // two column; not theater; tab collapse; chat expand; ep expand const IF_07a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED; const IF_07b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_DONATION_SHELF_EXPANDED; // two column; not theater; const IF_02a = BF_TWOCOL_N_THEATER; const IF_02b = LAYOUT_TWO_COLUMNS; // two column; not theater; tab expand; chat expand; const IF_03a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED; const IF_03b = base_twoCol_NoTheather_chatExpand_b | LAYOUT_TAB_EXPANDED; // two column; tab expand; chat expand; const IF_06a = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED; const IF_06b = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | 0; // two column; theater; const IF_04a = BF_TWOCOL_N_THEATER; const IF_04b = BF_TWOCOL_N_THEATER; // not fullscreen; two column; not theater; not tab expand; not EP expand; not expand chat; not donation shelf const IF_05a = LAYOUT_FULLSCREEN | LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED; const IF_05b = 0 | LAYOUT_TWO_COLUMNS | 0 | 0 | 0 | 0 | 0; let _isChatPopupedF = null let isChatPopupedF = () => { return _isChatPopupedF === null ? (_isChatPopupedF = cssElm.classList.contains('tyt-chat-popup')) : _isChatPopupedF } if (chat_expanded_changed && new_isExpandedChat) { checkChatIframeOnExpanded(); } const checkForMoreThanOne = () => { if (moreThanOneShown && (new_layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) { layoutStatusMutex.lockWith(unlock => { if (new_isTabExpanded && lstTab.lastPanel === null) { if (new_isExpandedChat) ytBtnCollapseChat(); if (new_isExpandedEPanel) ytBtnCloseEngagementPanels(); if (new_isExpandedDonationShelf) closeDonationShelf(); } else if (lstTab.lastPanel) { let lastPanel = lstTab.lastPanel || ''; if (typeof lastPanel !== 'string') lastPanel = ''; if (new_isExpandedChat && lastPanel !== '#chatroom') ytBtnCollapseChat(); if (new_isExpandedEPanel && lastPanel.indexOf('#engagement-panel-') < 0) ytBtnCloseEngagementPanels(); if (new_isExpandedDonationShelf && lastPanel !== '#donation-shelf') closeDonationShelf(); switchTabActivity(null); } getDMPromise().then(unlock); }); } } let waitingPromise = Promise.resolve(); if (new_isFullScreen) { if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_06a) === IF_06b && statusCollapsedFalse && !column_mode_changed) { // two column; tab expand; chat expand; switchTabActivity(null); } if (!!(tab_change & LAYOUT_CHATROOM_EXPANDED) && new_isExpandedChat) { //tab_change = LAYOUT_CHATROOM_EXPANDED //tab_change = LAYOUT_CHATROOM_EXPANDED|LAYOUT_TAB_EXPANDED /* triggered by iframe close, not by button click */ canScrollIntoViewWithOptions && waitingPromise.then(async () => { await getDMPromise(); let scrollElement = document.querySelector('ytd-app[scrolling]') if (!scrollElement) return; // single column view; click button; scroll to tab content area 100% let chatFrame = document.querySelector('ytd-live-chat-frame#chat'); if (chatFrame && isChatExpand()) { // _console.log(7290, 1) fullScreenTabScrollIntoView(); } }) } if (!!(tab_change & LAYOUT_ENGAGEMENT_PANEL_EXPANDED) && new_isExpandedEPanel) { waitingPromise.then(async () => { await getDMPromise(); let scrollElement = document.querySelector('ytd-app[scrolling]') if (!scrollElement) return; // single column view; click button; scroll to tab content area 100% let epPanel = document.querySelector('ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]:not([hidden])'); if (epPanel) { // _console.log(7290, 2) let pi = 50; let cid = setInterval(() => { if (--pi) epPanel.scrollIntoView(true); else clearInterval(cid) }, 17) // } }) } } else if (fullscreen_mode_changed) { // new_isFullScreen: false // fullscreen_mode_changed: true if (!new_isFullScreen && statusCollapsedTrue && isWideScreenWithTwoColumns() && !isTheater()) { showTabOrChat(); } else if (!new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns() && isTheater()) { if (isChatPopupedF()) { } else { ytBtnCancelTheater(); } } if (!new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns()) { // check more than one shown Promise.resolve().then(checkForMoreThanOne); } } else if ((new_layoutStatus & IF_01a) === IF_01b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED)) { // new_isFullScreen: false // fullscreen_mode_changed: false // two column; not theater; tab collapse; chat expand; ep expand if (epanel_expanded_changed) { layoutStatusMutex.lockWith(unlock => { ytBtnCollapseChat(); getDMPromise().then(unlock); }) } else if (chat_collapsed_changed) { layoutStatusMutex.lockWith(unlock => { ytBtnCloseEngagementPanels(); getDMPromise().then(unlock); }) } } else if ((new_layoutStatus & IF_07a) === IF_07b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_DONATION_SHELF_EXPANDED)) { // new_isFullScreen: false // fullscreen_mode_changed: false // two column; not theater; tab collapse; chat expand; ds expand if (ds_expanded_changed) { layoutStatusMutex.lockWith(unlock => { ytBtnCollapseChat(); getDMPromise().then(unlock); }) } else if (chat_collapsed_changed) { layoutStatusMutex.lockWith(unlock => { closeDonationShelf(); getDMHelper().then(unlock); }) } } else if (!tab_change && column_mode_changed && (new_layoutStatus & IF_02a) === IF_02b && moreThanOneShown) { // new_isFullScreen: false // fullscreen_mode_changed: false // two column; not theater; // moreThanOneShown showTabOrChat(); } else if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_03a) === IF_03b && statusCollapsedFalse && !column_mode_changed) { // new_isFullScreen: false // fullscreen_mode_changed: false // two column; not theater; tab expand; chat expand; switchTabActivity(null); } else if (isChatOrTabExpandTriggering && (new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse && (changes & BF_TWOCOL_N_THEATER) === 0) { // new_isFullScreen: false // fullscreen_mode_changed: false ytBtnCancelTheater(); } else if ((new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse) { // new_isFullScreen: false // fullscreen_mode_changed: false if (isChatPopupedF()) { } else { hideTabAndChat(); } } else if (isChatOrTabCollaspeTriggering && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue && !column_mode_changed) { // new_isFullScreen: false // fullscreen_mode_changed: false if (tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED) { lstTab.lastPanel = null; if (new_isFullScreen) { } else { showTabOrChat(); } } else if (tab_change == LAYOUT_DONATION_SHELF_EXPANDED) { lstTab.lastPanel = null; if (new_isFullScreen) { } else { showTabOrChat(); } } else if (tab_change == LAYOUT_CHATROOM_EXPANDED) { lstTab.lastPanel = null; if (new_isFullScreen) { } else { showTabOrChat(); } } else { if (new_isFullScreen) { } else { ytBtnSetTheater(); } } } else if (!tab_change && !!(changes & BF_TWOCOL_N_THEATER) && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue) { // new_isFullScreen: false // fullscreen_mode_changed: false showTabOrChat(); } else if ((new_layoutStatus & IF_05a) === IF_05b) { // bug fix for restoring from mini player layoutStatusMutex.lockWith(unlock => { setToActiveTab(); getDMPromise().then(unlock); }); } if (theater_mode_changed) { let tdt = Date.now(); theater_mode_changed_dt = tdt setTimeout(() => { if (theater_mode_changed_dt !== tdt) return; updateFloatingSlider(); }, 130) } let secondaryElement = null; if (secondaryElement = document.querySelector('.tabview-hover-slider-enable')) { scriptletDeferred.debounce(() => { secondaryElement.isConnected && secondaryElement.dispatchEvent(new CustomEvent('tabview-hover-slider-restore')) }); } if (fullscreen_mode_changed) { detailsTriggerReset = true; getDMPromise().then(setHiddenStateForDesc); } // resize => is-two-columns_ if (column_mode_changed) { Promise.resolve(0).then(() => { checkForMoreThanOne(); pageCheck(); if (global_columns_end_ito !== null) { //to trigger observation at the time layout being changed if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) { let endpos = document.querySelector('tabview-view-columns-endpos') if (endpos !== null) { global_columns_end_ito.observe(endpos) } } else { global_columns_end_ito.disconnect(); } } getDMPromise().then(() => { singleColumnScrolling(true); //initalize sticky }); }) } if (enableLivePopupCheck === true) { const new_isTwoColumnsTheater = fT(new_layoutStatus, LAYOUT_TWO_COLUMNS | LAYOUT_THEATER, 0) let currentIsTheaterPopupChat = new_isTwoColumnsTheater && new_isExpandedChat && isChatPopupedF() if (!currentIsTheaterPopupChat) { enableLivePopupCheck = false; sendToPageScript(document, "tyt-close-popup"); } } } function fixLayoutStatus(x) { const new_isExpandedChat = !(x & LAYOUT_CHATROOM_COLLAPSED) && (x & LAYOUT_CHATROOM) return new_isExpandedChat ? (x | LAYOUT_CHATROOM_EXPANDED) : (x & ~LAYOUT_CHATROOM_EXPANDED); } const wls = new Proxy({ /** @type {number | null} */ layoutStatus: undefined }, { get: function (target, prop) { return target[prop]; }, set: function (target, prop, value) { if (prop == 'layoutStatus') { if (value === 0) { target[prop] = value; return true; } else if (target[prop] === value) { return true; } else { if (!target.layoutStatus_pending) { target.layoutStatus_pending = true; const old_layoutStatus = target[prop]; target[prop] = value; layoutStatusMutex.lockWith(unlock => { target.layoutStatus_pending = false; let new_layoutStatus = target[prop]; if (old_layoutStatus !== new_layoutStatus) { layoutStatusChanged(old_layoutStatus, new_layoutStatus); getDMPromise().then(unlock); } else { unlock(); } }) return true; } } } target[prop] = value; return true; }, has: function (target, prop) { return (prop in target); } }); const svgElm = (w, h, vw, vh, p, m) => `${p}` function isVideoPlaying(video) { return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA; } function wAttr(elm, attr, kv) { if (elm) { if (kv === true) { elm.setAttribute(attr, ''); } else if (kv === false) { elm.removeAttribute(attr); } else if (kv === null) { //; } else if (typeof kv == 'string') { elm.setAttribute(attr, kv); } } } function setTabBtnVisible(tabBtn, toVisible) { let doClassListChange = false; if (tabBtn.getAttribute('tyt-tab-content') === '#tab-comments') { isCommentsTabBtnHidden = !toVisible; if ((hiddenTabsByUserCSS & 2) !== 2) { doClassListChange = true; } } else { doClassListChange = true; } if (doClassListChange) { if (toVisible) { tabBtn.classList.remove("tab-btn-hidden"); } else { tabBtn.classList.add("tab-btn-hidden"); } } } function hideTabBtn(tabBtn) { //console.log('hideTabBtn', tabBtn) let isActiveBefore = tabBtn.classList.contains('active'); setTabBtnVisible(tabBtn, false); if (isActiveBefore) { setToActiveTab(); } } // function hasAttribute(obj, key) { // return obj && obj.hasAttribute(key); // } function isTheater() { const cssElm = es.ytdFlexy; return (cssElm && cssElm.hasAttribute('theater')) } function isFullScreen() { const cssElm = es.ytdFlexy; return (cssElm && cssElm.hasAttribute('fullscreen')) } function isTabExpanded() { const cssElm = es.ytdFlexy; return cssElm && (cssElm.getAttribute('tyt-tab') || '').charAt(0) === '#' } function isChatExpand() { const cssElm = es.ytdFlexy; return cssElm && (cssElm.getAttribute('tyt-chat') || '').charAt(0) === '+' } function isWideScreenWithTwoColumns() { const cssElm = es.ytdFlexy; return (cssElm && cssElm.hasAttribute('is-two-columns_')) } function isAnyActiveTab() { return document.querySelector('#right-tabs .tab-btn.active') !== null } // function isAnyActiveTab2() { // return document.querySelectorAll('#right-tabs .tab-btn.active').length > 0 // } function isEngagementPanelExpanded() { //note: not checking the visual elements const cssElm = es.ytdFlexy; return (cssElm && +cssElm.getAttribute('tyt-ep-visible') > 0) } function isDonationShelfExpanded() { const cssElm = es.ytdFlexy; return (cssElm && cssElm.hasAttribute('tyt-donation-shelf')) } const engagementIdMap = new Map(); let engagementIdNext = 1; // max 1 << 62 function engagement_panels_() { let res = []; // let shownRes = []; let v = 0; // let k = 1; // let count = 0; for (const ePanel of document.querySelectorAll( `ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])` )) { let targetId = ePanel.getAttribute('target-id') if (typeof targetId !== 'string') continue; let eId = engagementIdMap.get(targetId) if (!eId) { engagementIdMap.set(targetId, eId = engagementIdNext) if (engagementIdNext === (1 << 62)) { engagementIdNext = 1; console.warn('engagementId reached 1 << 62') } else { engagementIdNext = engagementIdNext << 1; } } // console.log(55,eId, targetId) let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN let k = eId switch (visibility) { case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED': v |= k; // count++; // shownRes.push(ePanel) res.push({ ePanel, k, visible: true }); break; case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN': res.push({ ePanel, k, visible: false }); break; default: res.push({ ePanel, k, visible: false }); } //k = k << 1; } return { list: res, value: v }; // return { list: res, value: v, count: count, shownRes }; } function ytBtnOpenEngagementPanel(/** @type {number | string} */ panel_id) { // console.log(panel_id) if (typeof panel_id == 'string') { panel_id = panel_id.replace('#engagement-panel-', ''); panel_id = parseInt(panel_id); } if (panel_id >= 0) { } else return false; let panels = engagement_panels_(); // console.log(panels) let actions = [] for (const { ePanel, k, visible } of panels.list) { if ((panel_id & k) === k) { if (!visible) { actions.push({ panelId: ePanel.getAttribute('target-id'), toShow: true }) } } else { if (visible) { actions.push({ panelId: ePanel.getAttribute('target-id'), toHide: true }) } } } if (actions.length > 0) { // console.log(4545,actions) const _actions = actions; scriptletDeferred.debounce(() => { document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', { detail: _actions })); }); } actions = null; } function ytBtnCloseEngagementPanel(/** @type {HTMLElement} */ 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 openDonationShelf() { if (!isDonationShelfExpanded()) { let btn = document.querySelector('#tyt-donation-shelf-toggle-btn') if (btn) { btn.click(); return true; } } return false; } function closeDonationShelf() { if (isDonationShelfExpanded()) { let btn = document.querySelector('#tyt-donation-shelf-toggle-btn') if (btn) { btn.click(); return true; } } return false; } 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 = _querySelector.call(button, 'div.yt-spec-touch-feedback-shape') || _querySelector.call(button, '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 = _querySelector.call(button, 'div.yt-spec-touch-feedback-shape') || _querySelector.call(button, 'ytd-toggle-button-renderer'); if (button) button.click(); } } async function makeVideosAutoLoad2() { let sVideosList = document.querySelector('ytd-watch-flexy #tab-videos [placeholder-videos]'); if (!sVideosList) return null; //let ab = sVideosList.getAttribute('tabview-videos-autoload') await Promise.resolve(0); let endPosDOM = document.querySelector('tabview-view-videos-endpos') if (endPosDOM) endPosDOM.remove(); // just in case endPosDOM = document.createElement('tabview-view-videos-endpos') insertAfterTo(endPosDOM, sVideosList); await Promise.resolve(0); //sVideosList.setAttribute('tabview-videos-autoload', '1') // _console.log(9333) if (!sVideosITO) { sVideosITO = new IntersectionObserver((entries) => { if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) return; // _console.log(9334, entries) if (entries.length !== 1) return; if (entries[0].isIntersecting !== true) return; let elm = ((entries[0] || 0).target || 0); if (!elm) return; elm = null; entries = null; new Promise(resolve => { // compatibile with Search While Watching Video let isSearchGeneratedWithHiddenContinuation = !!document.querySelector('#related.style-scope.ytd-watch-flexy ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy ytd-compact-video-renderer.yt-search-generated.style-scope.ytd-item-section-renderer ~ ytd-continuation-item-renderer.style-scope.ytd-item-section-renderer[hidden]'); if (isSearchGeneratedWithHiddenContinuation) return; // native YouTube coding use different way to handle custom videos, unknown condition for the continutation loading. let isOtherChipSelected = !!document.querySelector('ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy yt-chip-cloud-renderer.style-scope.yt-related-chip-cloud-renderer yt-chip-cloud-chip-renderer.style-scope.yt-chip-cloud-renderer[aria-selected="false"] ~ [aria-selected="true"]') if (isOtherChipSelected) return; getDMPromise().then(resolve); // delay required to allow YouTube generate the continuation elements }).then(() => { let res = setVideosTwoColumns(2 | 4, true) // _console.log(9335, res) if (res.m2 && res.m3) { const m4 = closestDOM.call(res.m2, 'ytd-continuation-item-renderer'); if (m4) { const m5 = _querySelector.call(m4, 'ytd-button-renderer.style-scope.ytd-continuation-item-renderer, yt-button-renderer.style-scope.ytd-continuation-item-renderer'); // YouTube coding bug - correct is 'ytd-button-renderer'. If the page is redirected under single column mode, the tag become 'yt-button-renderer' // under 'yt-button-renderer', the const m6 = querySelectorFromAnchorX(m5, 'button.yt-spec-button-shape-next--call-to-action'); // main // _console.log(9337, m4, m5, m6) if (m6) { m6.click() // generic solution } else if (m5) { m5.click(); // not sure } else { m4.dispatchEvent(new Event('yt-service-request-sent-button-renderer')); // only for correct YouTube coding } } } res = null; }); }, { rootMargin: `0px`, // refer to css margin-top:-30vh threshold: [0] }) sVideosITO.observe(endPosDOM); } else { sVideosITO.disconnect(); sVideosITO.observe(endPosDOM); } } function fixTabs() { scriptletDeferred.debounce(() => { // if(document.documentElement.hasAttribute('p355')) return; if (!scriptEnable) return; let queryElement = document.querySelector('*:not(#tab-videos) > #related.ytd-watch-flexy > ytd-watch-next-secondary-results-renderer'); let isRelocated = !!queryElement; if (isRelocated) { // if(1885) return; // _console.log(3202, 2) let relatedElm = closestDOM.call(queryElement, '#related.ytd-watch-flexy'); // NOT NULL let right_tabs = document.querySelector('#right-tabs'); // can be NULL let tab_videos = querySelectorFromAnchorX(right_tabs, "#tab-videos"); // can be NULL if (tab_videos !== null) { // _console.log(3202, 4) let target_container = document.querySelector('ytd-watch-flexy:not([is-two-columns_]) #primary-inner.ytd-watch-flexy, ytd-watch-flexy[is-two-columns_] #secondary-inner.ytd-watch-flexy') if (target_container) elementAppend.call(target_container, right_tabs) // last-child elementAppend.call(tab_videos, relatedElm); // no any other element set these attr. only init / relocation relatedElm.setAttribute('placeholder-for-youtube-play-next-queue', '') relatedElm.setAttribute('placeholder-videos', '') makeVideosAutoLoad2(); } } /** @type {HTMLElement | null} */ check1885(); if (!chatController.allowChatControl) return; let chatroom = null; if (chatroom = document.querySelector('ytd-live-chat-frame#chat')) { const container = chatroom.parentNode.id === 'chat-container' ? chatroom.parentNode : chatroom; let pHolderElm = document.querySelector('tabview-view-pholder[data-positioner="before|#chat"]'); if (!pHolderElm || pHolderElm.nextElementSibling !== container) { if (pHolderElm) pHolderElm.remove(); // if (1885 && document.body.hasAttribute('data-ytlstm-theater-mode')) { // } else if (document.querySelector('.YouTubeLiveFilledUpView')) { // no relocation } else { let rightTabs = document.querySelector('#right-tabs'); // console.log(28784, rightTabs.previousSibling) if (rightTabs && rightTabs.previousSibling !== container) { const parentNode = rightTabs.parentNode; let useDefault = true; if (parentNode === container.parentNode && parentNode instanceof HTMLElement && typeof docFragmentCreate === 'function' && typeof docFragmentAppend === 'function') { const previousNodes = []; const nextNodes = []; let dv = 0; for (let node = parentNode.firstChild; node instanceof Node; node = node.nextSibling) { if (node === rightTabs) { dv |= 1; } else if (node === container) { dv |= 2; } else { if (node instanceof HTMLIFrameElement || (node instanceof Element && node.querySelector('iframe'))) { dv |= 4; break; } if (dv & 1) { nextNodes.push(node); } else { previousNodes.push(node); } } } if (dv === 3 && (previousNodes.length + nextNodes.length) < 8000) { if (previousNodes.length > 0) { const nw1 = docFragmentCreate.call(document); docFragmentAppend.call(nw1, ...previousNodes); parentNode.insertBefore(nw1, parentNode.firstChild); } const nw2 = docFragmentCreate.call(document); docFragmentAppend.call(nw2, rightTabs, ...nextNodes); parentNode.appendChild(nw2); // parentNode.replaceChildren(...previousNodes, container, rightTabs, ...nextNodes); useDefault = false; } } if (useDefault) { // console.log(28784, [...parentNode.childNodes]) insertBeforeTo(container, rightTabs); // const _chatroom = chatroom; // _chatroom && (async ()=>{ // await scriptletDeferred.d(); // if (typeof webkitRequestAnimationFrame === 'function' && typeof mozRequestAnimationFrame === 'undefined') { // await new Promise(r => setTimeout(r, 650)); // 650ms to avoid Brave Bug // } // // sendToPageScript(_chatroom, "tabview-chat-call-urlchange"); // })(); } } } if (!pHolderElm) { pHolderElm = document.createElement('tabview-view-pholder'); pHolderElm.setAttribute('data-positioner', 'before|#chat'); } insertBeforeTo(pHolderElm, container) } } }) } async function isDocumentInFullScreenMode() { return document.fullscreenElement !== null; } // let zatt = Date.now(); const dbId = `ep5wbmokDB-${instanceId}` async function tabviewEnergizedFn() { let db; const indexedDB = window.indexedDB; try { let dbReq = indexedDB.open(dbId, 3); db = await new Promise((resolve, reject) => { dbReq.onupgradeneeded = function (event) { /** @type {IDBDatabase} */ const db = event.target.result; let objectStore = db.createObjectStore("customers", { keyPath: "ssn" }); objectStore.createIndex("name", "name", { unique: false }); objectStore.createIndex("email", "email", { unique: true }); objectStore = null; db.deleteObjectStore("customers"); resolve(db); } dbReq.onsuccess = function (event) { const db = event.target.result; resolve(db); } dbReq.onerror = function () { reject(); } }); } finally { if (db) db.close(); } try { let request = indexedDB.deleteDatabase(dbId); await new Promise((resolve, reject) => { request.onsuccess = function (event) { resolve(1); }; request.onerror = function (event) { resolve(-1); }; request.onblocked = function (event) { resolve(-2); }; }); } catch (e) { } postMessage({ tabviewEnergized: true }, 'https://www.youtube.com'); // post message to make alive } async function energizedByMediaTimeUpdate() { const isFullscreen = await isDocumentInFullScreenMode(); if (isFullscreen) return; // force browser to load the videostream during playing (primarily for music videos) // both background and foreground _updateTimeAccum++; if (_updateTimeAccum >= 88000000 && (_updateTimeAccum % 88000000) === 0) _updateTimeAccum = 0; if ((_updateTimeAccum + _viTimeNum) % 11 === 0) { // console.log(document.querySelector('video').currentTime) // 2.55, 2.64, 3.12, ... // about 2.66s if (_viTimeNum > 208) { _viTimeNum = 200; _updateTimeAccum = (_updateTimeAccum % 8) + 1; // reset to 1 ~ 8 } document.head.dataset.viTime = `${_viTimeNum + 1}`; await Promise.resolve(0) _viTimeNum = +document.head.dataset.viTime || 0; } } function autoCompletePosCreate() { let positioner = document.createElement("tabview-view-autocomplete-pos"); let oldPositioner = document.querySelector("tabview-view-autocomplete-pos"); if (oldPositioner) oldPositioner.remove(); return positioner } function handlerAutoCompleteExist() { // Youtube - Search While Watching Video if (document.querySelector('autocomplete-holder')) return; /** @type {HTMLElement} */ let searchBox, autoComplete; searchBox = this; this.removeEventListener('tyt-autocomplete-sc-exist', handlerAutoCompleteExist, false) let domId = this.getAttribute('data-autocomplete-results-id') autoComplete = document.querySelector(`[data-autocomplete-input-id="${domId}"]`) if (!domId || !searchBox) return; let positioner = nodePrevSibling(searchBox); if (!positioner || positioner.nodeName.toLowerCase() !== "tabview-view-autocomplete-pos") { positioner = autoCompletePosCreate(); insertBeforeTo(positioner, searchBox); } prependTo(autoComplete, positioner); setupSearchBox(searchBox, positioner); } async function setupSearchBox(searchBox, positioner) { let h = searchBox.offsetHeight + 'px' positioner.style.setProperty('--tyt-swwv-searchbox-h', h) mtf_autocomplete_search() } function mtf_autocomplete_search() { // Youtube - Search While Watching Video /** @type {HTMLElement | null} */ const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; const autocomplete = _querySelector.call(ytdFlexyElm, '[placeholder-for-youtube-play-next-queue] #suggestions-search-container tabview-view-autocomplete-pos > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])') if (autocomplete) { const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search') if (searchBox) { const rAutoComplete = mWeakRef(autocomplete); function setVisible(autocomplete, b) { autocomplete.style.display = (b ? 'block' : 'none'); } function isContentNotEmpty(searchbox, autocomplete) { return (searchbox.value || '').length > 0 && (autocomplete.textContent || '').length > 0; } nodeParent(autocomplete).setAttribute('position-fixed-by-tabview-youtube', ''); autocomplete.setAttribute('position-fixed-by-tabview-youtube', ''); autocomplete.setAttribute('userscript-scrollbar-render', '') //let cancelClickToggle = false; if (!searchBox.hasAttribute('is-set-click-to-toggle')) { searchBox.setAttribute('is-set-click-to-toggle', '') searchBox.addEventListener('click', function () { Promise.resolve(0).then(() => { const autocomplete = kRef(rAutoComplete); if (!autocomplete) return; const isNotEmpty = isContentNotEmpty(this, autocomplete); if (isNotEmpty) { let elmVisible = isDOMVisible(autocomplete); if (elmVisible) { setVisible(autocomplete, false) } else { setVisible(autocomplete, true) } } }) }, bubblePassive) let cacheScrollIntoView = null; let rafXC = 0; searchBox.addEventListener('keydown', function (evt) { //cancelClickToggle = true; switch (evt.code) { case 'ArrowUp': case 'ArrowDown': let t = Date.now(); if (rafXC === 0) { getRAFPromise().then(() => { rafXC = 0; let d = Date.now(); if (d - t > 300) return; const autocomplete = kRef(rAutoComplete); if (!autocomplete) return; let selected = _querySelector.call(autocomplete, '.autocomplete-suggestion.selected'); let bool = selected && selected !== cacheScrollIntoView; cacheScrollIntoView = selected; if (bool) { try { selected.scrollIntoView({ block: "nearest", inline: "nearest" }); } catch (e) { } } }); rafXC = 1; } default: // } }, bubblePassive) searchBox.addEventListener('tyt-autocomplete-suggestions-change', function (evt) { //cancelClickToggle = true; if (evt.target !== document.activeElement) return; getDMPromise().then(() => { const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${this.getAttribute('data-autocomplete-results-id')}"]`); if (!autocomplete) return; const isNotEmpty = isContentNotEmpty(this, autocomplete); if (isNotEmpty) { // dont detect visibility; just set to visible setVisible(autocomplete, true); } }); }, bubblePassive) } } } } const insertBeforeTo = HTMLElement.prototype.before ? (elm, target) => { if (!target || !elm) return null; // using before HTMLElement.prototype.before.call(target, elm); return true; } : (elm, target) => { if (!target || !elm) return null; // using insertBefore try { HTMLElement.prototype.insertBefore.call(nodeParent(target), elm, target); return true; } catch (e) { console.log('element insert failed in old browser CE') } return false; } const insertAfterTo = HTMLElement.prototype.after ? (elm, target) => { if (!target || !elm) return null; // using after HTMLElement.prototype.after.call(target, elm); return true; } : (elm, target) => { if (!target || !elm) return null; // using insertBefore try { HTMLElement.prototype.insertBefore.call(nodeParent(target), elm, nodeNextSibling(target)); return true; } catch (e) { console.log('element insert failed in old browser CE') } return false; } const prependTo = HTMLElement.prototype.prepend ? (elm, target) => { if (!target || !elm) return null; // using prepend HTMLElement.prototype.prepend.call(target, elm); return true; } : (elm, target) => { if (!target || !elm) return null; // using insertBefore try { HTMLElement.prototype.insertBefore.call(target, elm, nodeFirstChild(target)); return true; } catch (e) { console.log('element insert failed in old browser CE') } return false; } const appends = HTMLElement.prototype.append ? (target, ...args) => { HTMLElement.prototype.append.call(target, ...args); return true; } : (target, ...args) => { for (const s of args) { target.appendChild(s) } return true; } function getWrapper(wrapperId) { let wrapper = document.getElementById(wrapperId); if (!wrapper) { wrapper = document.createElement('div'); wrapper.id = wrapperId; } return wrapper; } // continuous check for element relocation // fired at begining & window resize, etc // might moved to #primary function mtf_append_playlist(/** @type {HTMLElement | null} */ playlist) { if (playlist === null) { playlist = document.querySelector('ytd-watch-flexy[playlist] *:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer.style-scope.ytd-watch-flexy#playlist:not(.ytd-miniplayer)'); // this playlist is highly possible to have '#items' if (!playlist) return; } /** @type {HTMLElement | null} */ const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; let items = _querySelector.call(playlist, "*:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer#playlist:not(.ytd-miniplayer) #items.ytd-playlist-panel-renderer:not(:empty)"); if (items !== null && playlist.nodeName.toUpperCase() === 'YTD-PLAYLIST-PANEL-RENDERER') { let tab_list = document.querySelector("#tab-list"); if (!tab_list) return; let w = getWrapper("tabview-playlist-wrapper"); let docFrag = new DocumentFragment(); // avoid immediate reflow for append playlist before append to tab_list docFrag.appendChild(w); elementAppend.call(w, playlist); elementAppend.call(tab_list, docFrag); docFrag = null; w = null; } } function getCountHText(elm) { return `${pageFetchedDataVideoId || 0}...${elm.textContent}` } function mtf_fix_collapsible_playlist() { // just in case the playlist is collapsed let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist') if (playlist && playlist.matches('[collapsed], [collapsible]')) { const domElement = playlist; playlist = null; // if(!domElement.parentElement || domElement.nodeType!==1) return; // not working in pseudo custom element - parentNode = documentFragment const tablist = closestDOM.call(domElement, 'ytd-watch-flexy #tab-list') if (!tablist || tablist.nodeType !== 1) return; // checking whether it is still on the page if (domElement.hasAttribute('collapsed')) wAttr(domElement, 'collapsed', false); if (domElement.hasAttribute('collapsible')) wAttr(domElement, 'collapsible', false); } } // content fix - info & playlist // fired at begining, and keep for in case any change async function mtf_fix_details() { if (!scriptEnable) return 0; // in case await scriptletDeferred.d(); await Promise.all([ Promise.resolve().then(() => { let contentToggleBtn = document.querySelector('ytd-watch-flexy #tab-info ytd-expander tp-yt-paper-button#less.ytd-expander:not([hidden]), #tab-info ytd-expander tp-yt-paper-button#more.ytd-expander:not([hidden])'); if (contentToggleBtn) { const domElement = contentToggleBtn; contentToggleBtn = null; // if(!domElement.parentElement) return; // not working in pseudo custom element - parentNode = documentFragment const expander = closestDOM.call(domElement, 'ytd-watch-flexy #tab-info ytd-expander') if (!expander || expander.nodeType !== 1) return; // checking whether it is still on the page if (expander.style.getPropertyValue('--ytd-expander-collapsed-height')) { expander.style.setProperty('--ytd-expander-collapsed-height', '') } sendToPageScript(expander, "tabview-expander-config"); } }), Promise.resolve().then(() => { let strcturedInfo = document.querySelector('ytd-watch-flexy #tab-info ytd-structured-description-content-renderer.style-scope.ytd-video-secondary-info-renderer') if (strcturedInfo) { const isHidden = closestDOM.call(strcturedInfo, '[hidden]'); if (isHidden) { if (strcturedInfo.hasAttribute('hidden')) strcturedInfo.removeAttribute('hidden'); const descriptionElement = closestDOM.call(strcturedInfo, 'ytd-expander.style-scope.ytd-video-secondary-info-renderer #description.style-scope.ytd-video-secondary-info-renderer'); if (descriptionElement && descriptionElement.hasAttribute('hidden')) { descriptionElement.removeAttribute('hidden'); } } isHidden && setTimeout(() => { let e = closestDOM.call(strcturedInfo, 'ytd-watch-flexy #tab-info ytd-expander'); if (!e) return; let s = _querySelectorAll.call(e, '#tab-info .more-button.style-scope.ytd-video-secondary-info-renderer[role="button"]'); if (s.length === 1) { let sp = nodeParent(s[0]); if (sp.nodeName.toUpperCase() === 'TP-YT-PAPER-BUTTON') { sp.click(); } } }, 300); } }), Promise.resolve().then(() => { let subscribersCount = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #owner #owner-sub-count'); if (subscribersCount) { if (!subscribersCount.hasAttribute('title')) { // assume YouTube native coding would not implement [title] let ytdWatchMetaDataElm = closestDOM.call(subscribersCount, 'body #primary.ytd-watch-flexy #below ytd-watch-metadata:not([tabview-uploader-hover])'); if (ytdWatchMetaDataElm) { ytdWatchMetaDataElm.setAttribute('tabview-uploader-hover', '') let _h = 0; ytdWatchMetaDataElm.addEventListener('transitionend', function (evt) { // no css selector rule required; no delay js function call required let selection = evt.propertyName === 'background-position-y' ? 1 : evt.propertyName === 'background-position-x' ? 2 : 0; if (selection && evt.target) { let cssRoot = this; // no querySelector is required if (cssRoot.classList.contains('tabview-uploader-hover')) { if (evt.target.id !== 'owner') return; cssRoot.classList.toggle('tabview-uploader-hover', false); } } if (selection === 1) { // string comparision only // If the cursor initially stayed at the owner info area, // the mechanism will be broken // so implement the true leave detection at their parent let isHover = evt.elapsedTime < 0.03; // 50ms @normal; 10ms @hover; let cssRoot = this; // no querySelector is required if (!isHover) { // 50ms is slowest than sub element leave effect if (_h > 0) cssRoot.classList.toggle('tabview-uploader-hover', false) // even the order is incorrect, removal of class is safe. _h = 0; // in case } else if (isHover) { // 10ms is faster than sub element hover effect // no removal of class in case browser's transition implemention order is incorrect _h = 0; // in case } } else if (selection === 2) { // string comparision only //from one element to another element; hover effect of 2nd element transition end first. // _h: 0 -> 1 -> 2 -> 1 let isHover = evt.elapsedTime < 0.03; // 40ms @normal; 20ms @hover; if (isHover) _h++; else _h--; let cssRoot = this; // no querySelector is required if (_h <= 0) { cssRoot.classList.toggle('tabview-uploader-hover', false) _h = 0; // in case } else if (_h === 1) { cssRoot.classList.toggle('tabview-uploader-hover', true) } } }, capturePassive) // capture the hover effect inside the cssRoot } } subscribersCount.setAttribute('title', subscribersCount.textContent); // set at every page update } }) ]); } const innerCommentsFuncs = [ // comments function () { let elm = kRef(this.elm); // _console.log(2907, 1, !!elm) if (!elm) return; let span = document.querySelector("span#tyt-cm-count") // let r = '0'; // let txt = elm.textContent // if (typeof txt == 'string') { // let m = txt.match(/[\d\,\.\s]+/) // if (m) { // let d = +m[0].replace(/\D+/g, ''); // let ds = d.toLocaleString(document.documentElement.lang); // let rtxt = txt.replace(ds, '') // if (rtxt !== txt && !/\d/.test(rtxt)) { // r = ds; // } // } // } if (span) { let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]') if (tab_btn) tab_btn.setAttribute('loaded-comment', 'normal'); sendToPageScript(document, 'tyt-update-cm-count'); // span.textContent = r; } setCommentSection(1); m_last_count = getCountHText(elm); // _console.log(2907, 2, m_last_count) return true; }, // message function () { let elm = kRef(this.elm); // _console.log(2907, 2, !!elm) if (!elm) return; let span = document.querySelector("span#tyt-cm-count") if (span) { let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]') if (tab_btn) tab_btn.setAttribute('loaded-comment', 'message') span.textContent = '\u200B'; } setCommentSection(1); m_last_count = getCountHText(elm); // _console.log(2907, 2, m_last_count) return true; } ] let innerDOMCommentsCountTextCache = null; /** * * @param {boolean} [requireResultCaching] * @returns */ function innerDOMCommentsCountLoader(requireResultCaching) { // independent of tabs initialization // f() is executed after tabs being ready /** @type {HTMLElement | null} */ const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; // _console.log(3434, pageType) if (pageType !== 'watch') return; /** @type {HTMLElement | null} */ const qtElm = document.querySelector('ytd-comments#comments'); if (!qtElm) return; /** @type {Array} */ const qmElms = _querySelectorAll.call(qtElm, '#count.ytd-comments-header-renderer, ytd-item-section-renderer.ytd-comments#sections #header ~ #contents > ytd-message-renderer.ytd-item-section-renderer'); const eTime = +`${Date.now() - mTime}00`; let res = new Array(qmElms.length); res.newFound = false; let ci = 0; let latest = -1; let retrival = cmTime; cmTime = eTime; for (const qmElm of qmElms) { let mgz = 0 if (qmElm.id === 'count') { //#count.ytd-comments-header-renderer mgz = 1; } else if ((qmElm.textContent || '').trim()) { //ytd-message-renderer.ytd-item-section-renderer mgz = 2; // it is possible to get the message before the header generation. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8 } if (mgz > 0) { let lastUpdate = loadedCommentsDT.get(qmElm) || 0; let diff = retrival - lastUpdate // _console.log(2907, diff) let isNew = (diff > 4 || diff < -4); if (!isNew) { loadedCommentsDT.set(qmElm, eTime); } else { loadedCommentsDT.set(qmElm, eTime + 1); res.newFound = true; latest = ci; } res[ci] = { elm: mWeakRef(qmElm), isNew: isNew, isLatest: false, //set afterwards f: innerCommentsFuncs[mgz - 1] } if (DEBUG_LOG) { res[ci].status = mgz; res[ci].text = qmElm.textContent; } ci++; } } if (res.length > ci) res.length = ci; if (latest >= 0) { res[latest].isLatest = true; let elm = kRef(res[latest].elm); if (elm) innerDOMCommentsCountTextCache = elm.textContent; } else if (res.length === 1) { let qmElm = kRef(res[0].elm); let t = null; if (qmElm) { let t = qmElm.textContent; if (t !== innerDOMCommentsCountTextCache) { loadedCommentsDT.set(qmElm, eTime + 1); res.newFound = true; res[0].isNew = true; latest = 0; res[latest].isLatest = true; } innerDOMCommentsCountTextCache = t; } } // _console.log(2908, res, Q.comments_section_loaded) // _console.log(696, res.map(e => ({ // text: kRef(e.elm).textContent, // isNew: e.isNew, // isLatest: e.isLatest // }))) if (requireResultCaching) { resultCommentsCountCaching(res); } return res; } function restoreFetching() { if (mtf_forceCheckLiveVideo_disable === 2) return; const ytdFlexyElm = es.ytdFlexy; if (!ytdFlexyElm) return; // _console.log(2901) if ((ytdFlexyElm.getAttribute('tyt-comments') || '').indexOf('K') >= 0) return; // _console.log(2902) let visibleComments = _querySelector.call(ytdFlexyElm, 'ytd-comments#comments:not([hidden])') if (!visibleComments) return; // _console.log(2903) ytdFlexyElm.setAttribute('tyt-comments', 'Kz'); const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]'); let span = _querySelector.call(tabBtn, 'span#tyt-cm-count'); tabBtn.removeAttribute('loaded-comment') span.innerHTML = createHTML(''); if (tabBtn) { setTabBtnVisible(tabBtn, true); } // _console.log(2905) } function checkAndMakeNewCommentFetch() { if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) { fetchCounts.new.f(); fetchCounts.fetched = true; fetchCommentsFinished(); // _console.log(9972, 'fetched = true') } } function onCommentsReady(e) { // e => from commentsHeaderAppended let b = false; if (e && mtf_forceCheckLiveVideo_disable !== 2 && document.querySelector('ytd-comments#comments:not([hidden])')) { // 'YTD-COMMENTS-HEADER-RENDERER', native DOM setCommentSection(0); checkAndMakeNewCommentFetch(); b = true; } _onCommentsReady(b); } function _onCommentsReady(b) { if (mtf_forceCheckLiveVideo_disable !== 2) { if (document.querySelector('ytd-comments#comments').hasAttribute('hidden')) { // unavailable but not due to live chat _disableComments(); } else if (Q.comments_section_loaded === 0) { if (b || (comments_loader & 3) === 3) { getFinalComments(); } } } } const resultCommentsCountCaching = (res) => { // update fetchCounts by res // indepedent of previous state of fetchCounts // _console.log(2908, 10, res) if (!res) return; fetchCounts.count = res.length; //if(fetchCounts.new && !document.documentElement.contains(fetchCounts.new.elm)) fetchCounts.new = null; //if(fetchCounts.base && !document.documentElement.contains(fetchCounts.base.elm)) fetchCounts.base = null; if (fetchCounts.new) return; let newFound = res.newFound; if (!newFound) { if (res.length === 1) { fetchCounts.base = res[0]; return false; } } else if (res.length === 1) { fetchCounts.new = res[0]; return true; } else if (res.length > 1) { for (const entry of res) { if (entry.isLatest === true && entry.isNew) { fetchCounts.new = entry; return true; } } } } const domInit_comments = () => { let comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`); if (!comments) return; // once per {ytd-comments#comments} detection const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; if (mtoVisibility_Comments.bindElement(comments)) { mtoVisibility_Comments.observer.check(9); } }; const fixLiveChatToggleButtonDispatchEvent = () => { sendToPageScript(document, "tabview-fix-live-chat-toggle-btn"); } // let chatroomAttrCollapseCount = 0; const FP = { mtf_attrPlaylist: (attrName, newValue) => { //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single //::attr ~ hidden //console.log(1210) // _console.log(21311) if (!scriptEnable) return; if (pageType !== 'watch') return; /** @type {HTMLElement|null} */ let cssElm = es.ytdFlexy; if (!cssElm) return; // _console.log(21312) let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist'); // can be null if it is manually triggered let isAnyPlaylistExist = playlist && !playlist.hasAttribute('hidden'); const tabBtn = document.querySelector('[tyt-tab-content="#tab-list"]'); //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden')) if (tabBtn) { //console.log('attr playlist changed') let isPlaylistTabHidden = tabBtn.classList.contains('tab-btn-hidden') if (isPlaylistTabHidden && isAnyPlaylistExist) { //console.log('attr playlist changed - no hide') tabBtn.classList.remove("tab-btn-hidden"); } else if (!isPlaylistTabHidden && !isAnyPlaylistExist) { //console.log('attr playlist changed - add hide') hideTabBtn(tabBtn); } } /* visible layout for triggering hidden removal */ }, mtf_attrComments: (attrName, newValue) => { //attr mutation checker - {ytd-comments#comments} \single //::attr ~ hidden // *** consider this can happen immediately after pop state. timeout / interval might clear out. renderDeferred.resolved && innerDOMCommentsCountLoader(true); // this is triggered by mutationobserver, the comment count update might have ouccred if (pageType !== 'watch') return; let comments = document.querySelector('ytd-comments#comments') const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]'); if (!comments || !tabBtn) return; let isCommentHidden = comments.hasAttribute('hidden') //console.log('attr comments changed') const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; if (mtf_forceCheckLiveVideo_disable === 2) { } else if (!isCommentHidden) { ytdFlexyElm.setAttribute('tyt-comments', 'Kv'); if (!fetchCounts.fetched) { emptyCommentSection(); } //_console.log(9360, 71); setTabBtnVisible(tabBtn, true); //if contains } else if (isCommentHidden) { ytdFlexyElm.setAttribute('tyt-comments', 'Kh'); if (pageType === 'watch' && Q.comments_section_loaded === 1) { emptyCommentSection(); // _console.log(9360, 72); } } }, mtf_attrChatroom: () => { //attr mutation checker - {ytd-live-chat-frame#chat} \single //::attr ~ collapsed const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; if (pageType !== 'watch') return; setToggleBtnTxt(); layoutStatusMutex.lockWith(unlock => { const chatBlock = document.querySelector('ytd-live-chat-frame#chat'); /** @type {HTMLElement | null} */ const cssElm = es.ytdFlexy; if (!chatBlock || !cssElm) { unlock(); return; } if (pageType !== 'watch') { unlock(); return; } let newAttrV = ''; //mtf_attrChatroom => chat exist => tyt-chat non-null let isCollapsed = !!chatBlock.hasAttribute('collapsed'); let currentAttr = cssElm.getAttribute('tyt-chat'); if (currentAttr !== null) { //string // [+-]?[az]+[az\$]+ let isPlusMinus = currentAttr.charCodeAt(0) < 46; // 43 OR 45 if (isPlusMinus) newAttrV = currentAttr.substring(1); } if (isCollapsed) newAttrV = `-${newAttrV}`; if (!isCollapsed) newAttrV = `+${newAttrV}`; wAttr(cssElm, 'tyt-chat', newAttrV); if (typeof newAttrV === 'string' && !isCollapsed) lstTab.lastPanel = '#chatroom'; if (!isCollapsed && isAnyActiveTab() && isWideScreenWithTwoColumns() && !isTheater()) { switchTabActivity(null); getDMPromise().then(unlock); } else { unlock(); } if (isCollapsed) { // ++chatroomAttrCollapseCount; // chatBlock.removeAttribute('tyt-iframe-loaded'); chatBlock.classList.remove('tyt-chat-frame-ready'); // console.log(922,1) // buggy; this section might not be correctly executed. // guess no collapse change but still iframe will distory and reload. let btn = document.querySelector('tyt-iframe-popup-btn') if (btn) btn.remove(); } else { const iframe = _querySelector.call(chatBlock, 'body iframe.style-scope.ytd-live-chat-frame#chatframe'); // console.log("iframe.xx",501,iframe) // showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 0B'); if (iframe) Promise.resolve(iframe).then(iframeToVisible); // fix empty } fixLiveChatToggleButtonDispatchEvent(); }) }, mtf_attrEngagementPanel: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => { //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple //::attr ~ visibility const cssElm = es.ytdFlexy; if (!scriptEnable || !cssElm) return; let found = null if (mutations === 9) { found = observer } else { if (document.querySelector('ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]:not([hidden])')) { // do nothing } else { mtoVisibility_EngagementPanel.clear(true) storeLastPanel = null; wAttr(cssElm, 'tyt-ep-visible', false); } return } let nextValue = engagement_panels_().value; let previousValue = +cssElm.getAttribute('tyt-ep-visible') || 0; if (nextValue === 0 || previousValue === nextValue) return cssElm.setAttribute('tyt-ep-visible', nextValue); lstTab.lastPanel = `#engagement-panel-${nextValue}`; storeLastPanel = mWeakRef(found) discardableFn(() => { if (es.storeLastPanel !== found) return layoutStatusMutex.lockWith(unlock => { if (es.storeLastPanel === found && whenEngagemenetPanelVisible()) { getDMPromise().then(unlock); } else { unlock(); } }); }); } } function variableResets() { // reset variables when it is confirmed a new page is loaded lstTab = { lastTab: null, //tab-xxx lastPanel: null, last: null }; scriptEnable = false; ytdFlexy = null; wls.layoutStatus = 0; mtoVisibility_Playlist.clear(true) mtoVisibility_Comments.clear(true) mtoVisibility_Chatroom.clear(true) mtoFlexyAttr.clear(true) mtoBodyAttr.clear(true) mtf_chatBlockQ = null; } function contentExtractor(elm) { if (!(elm instanceof HTMLElement)) { return null; } let m = elm.textContent; let isEmpty = m.trim().length === 0; if (isEmpty) return null; let s = elm.nodeName; let id = elm.id; if (id) s += '#' + id; return s + '\n' + m; } async function checkDuplicatedMetaRecommendation() { let mainContent0 = document.querySelector('#primary.ytd-watch-flexy #above-the-fold.ytd-watch-metadata'); let mainContent1 = document.querySelector('#tab-info ytd-expander > #content'); if (mainContent0 && mainContent1) { const hashedContents = new Set(); for (let s1 = elementNextSibling(mainContent1); s1 instanceof HTMLElement; s1 = elementNextSibling(s1)) { let m = contentExtractor(s1); if (m === null) continue; hashedContents.add(m); } for (let s0 = elementNextSibling(mainContent0); s0 instanceof HTMLElement; s0 = elementNextSibling(s0)) { let m = contentExtractor(s0); if (m !== null && hashedContents.has(m)) { s0.classList.add('tyt-hidden-duplicated-meta'); } else { s0.classList.remove('tyt-hidden-duplicated-meta'); } } hashedContents.clear(); } } const elementMapper = new WeakMap(); const isContentDuplicationCheckAllow = () => { return document.querySelectorAll('[tyt-info-expander-placeholder]').length === 1 && document.querySelectorAll('[tyt-info-expander-content]').length === 1; } let waitForContentReady = new PromiseExternal(); // dummy initial value async function removeContentMismatch() { if (!document.querySelector('#tab-info')) return; const dmysOnPage = document.querySelectorAll('[tyt-info-expander-placeholder]'); if (dmysOnPage.length === 1) { const dmyElm = dmysOnPage[0]; const expander = elementMapper.get(dmyElm); if (!expander) return; for (const s of document.querySelectorAll('[tyt-info-expander-content]')) { if (expander !== s) s.remove(); } if (expander.isConnected === false) { document.querySelector('#tab-info').appendChild(expander); } isContentDuplicationCheckAllow() && waitForContentReady.resolve(); } } function getWord(tag) { return langWords[pageLang][tag] || langWords['en'][tag] || ''; } function getTabsHTML() { const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}${getWord('videos')}`; const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}${getWord('info')}`; const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}${getWord('playlist')}`; let str1 = `
`; let str_fbtns = `
`.replace(/[\r\n]+/g, ''); const str_tabs = [ `${sTabBtnInfo}${str1}${str_fbtns}`, `${svgElm(16, 16, 120, 120, svgComments)}${str1}${str_fbtns}`, `${sTabBtnVideos}${str1}${str_fbtns}`, `${sTabBtnPlayList}${str1}${str_fbtns}` ].join(''); let addHTML = `
${str_tabs}
`; 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'; } // function checkEvtTarget(evt, nodeNames) { // return nodeNames.includes((((evt || 0).target || 0).nodeName || 0)); // } function pageCheck() { // yt-player-updated // yt-page-data-updated // [is-two-columns_] attr changed => layout changed /** @type {HTMLElement | null} */ const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; const rootDom = document.documentElement; rootDom.setAttribute('sxmq8', rootDom.getAttribute('sxmq8') === '1' ? '0' : '1'); console.log('sxmq8 r1', document.documentElement.getAttribute('sxmq8') ); let comments = _querySelector.call(ytdFlexyElm, '#primary.ytd-watch-flexy ytd-watch-metadata ~ ytd-comments#comments'); if (comments) { let tabComments = document.querySelector('#tab-comments'); if (tabComments) { elementAppend.call(tabComments, comments); } } mtf_append_playlist(null); // playlist relocated after layout changed fixTabs(); mtf_autocomplete_search(); } function globalHook(eventType, func) { if (!func) return; const count = (globalHook_hashs[eventType] || 0) + 1; globalHook_hashs[eventType] = count; const s = globalHook_symbols[count - 1] || (globalHook_symbols[count - 1] = Symbol()); document.addEventListener(eventType, function (evt) { if (evt[s]) return; evt[s] = true; Promise.resolve().then(() => { func(evt); }) }, capturePassive) } async function makeHeaderFloat() { if (isMakeHeaderFloatCalled) return; isMakeHeaderFloatCalled = true; await Promise.resolve(0); const [header, headerP, navElm] = await Promise.all([ Promise.resolve().then(() => document.querySelector("#right-tabs header")), Promise.resolve().then(() => document.querySelector("#right-tabs tabview-view-pos-thead")), Promise.resolve().then(() => document.querySelector('#masthead-container, #masthead')) ]); let ito_dt = 0; let ito = new IntersectionObserver((entries) => { let xyStatus = null; //console.log(entries); let xRect = null; let rRect = null; for (const entry of entries) { if (!entry.boundingClientRect || !entry.rootBounds) continue; // disconnected from DOM tree if (!entry.isIntersecting && entry.boundingClientRect.y <= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) { xyStatus = 2; xRect = entry.boundingClientRect; rRect = entry.rootBounds; } else if (entry.isIntersecting && entry.boundingClientRect.y >= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) { xyStatus = 1; xRect = entry.boundingClientRect; rRect = entry.rootBounds; } } let p = wls.layoutStatus; //console.log(document.documentElement.clientWidth) if (xyStatus !== null) { if (xyStatus === 2 && isStickyHeaderEnabled === true) { } else if (xyStatus === 1 && isStickyHeaderEnabled === false) { } else { singleColumnScrolling2(xyStatus, xRect.width, { left: xRect.left, right: rRect.width - xRect.right }); } } let tdt = Date.now(); ito_dt = tdt; setTimeout(() => { if (ito_dt !== tdt) return; if (p !== wls.layoutStatus) singleColumnScrolling(); }, 300) }, { rootMargin: `0px 0px 0px 0px`, threshold: [0] }) ito.observe(headerP) } function setupVideoTitleHover() { let h1 = document.querySelector('#below h1.ytd-watch-metadata yt-formatted-string'); if (h1) { let s = ''; try { if (formatDates && Object.keys(formatDates).length > 0) { function getDurationInfo(bd1, bd2) { let bdd = bd2 - bd1 let hrs = Math.floor(bdd / 3600000) bdd = bdd - hrs * 3600000 let mins = Math.round(bdd / 60000) let seconds = null if (mins < 10 && hrs === 0) { mins = Math.floor(bdd / 60000) bdd = bdd - mins * 60000 seconds = Math.round(bdd / 1000) if (seconds === 0) seconds = null } return { hrs, mins, seconds } } const formatDateResult = getFormatDateResultFunc(); if (formatDates.broadcastBeginAt && formatDates.isLiveNow === false) { let bd1 = new KDate(formatDates.broadcastBeginAt) let bd2 = formatDates.broadcastEndAt ? new KDate(formatDates.broadcastEndAt) : null let isSameDay = 0 if (bd2 && bd1.toLocaleDateString() === bd2.toLocaleDateString()) { isSameDay = 1 } else if (bd2 && +bd2 > +bd1 && bd2 - bd1 < 86400000) { if (bd1.getHours() >= 6 && bd2.getHours() < 6) { isSameDay = 2 } } let durationInfo = getDurationInfo(bd1, bd2) if (isSameDay > 0) { bd2.dayBack = (isSameDay === 2) s = formatDateResult(0x200, { bd1, bd2, isSameDay, durationInfo, formatDates }) } else if (bd2 && isSameDay === 0) { s = formatDateResult(0x210, { bd1, bd2, isSameDay, durationInfo, formatDates }) } } else if (formatDates.broadcastBeginAt && formatDates.isLiveNow === true) { let bd1 = new KDate(formatDates.broadcastBeginAt) s = formatDateResult(0x300, { bd1, formatDates }) } else { if (formatDates.uploadDate) { if (formatDates.publishDate && formatDates.publishDate !== formatDates.uploadDate) { s = formatDateResult(0x600, { formatDates }) } else { s = formatDateResult(0x610, { formatDates }) } } else if (!formatDates.uploadDate && formatDates.publishDate) { s = formatDateResult(0x700, { formatDates }) } } } } catch (e) { s = ''; } if (s) { h1.setAttribute('data-title-details', s) } else { h1.removeAttribute('data-title-details') } } } const skPlayListDataReassign = ControllerID(); function checkPlaylistForInitialization() { // if the page url is with playlist; renderer event might not occur. // playlist already added to dom; this is to set the visibility event and change hidden status let m_playlist = document.querySelector(`#tab-list ytd-playlist-panel-renderer#playlist:not([o3r-${sa_playlist}])`) // once per {ytd-playlist-panel-renderer#playlist} detection // _console.log(3902, !!m_playlist) const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) { } else if (m_playlist) { if (mtoVisibility_Playlist.bindElement(m_playlist)) { mtoVisibility_Playlist.observer.check(9); // delay check required for browser bug - hidden changed not triggered } let playlist_wr = mWeakRef(m_playlist); scriptletDeferred.debounce(() => { let m_playlist = kRef(playlist_wr); playlist_wr = null; if (m_playlist && m_playlist.isConnected === true) { sendToPageScript(m_playlist, "tabview-yt-data-reassign"); } m_playlist = null; }, skPlayListDataReassign) m_playlist = null; } FP.mtf_attrPlaylist(); Promise.resolve(0).then(() => { // ['tab-btn', 'tab-btn', 'tab-btn active', 'tab-btn tab-btn-hidden'] // bug const ytdFlexyElm = es.ytdFlexy; if (!scriptEnable || !ytdFlexyElm) return; if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tyt-tab') + '').indexOf('#tab-') === 0 && location.pathname === '/watch') { if (/[\?\&]list=[\w\-\_]+/.test(location.search)) { if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list'; } else if (/[\?\&]lc=[\w\-\_]+/.test(location.search)) { if (setToActiveTab('#tab-comments')) switchTabActivity_lastTab = '#tab-comments'; } } }) Promise.resolve(0).then(() => { mtf_fix_collapsible_playlist(); }); } const _pageBeingInit = function () { psId.inc(); // add one fetchCounts = { base: null, new: null, fetched: false, count: null } pageFetchedDataVideoId = null; chatroomDetails = null; } const pageBeingInit = function () { // trigger at pageSeq1/2: yt-navigate-start / yt-navigate-cache / yt-navigate-redirect / yt-page-data-fetched // call regardless pageType // run once on / before pageSeq2 (yt-page-data-fetched) const rootDom = document.documentElement; // rootDom.setAttribute('sxmq7', rootDom.getAttribute('sxmq7') === '1' ? '0' : '1'); rootDom.setAttribute('sxmq8', rootDom.getAttribute('sxmq8') === '1' ? '0' : '1'); rootDom.removeAttribute('pnzgu'); console.log('sxmq8 r2', document.documentElement.getAttribute('sxmq8') ); infoContentDS = 0; renderIdentifier.inc(); // add 1 renderDeferred.reset(); // clear quene of pending renderDeferreds if (!scriptletDeferred.resolved && (firstLoadStatus & 2) === 2) { // insert on first pageBeingInit(), regardless pageType firstLoadStatus -= 2; script_inject_js1.inject(); } else if (pageRendered === 2) { // (pageRendered = 2 after pageSeq2) // reset on 2nd+ pageBeingInit(), regardless pageType pageRendered = 0; let elmPL = document.querySelector('tabview-view-ploader'); if (elmPL) elmPL.remove(); // tabview-view-ploader is appended in pageBeingFetched (pageSeq2: yt-page-data-fetched) } if (tabsDeferred.resolved) { comments_loader = 1; tabsDeferred.reset(); // tabsDeferred.resolve() again in yt-navigate-finish[page=watch] if ((firstLoadStatus & 8) === 0) { innerDOMCommentsCountLoader(false); //ensure the previous record is saved // no need to cache to the rendering state _pageBeingInit(); } // _console.log('pageBeingInit', firstLoadStatus) } }; function getFinalComments() { comments_loader = 0; let ei = 0; function execute() { //sync -> animateLoadDeferred.resolved always true if (!renderDeferred.resolved) return; // _console.log(2323) if (Q.comments_section_loaded !== 0) return; if (fetchCounts.fetched) return; let ret = innerDOMCommentsCountLoader(true); if (fetchCounts.new && !fetchCounts.fetched) { if (fetchCounts.new.f()) { fetchCounts.fetched = true; fetchCommentsFinished(); // _console.log(9972, 'fetched = true') } return; } ei++; if (fetchCounts.base && !fetchCounts.new && !fetchCounts.fetched && fetchCounts.count === 1) { let elm = kRef(fetchCounts.base.elm); let txt = elm ? getCountHText(elm) : null; let condi1 = ei > 7; let condi2 = txt === m_last_count; if (condi1 || condi2) { if (fetchCounts.base.f()) { fetchCounts.fetched = true; fetchCommentsFinished(); // _console.log(9972, 'fetched = true') } } } if (!fetchCounts.fetched) { if (ei > 7) { let elm = ret.length === 1 ? kRef(ret[0].elm) : null; let txt = elm ? getCountHText(elm) : null; if (elm && txt !== m_last_count) { fetchCounts.base = null; fetchCounts.new = ret[0]; fetchCounts.new.f(); fetchCounts.fetched = true; // _console.log(9979, 'fetched = true') fetchCommentsFinished(); } return; } return true; } } async function alCheckFn(ks) { let alCheckInterval = 420; for (let alCheckCount = 9; --alCheckCount > 0;) { if (renderIdentifier.valueOf() !== ks) break; if (execute() !== true) break; await new Promise(r => setTimeout(r, alCheckInterval)); } } let ks = renderIdentifier.valueOf(); renderDeferred.debounce(() => { if (ks !== renderIdentifier.valueOf()) return if (mvideoState & 32) return; mvideoState |= 32; alCheckFn(ks); }); } const { removeDuplicateInfoFn, setHiddenStateForDesc } = (() => { let g_check_detail_A = 0; function setHiddenStateForDesc() { let ytdFlexyElm = es.ytdFlexy; if (!ytdFlexyElm) return; let hiddenBool = !document.fullscreenElement ? ytdFlexyElm.classList.contains('tabview-info-duplicated') : false; let elm; elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata #plain-snippet-text'); if (elm) { wAttr(elm, 'hidden', hiddenBool); } elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata #formatted-snippet-text'); if (elm) { wAttr(elm, 'hidden', hiddenBool); } } function checkDuplicatedInfo_then() { const ytdFlexyElm = es.ytdFlexy; if (!ytdFlexyElm) return; //unlikely let cssbool_c1 = false, cssbool_c2 = false; if (ytdFlexyElm.matches('.tabview-info-duplicated[flexy]')) { cssbool_c1 = !!_querySelector.call(ytdFlexyElm, '#description.style-scope.ytd-watch-metadata > #description-inner:only-child'); cssbool_c2 = !!_querySelector.call(ytdFlexyElm, '#tab-info ytd-expander #description.ytd-video-secondary-info-renderer'); } setHiddenStateForDesc(); ytdFlexyElm.setAttribute('tyt-has', `${cssbool_c1 ? 'A' : 'a'}${cssbool_c2 ? 'B' : 'b'}`); } async function contentPairSet(firstElement, secondElement) { // const firstElementSelector = "ytd-text-inline-expander#description-inline-expander"; // const secondElementSelector = "#tab-info ytd-expander #description"; // const firstElement = document.querySelector(firstElementSelector); // const secondElement = document.querySelector(secondElementSelector); if (firstElement && secondElement) { // checked that e1 e2 shall be considered with matching pair firstElement.setAttribute('tyt-du744', ''); secondElement.setAttribute('tyt-du744', ''); } } async function _checkDuplicatedInfoAug2023(_firstElement, _secondElement, spk) { /** @type {HTMLElement} */ const firstElement = _firstElement; /** @type {HTMLElement} */ const secondElement = _secondElement; // dont detect the content change of main info box // if second info box is checked okay before, skip if (firstElement.hasAttribute('tyt-du744') || secondElement.hasAttribute('tyt-du744')) return true; // assume still ok as we checked before if (!firstElement || !secondElement) return false; if (firstElement.hasAttribute('hidden') || secondElement.hasAttribute('hidden')) return false; const isCryptoRandomUUIDAvailable = typeof crypto === 'object' && typeof crypto.randomUUID === 'function' ? `${crypto.randomUUID()}` : ''; const isReplaceAllAvailable = typeof String.prototype.replaceAll === 'function'; const doNameReplace = isCryptoRandomUUIDAvailable && isReplaceAllAvailable; let nameText = doNameReplace ? (sessionStorage.getItem('js-yt-usernames') || '') : ''; { const ownerEndpoints = document.querySelectorAll('#owner a.yt-simple-endpoint[href]'); let handle = null; let displayName = null; for (const anchor of ownerEndpoints) { if (displayName === null && anchor.firstElementChild === null && (anchor.textContent || "").length > 0) { displayName = anchor.textContent } let m; if (handle === null && (m = /\/(\@[-_a-zA-Z0-9.]{3,30})(\/|$)/.exec(anchor.getAttribute('href')))) { handle = m[1]; } } if (handle !== null && displayName !== null) { if (nameText.indexOf(`\n${handle}\t`) < 0 && `${nameText}\n`.indexOf(`\t${displayName}\n`) < 0) { nameText = `\n${handle}\t${displayName}\n${nameText}`.trim(); } } } const replaceArr = []; let rid; if (nameText) { do { rid = crypto.randomUUID(); } while (nameText.includes(rid)); let nni = 0; const nameMap = new Map(); for (const nameTextS of nameText.split('\n')) { if (nameTextS.length < 4) continue; const nameTextA = nameTextS.split('\t'); const m = nameTextA[1].replaceAll(' ', ''); let t = nameMap.get(m); if (!t) { t = ++nni; nameMap.set(m, t); } replaceArr.push([nameTextA[0], t]); replaceArr.push([m, t]); } nameMap.clear(); if (replaceArr.length > 1) replaceArr.sort((a, b) => b[0].length - a[0].length); } const fixNameConversion = doNameReplace && rid && replaceArr.length > 0 ? (text) => { if (typeof text === 'string') { text = text.replaceAll(' ', ''); for (const s of replaceArr) { const w = `[[${rid}::${s[1]}]]`; text = text.replaceAll(s[0], w); } } return text; } : (text) => text; const hiddenTexts = new Map(); const hiddenTextReplacement = (element) => { for (const hiddenElement of _querySelectorAll.call(element, '[hidden]')) { const walker = document.createTreeWalker(hiddenElement, NodeFilter.SHOW_TEXT, null, null); let node; while (node = walker.nextNode()) { const text = node.nodeValue; if (text && !text.startsWith('\uF204')) { hiddenTexts.set(node, text); node.nodeValue = `\uF204${text.replace(/[\uF204\uF205]/g, '')}\uF205`; } } } } const addContent = (currentNode, contentArray) => { if (currentNode instanceof HTMLElement) { hiddenTextReplacement(currentNode); } /** @type {string} */ let trText = currentNode.textContent.trim(); let withText = trText.length > 0; if (withText && trText.includes('\uF204')) { trText = trText.replace(/\uF204[^\uF204\uF205]+\uF205/g, ''); trText = trText.replace(/[\uF204\uF205]/g, ''); withText = trText.length > 0; } if (withText) { trText = trText.replace(/\n[\n\x20]+\n/g, '\n\n'); trText = trText.replace(/[\u0020\u00A0\u16A0\u180E\u2000-\u200A\u202F\u205F\u3000]/g, ' '); trText = trText.replace(/[\u200b\uFEFF]/g, ''); let loop = 64; while (loop-- > 0) { const before = trText; trText = trText.replace(/([\u1000-\uDF77])\x20([\x21-\x7E])/g, '$1$2'); // 中英文之间加空白 ? trText = trText.replace(/([\x21-\x7E])\x20([\u1000-\uDF77])/g, '$1$2'); // 中英文之间加空白 ? if (before === trText) loop = 0; } // "白州大根\n \n チャンネル登録者数 698人\n \n \n\n\n 動画\n \n\n\n \n \n概要" // "白州大根\n \n チャンネル登録者数 698人\n \n \n\n\n 動画\n \n \n概要" trText = fixNameConversion(trText); trText = trText.replace(/[,,.。、]/g, ' '); trText = trText.replace(/[##]/g, '#'); trText = trText.replace(/[**]/g, '*'); trText = trText.replace(/[「」『』”’<>"'<>\[\]\{\}]/g, '"'); trText = trText.replace(/[::]/g, ':'); trText = trText.replace(/\s+/g, ' '); // trText = trText.replace(/[12345678901234567890]/g, '0'); trText = trText.trim(); if (trText) contentArray.push(trText); } } const filterNode = (currentNode) => { if (currentNode.nodeType === Node.ELEMENT_NODE) { if (currentNode.nodeName === "STYLE") { //