Tabview Youtube

把Youtube Videos中的評論及影片清單製作成Tabs

目前為 2025-01-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Tabview Youtube
  3. // @name:en Tabview Youtube
  4. // @description Make comments and lists into tabs for YouTube Videos
  5. // @description:en Make comments and lists into tabs for YouTube Videos
  6. // @name:ja Tabview Youtube
  7. // @description:ja YouTube動画のコメントやリストなどをタブに作成します
  8. // @name:ko Tabview Youtube
  9. // @description:ko YouTube 동영상의 댓글 및 목록을 탭으로 만듭니다
  10. // @name:zh-TW Tabview Youtube
  11. // @name:zh-HK Tabview Youtube
  12. // @name:zh-CN Tabview Youtube
  13. // @description:zh-TW 把Youtube Videos中的評論及影片清單製作成Tabs
  14. // @description:zh-HK 把Youtube Videos中的評論及影片清單製作成Tabs
  15. // @description:zh-CN 把Youtube Videos中的评论及视频列表制作成Tabs
  16. // @name:ru Tabview Youtube
  17. // @description:ru Сделайте описание, комментарии и список видео в виде вкладок для видео на YouTube
  18.  
  19. // @name:af Tabview Youtube
  20. // @name:az Tabview Youtube
  21. // @name:id Tabview Youtube
  22. // @name:ms Tabview Youtube
  23. // @name:bs Tabview Youtube
  24. // @name:ca Tabview Youtube
  25. // @name:cs Tabview Youtube
  26. // @name:da Tabview Youtube
  27. // @name:de Tabview Youtube
  28. // @name:et Tabview Youtube
  29. // @name:es Tabview Youtube
  30. // @name:eu Tabview Youtube
  31. // @name:fr Tabview Youtube
  32. // @name:gl Tabview Youtube
  33. // @name:hr Tabview Youtube
  34. // @name:zu Tabview Youtube
  35. // @name:is Tabview Youtube
  36. // @name:it Tabview Youtube
  37. // @name:sw Tabview Youtube
  38. // @name:lv Tabview Youtube
  39. // @name:lt Tabview Youtube
  40. // @name:hu Tabview Youtube
  41. // @name:nl Tabview Youtube
  42. // @name:uz Tabview Youtube
  43. // @name:pl Tabview Youtube
  44. // @name:pt Tabview Youtube
  45. // @name:pt-BR Tabview Youtube
  46. // @name:ro Tabview Youtube
  47. // @name:sq Tabview Youtube
  48. // @name:sk Tabview Youtube
  49. // @name:sl Tabview Youtube
  50. // @name:sr Tabview Youtube
  51. // @name:fi Tabview Youtube
  52. // @name:sv Tabview Youtube
  53. // @name:vi Tabview Youtube
  54. // @name:tr Tabview Youtube
  55. // @name:be Tabview Youtube
  56. // @name:bg Tabview Youtube
  57. // @name:ky Tabview Youtube
  58. // @name:kk Tabview Youtube
  59. // @name:mk Tabview Youtube
  60. // @name:mn Tabview Youtube
  61. // @name:uk Tabview Youtube
  62. // @name:el Tabview Youtube
  63. // @name:hy Tabview Youtube
  64. // @name:ur Tabview Youtube
  65. // @name:ar Tabview Youtube
  66. // @name:fa Tabview Youtube
  67. // @name:ne Tabview Youtube
  68. // @name:mr Tabview Youtube
  69. // @name:hi Tabview Youtube
  70. // @name:as Tabview Youtube
  71. // @name:bn Tabview Youtube
  72. // @name:pa Tabview Youtube
  73. // @name:gu Tabview Youtube
  74. // @name:or Tabview Youtube
  75. // @name:ta Tabview Youtube
  76. // @name:te Tabview Youtube
  77. // @name:kn Tabview Youtube
  78. // @name:ml Tabview Youtube
  79. // @name:si Tabview Youtube
  80. // @name:th Tabview Youtube
  81. // @name:lo Tabview Youtube
  82. // @name:my Tabview Youtube
  83. // @name:ka Tabview Youtube
  84. // @name:am Tabview Youtube
  85. // @name:km Tabview Youtube
  86.  
  87.  
  88. // @description:af Maak kommentaar en lyste as oortjies vir YouTube-video's
  89. // @description:az YouTube Videoları üçün şərhləri və siyahıları tablara çevirin
  90. // @description:id Ubah komentar dan daftar menjadi tab untuk Video YouTube
  91. // @description:ms Ubah komen dan senarai menjadi tab untuk Video YouTube
  92. // @description:bs Pretvorite komentare i liste u kartice za YouTube videozapise
  93. // @description:ca Converteix comentaris i llistes en pestanyes per a Vídeos de YouTube
  94. // @description:cs Převeďte komentáře a seznamy na karty pro YouTube videa
  95. // @description:da Lav kommentarer og lister til faner for YouTube-videoer
  96. // @description:de Machen Sie Kommentare und Listen zu Tabs für YouTube-Videos
  97. // @description:et Muutke kommentaarid ja loendid YouTube'i videote jaoks kaartideks
  98. // @description:es Convierte los comentarios y listas en pestañas para los Videos de YouTube
  99. // @description:eu Egin iruzkinak eta zerrendak YouTube Bideoetarako fitxetan
  100. // @description:fr Transformez les commentaires et les listes en onglets pour les vidéos YouTube
  101. // @description:gl Converte comentarios e listas en lapelas para Vídeos de YouTube
  102. // @description:hr Pretvorite komentare i popise u kartice za YouTube videe
  103. // @description:zu Yenza ukubhala phansi kanye nemingcele ukuba yiithebhu kuVidiyo ze-YouTube
  104. // @description:is Breyttu athugasemdum og listum í flipa fyrir YouTube myndbönd
  105. // @description:it Trasforma commenti e liste in schede per i Video di YouTube
  106. // @description:sw Geuza maoni na orodha kuwa vichupo kwa Video za YouTube
  107. // @description:lv Pārveidojiet komentārus un sarakstus cilnēs YouTube video
  108. // @description:lt Paverčia komentarus ir sąrašus skirtukais YouTube vaizdo įrašams
  109. // @description:hu Alakítsa át a megjegyzéseket és listákat fülekké a YouTube videókhoz
  110. // @description:nl Maak van reacties en lijsten tabs voor YouTube-video's
  111. // @description:uz YouTube videolar uchun sharhlar va ro'yxatlarni ichki oynalar qiling
  112. // @description:pl Przekształć komentarze i listy w karty dla filmów na YouTube
  113. // @description:pt Transforme comentários e listas em abas para Vídeos do YouTube
  114. // @description:pt-BR Transforme comentários e listas em abas para Vídeos do YouTube
  115. // @description:ro Transformă comentariile și listele în file pentru Videoclipuri YouTube
  116. // @description:sq Kthe komentet dhe listat në skeda për Videot në YouTube
  117. // @description:sk Premente komentáre a zoznamy na karty pre YouTube videá
  118. // @description:sl Pretvori komentarje in sezname v zavihke za YouTube videe
  119. // @description:sr Pretvorite komentare i liste u kartice za YouTube videe
  120. // @description:fi Muuta kommentit ja luettelot välilehdiksi YouTube-videoille
  121. // @description:sv Gör kommentarer och listor till flikar för YouTube-videor
  122. // @description:vi Chuyển đổi bình luận và danh sách thành tab cho Video YouTube
  123. // @description:tr Yorumları ve listeleri YouTube Videoları için sekmelere dönüştürün
  124. // @description:be Пераўтварыце каментарыі і спісы ў закладкі для відэа на YouTube
  125. // @description:bg Превърнете коментарите и списъците в раздели за видеоклипове в YouTube
  126. // @description:ky YouTube видеолору үчүн эскертүүлөрдү жана тизмелерди табдыктарга айлантырыңыз
  127. // @description:kk Пікірлер мен тізімдерді YouTube видеолары үшін қоймақтарға айналдырыңыз
  128. // @description:mk Претворете ги коментарите и листите во јазичиња за Видеа на YouTube
  129. // @description:mn YouTube видео дэх сэтгэгдлүүд болон жагсаалтыг табчууд болгоно уу
  130. // @description:uk Зробіть коментарі та списки у вкладки для відео на YouTube
  131. // @description:el Μετατρέψτε τα σχόλια και τις λίστες σε καρτέλες για τα βίντεο του YouTube
  132. // @description:hy Վերածեք մեկնաբանությունները և ցուցակները YouTube տեսանյութերի ներդիրների
  133. // @description:ur YouTube ویڈیوز کے لئے تبصرے اور فہرستوں کو ٹیب میں تبدیل کریں
  134. // @description:ar قم بتحويل التعليقات والقوائم إلى علامات تبويب لفيديوهات YouTube
  135. // @description:fa نظرات و فهرست ها را به زبانه ها برای ویدیوهای YouTube تبدیل کنید
  136. // @description:ne YouTube भिडियोहरूका लागि प्रतिक्रिया र सूचीहरूलाई ट्याबहरूमा परिवर्तन गर्नुहोस्
  137. // @description:mr YouTube व्हिडिओसाठी टिप्पण्या आणि यादीतबांमध्ये करा
  138. // @description:hi YouTube वीडियो के लिए टिप्पणियाँ और सूचियों को टैब में बदलें
  139. // @description:as YouTube ভিডিঅ'ৰ বাবে মন্তব্য আৰু তালিকাসমূহ টেবলত পৰিণত কৰক
  140. // @description:bn YouTube ভিডিওর জন্য মন্তব্য এবং তালিকা ট্যাবে পরিণত করুন
  141. // @description:pa ਯੂਟਿਊਬ ਵੀਡੀਓਜ਼ ਲਈ ਟਿੱਪਣੀਆਂ ਅਤੇ ਸੂਚੀਆਂ ਨੂੰ ਟੈਬਾਂ ਵਿੱਚ ਬਦਲੋ
  142. // @description:gu YouTube વિડિઓ માટે ટિપ્પણીઓ અને યાદીઓ ટૅબમાં બદલો
  143. // @description:or YouTube ଭିଡିଓ ପାଇଁ ମନ୍ତବ୍ୟ ଏବଂ ତାଲିକାଗୁଡ଼ିକ ଟ୍ୟାବମାନେ ପରିବର୍ତ୍ତନ କରନ୍ତୁ
  144. // @description:ta YouTube வீடியோக்கான கருத்துக்கள் மற்றும் பட்டியல்களை தாவல்களாக மாற்றவும்
  145. // @description:te YouTube వీడియోల కోసం వ్యాఖ్యలు మరియు జాబితాలను ట్యాబ్లలుగా మార్చండి
  146. // @description:kn YouTube ವೀಡಿಯೊಗಳಿಗಾಗಿ ಟಿಪ್ಪಣಿಗಳನ್ನು ಮತ್ತು ಪಟ್ಟಿಗಳನ್ನು ಟ್ಯಾಬ್‌ಗಳಾಗಿ ಮಾಡಿ
  147. // @description:ml YouTube വീഡിയോകൾക്കായി അഭിപ്രായങ്ങളും പട്ടികകളും ടാബുകളായി മാറ്റുക
  148. // @description:si YouTube වීඩියෝ සඳහා අදහස් සහ ලැයිස්තු ටැබ් කරන්න
  149. // @description:th ทำให้ความคิดเห็นและรายการเป็นแท็บสำหรับวิดีโอ YouTube
  150. // @description:lo ປ່ຽນຄວາມເຫັນຂອງຄົນເບິ່ງແລະລາຍການເປັນແຖບສໍາລັບວິດີໂອ YouTube
  151. // @description:my YouTube ဗီဒီယိုများအတွက် မှတ်ချက်များနှင့် စာရင်းများကို Tabs အဖြစ် ပြောင်းပါ
  152. // @description:ka გადაიყვანეთ კომენტარები და სიები ჩანართებში YouTube ვიდეოებისთვის
  153. // @description:am አስተያየቶችን እና ዝርዝሮችን YouTube ቪዲዮዎች ለትርጓሜዎች ውስጥ ያስተካክሉ
  154. // @description:km បង្កើតមតិយោបល់និងបញ្ជីទៅជាផ្ទាំងសម្រាប់វីដេអូ YouTube
  155.  
  156. // @version 4.73.32
  157. // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/8426b63ce6c0e27e7578b3ba1a0617252b895e20/css/style_content.css
  158. // @resource chatCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/8426b63ce6c0e27e7578b3ba1a0617252b895e20/css/style_chat.css
  159. // @resource controlCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/8426b63ce6c0e27e7578b3ba1a0617252b895e20/css/style_control.css
  160. // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/8426b63ce6c0e27e7578b3ba1a0617252b895e20/js/injection_script_1.js
  161. // @require https://gf.qytechs.cn/scripts/465421-vanilla-js-dialog/code/Vanilla%20JS%20Dialog.js?version=1188332
  162.  
  163. // @namespace http://tampermonkey.net/
  164. // @author CY Fung
  165. // @license MIT
  166. // @supportURL https://github.com/cyfung1031/Tabview-Youtube
  167. // @run-at document-start
  168. // @match https://www.youtube.com/*
  169. // @exclude /^https?://\w+\.youtube\.com\/live_chat.*$/
  170. // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
  171. // @icon https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/main/images/icon128p.png
  172.  
  173. // @compatible edge Edge [Blink] >= 79; Tampermonkey (Beta) / Violentmonkey
  174. // @compatible chrome Chrome >= 54; Tampermonkey (Beta) / Violentmonkey
  175. // @compatible firefox FireFox / Waterfox (Classic) >= 55; Tampermonkey / Violentmonkey
  176. // @compatible opera Opera >= 41; Tampermonkey (Beta) / Violentmonkey
  177. // @compatible safari Safari >= 12.1
  178.  
  179. // @grant GM_getResourceText
  180. // @grant GM.getResourceText
  181. // @grant GM_registerMenuCommand
  182. // @noframes
  183. // ==/UserScript==
  184.  
  185. /*
  186.  
  187. MIT License
  188.  
  189. Copyright (c) 2021-2024 cyfung1031
  190.  
  191. Permission is hereby granted, free of charge, to any person obtaining a copy
  192. of this software and associated documentation files (the "Software"), to deal
  193. in the Software without restriction, including without limitation the rights
  194. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  195. copies of the Software, and to permit persons to whom the Software is
  196. furnished to do so, subject to the following conditions:
  197.  
  198. The above copyright notice and this permission notice shall be included in all
  199. copies or substantial portions of the Software.
  200.  
  201. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  202. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  203. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  204. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  205. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  206. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  207. SOFTWARE.
  208.  
  209. */
  210.  
  211. /* jshint esversion:8 */
  212.  
  213. function main(){
  214. 'use strict';
  215. // MIT License
  216. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  217.  
  218. if (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy === null) {
  219. let s = s => s;
  220. trustedTypes.createPolicy('default', { createHTML: s, createScriptURL: s, createScript: s });
  221. }
  222.  
  223. const defaultPolicy = (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy) || { createHTML: s => s };
  224. function createHTML(s) {
  225. return defaultPolicy.createHTML(s);
  226. }
  227.  
  228. let trustHTMLErr = null;
  229. try {
  230. document.createElement('div').innerHTML = createHTML('1');
  231. } catch (e) {
  232. trustHTMLErr = e;
  233. }
  234.  
  235. if (trustHTMLErr) {
  236. console.log(`trustHTMLErr`, trustHTMLErr);
  237. trustHTMLErr(); // exit userscript
  238. }
  239.  
  240. if (typeof window === 'object') {
  241. function script3278() {
  242. const DISABLE_FLAGS_SHADYDOM_FREE = true;
  243.  
  244. /**
  245. *
  246. * Minified Code from https://gf.qytechs.cn/en/scripts/475632-ytconfighacks/code (ytConfigHacks)
  247. * Date: 2024.04.17
  248. * Minifier: https://www.toptal.com/developers/javascript-minifier
  249. *
  250. */
  251. (() => {
  252. let e = "undefined" != typeof unsafeWindow ? unsafeWindow : this instanceof Window ?
  253. this : window; if (!e._ytConfigHacks) {
  254. let t = 4; class n extends Set {
  255. add(e) {
  256. if (t <= 0) return console.warn(
  257. "yt.config_ is already applied on the page."); "function" == typeof e && super.add(e)
  258. }
  259. } let a = (async () => { })()
  260. .constructor, i = e._ytConfigHacks = new n, l = () => { let t = e.ytcsi.originalYtcsi; t && (e.ytcsi = t, l = null) },
  261. c = null, o = () => {
  262. if (t >= 1) {
  263. let n = (e.yt || 0).config_ || (e.ytcfg || 0).data_ || 0; if ("string" == typeof n.
  264. INNERTUBE_API_KEY && "object" == typeof n.EXPERIMENT_FLAGS) for (let a of (--t <= 0 && l && l(), c = !0, i)) a(n)
  265. }
  266. }, f = 1,
  267. d = t => {
  268. 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]) })
  269. , !0
  270. }; d() || Object.defineProperty(e, "ytcsi", {
  271. get() { }, set: t => (t && (delete e.ytcsi, d(t)), !0), enumerable: !1, configurable: !0
  272. }); let { addEventListener: s, removeEventListener: y } = Document.prototype; function r(t) {
  273. o(),
  274. t && e.removeEventListener("DOMContentLoaded", r, !1)
  275. } new a(e => {
  276. if ("undefined" != typeof AbortSignal) s.call(document,
  277. "yt-page-data-fetched", e, { once: !0 }), s.call(document, "yt-navigate-finish", e, { once: !0 }), s.call(document, "spfdone", e,
  278. { once: !0 }); else {
  279. let t = () => {
  280. e(), y.call(document, "yt-page-data-fetched", t, !1), y.call(document, "yt-navigate-finish", t, !1),
  281. y.call(document, "spfdone", t, !1)
  282. }; s.call(document, "yt-page-data-fetched", t, !1), s.call(document, "yt-navigate-finish", t, !1),
  283. s.call(document, "spfdone", t, !1)
  284. }
  285. }).then(o), new a(e => {
  286. if ("undefined" != typeof AbortSignal) s.call(document, "yt-action", e,
  287. { once: !0, capture: !0 }); else { let t = () => { e(), y.call(document, "yt-action", t, !0) }; s.call(document, "yt-action", t, !0) }
  288. }).then(o),
  289. a.resolve().then(() => { "loading" !== document.readyState ? r() : e.addEventListener("DOMContentLoaded", r, !1) })
  290. }
  291. })();
  292.  
  293. let configOnce = false;
  294. window._ytConfigHacks.add((config_) => {
  295. if (configOnce) return;
  296. configOnce = true;
  297.  
  298. const EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS || 0;
  299. const EXPERIMENTS_FORCED_FLAGS = config_.EXPERIMENTS_FORCED_FLAGS || 0;
  300. for (const flags of [EXPERIMENT_FLAGS, EXPERIMENTS_FORCED_FLAGS]) {
  301. if (flags) {
  302. flags.kevlar_watch_metadata_refresh_no_old_secondary_data = false;
  303. flags.live_chat_overflow_hide_chat = false;
  304. flags.web_watch_chat_hide_button_killswitch = false;
  305. flags.web_watch_theater_chat = false; // for re-openable chat (ytd-watch-flexy's liveChatCollapsed is always undefined)
  306. flags.suppress_error_204_logging = true;
  307. flags.kevlar_watch_grid = false; // A/B testing for watch grid
  308.  
  309. if (DISABLE_FLAGS_SHADYDOM_FREE) {
  310. flags.enable_shadydom_free_scoped_node_methods = false;
  311. flags.enable_shadydom_free_scoped_query_methods = false;
  312. flags.enable_shadydom_free_scoped_readonly_properties_batch_one = false;
  313. flags.enable_shadydom_free_parent_node = false;
  314. flags.enable_shadydom_free_children = false;
  315. flags.enable_shadydom_free_last_child = false;
  316. }
  317. }
  318. }
  319.  
  320. });
  321.  
  322.  
  323. // ---- << this.overscrollConfig HACK >> -----
  324.  
  325. // 2024.04.19 - Playlist in Single Column Mode cannot be scrolled correctly.
  326.  
  327. /*
  328.  
  329. ;function gZb(a, b) {
  330. b = void 0 === b ? !0 : b;
  331. a.addEventListener("wheel", hZb);
  332. a.overscrollConfig = {
  333. cooldown: b
  334. }
  335. }
  336. function iZb(a) {
  337. a.overscrollConfig = void 0;
  338. a.removeEventListener("wheel", hZb)
  339. }
  340. function hZb(a) {
  341. var b = a.deltaY
  342. , c = a.target
  343. , d = null;
  344. if (window.Polymer && window.Polymer.Element) {
  345. if (c = a.path || a.composedPath && a.composedPath()) {
  346. c = g(c);
  347. for (var e = c.next(); !e.done && (e = e.value,
  348. !jZb(e, b)); e = c.next())
  349. if (e.overscrollConfig) {
  350. d = e;
  351. break
  352. }
  353. }
  354. } else
  355. for (; c && !jZb(c, b); ) {
  356. if (c.overscrollConfig) {
  357. d = c;
  358. break
  359. }
  360. c = c.parentElement
  361. }
  362. d && (b = d.overscrollConfig,
  363. b.cooldown ? (d = a.deltaY,
  364. c = b.lastDeltaY || 0,
  365. b.lastDeltaY = d,
  366. e = b.lastStopped || 0,
  367. c && e && 0 < c == 0 < d ? Math.abs(c) >= Math.abs(d) ? (d = e + 1200,
  368. c = !1) : (d = e + 600,
  369. c = !0) : (d = Date.now() + 600,
  370. c = !0),
  371. d > Date.now() && (a.preventDefault(),
  372. c && (b.lastStopped = Date.now()))) : a.preventDefault())
  373. }
  374. */
  375.  
  376. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  377. // const indr = o => insp(o).$ || o.$ || 0;
  378.  
  379. // let [YouTube JS Engine Tamer] to hack the addEventListener first
  380. new Promise(resolve => {
  381. document.addEventListener('yt-action', resolve, { once: true, capture: true });
  382. }).then(() => {
  383.  
  384. if (typeof EventTarget.prototype.addEventListener52178 !== 'function' && typeof EventTarget.prototype.addEventListener === 'function') {
  385.  
  386. EventTarget.prototype.addEventListener52178 = EventTarget.prototype.addEventListener
  387. EventTarget.prototype.addEventListener = function (type, callback, option = void 0) {
  388. // M-tabview-youtube.52178
  389. if (type === 'wheel' && !option && typeof callback === 'function' && this.overscrollConfigDisable === void 0) {
  390. try {
  391. this.overscrollConfigDisable = true;
  392. delete this.overscrollConfig;
  393. Object.defineProperty(this, 'overscrollConfig', { get() { return undefined }, set(nv) { return true }, configurable: true, enumerable: false });
  394. const cnt = insp(this);
  395. if (cnt !== this) {
  396. delete cnt.overscrollConfig;
  397. Object.defineProperty(cnt, 'overscrollConfig', { get() { return undefined }, set(nv) { return true }, configurable: true, enumerable: false });
  398. }
  399. } catch (e) { }
  400. }
  401. return this.addEventListener52178(type, callback, option);
  402. }
  403.  
  404. // Object.defineProperty( HTMLElement.prototype, 'overscrollConfig' , {get(){return undefined}, set(nv){return true}, configurable: true, enumerable: false})
  405.  
  406. }
  407.  
  408. });
  409.  
  410.  
  411. // ---- << this.overscrollConfig HACK >> -----
  412.  
  413.  
  414. }
  415. let mbutton = document.createElement('button');
  416. mbutton.setAttribute('onclick', `(${script3278})()`);
  417. mbutton.click();
  418.  
  419. }
  420.  
  421. -(function (__CONTEXT__) {
  422. 'use strict';
  423.  
  424. let __Promise__;
  425. try {
  426. __Promise__ = (async () => { })().constructor; // due to YouTube's Promise Hack
  427. } catch (e) {
  428. throw 'Please update your browser to use Tabview Youtube.';
  429. }
  430.  
  431. const fxOperator = (proto, propertyName) => {
  432. let propertyDescriptorGetter = null;
  433. try {
  434. propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
  435. } catch (e) { }
  436. return typeof propertyDescriptorGetter === 'function' ? (e) => propertyDescriptorGetter.call(e) : (e) => e[propertyName];
  437. };
  438.  
  439. const fxAPI = (proto, propertyName) => {
  440. const methodFunc = proto[propertyName];
  441. return typeof methodFunc === 'function' ? (e, ...args) => methodFunc.apply(e, args) : (e, ...args) => e[propertyName](...args);
  442. };
  443.  
  444. const nodeParent = fxOperator(Node.prototype, 'parentNode');
  445. const nodeFirstChild = fxOperator(Node.prototype, 'firstChild');
  446. const nodeNextSibling = fxOperator(Node.prototype, 'nextSibling');
  447. const nodePrevSibling = fxOperator(Node.prototype, 'previousSibling');
  448.  
  449. // const elementQS = fxAPI(Element.prototype, 'querySelector');
  450. // const elementQSA = fxAPI(Element.prototype, 'querySelectorAll');
  451. const elementNextSibling = fxOperator(Element.prototype, 'nextElementSibling');
  452. // const elementPrevSibling = fxOperator(Element.prototype, 'previousElementSibling');
  453.  
  454. const docFragmentCreate = Document.prototype.createDocumentFragment;
  455. const docFragmentAppend = DocumentFragment.prototype.append;
  456. // const docFragmentPrepend = DocumentFragment.prototype.prepend;
  457.  
  458. /** @type {PromiseConstructor} */
  459. const Promise = __Promise__; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  460.  
  461. const { requestAnimationFrame, cancelAnimationFrame } = __CONTEXT__;
  462.  
  463. let _rafPromise = null;
  464.  
  465. const getRAFPromise = () => _rafPromise || (_rafPromise = new Promise(resolve => {
  466. requestAnimationFrame(hRes => {
  467. _rafPromise = null;
  468. resolve(hRes);
  469. });
  470. }));
  471.  
  472. function inIframe() {
  473. try {
  474. return window.self !== window.top;
  475. } catch (e) {
  476. return true;
  477. }
  478. }
  479.  
  480. if (inIframe()) return;
  481.  
  482. if (document.documentElement && document.documentElement.hasAttribute('plugin-tabview-youtube')) {
  483. console.warn('Multiple instances of Tabview Youtube is attached. [0x7F01]')
  484. return;
  485. }
  486.  
  487. const createPipeline = () => {
  488. let pipelineMutex = Promise.resolve();
  489. const pipelineExecution = fn => {
  490. return new Promise((resolve, reject) => {
  491. pipelineMutex = pipelineMutex.then(async () => {
  492. let res;
  493. try {
  494. res = await fn();
  495. } catch (e) {
  496. console.log('error_F1', e);
  497. reject(e);
  498. }
  499. resolve(res);
  500. }).catch(console.warn);
  501. });
  502. };
  503. return pipelineExecution;
  504. };
  505. const iframePipeline = createPipeline();
  506.  
  507. /*
  508. const observablePromise = (proc, timeoutPromise) => {
  509. let promise = null;
  510. return {
  511. obtain() {
  512. if (!promise) {
  513. promise = new Promise(resolve => {
  514. let mo = null;
  515. const f = () => {
  516. let t = proc();
  517. if (t) {
  518. mo.disconnect();
  519. mo.takeRecords();
  520. mo = null;
  521. resolve(t);
  522. }
  523. }
  524. mo = new MutationObserver(f);
  525. mo.observe(document, { subtree: true, childList: true })
  526. f();
  527. timeoutPromise && timeoutPromise.then(() => {
  528. resolve(null)
  529. });
  530. });
  531. }
  532. return promise
  533. }
  534. }
  535. }
  536. */
  537.  
  538.  
  539. //if (!$) return;
  540.  
  541. /**
  542. * SVG resources:
  543. * <div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
  544. */
  545.  
  546. const scriptVersionForExternal = '2022/12/04';
  547.  
  548. const isMyScriptInChromeRuntime = () => typeof GM === 'undefined' && typeof ((((window || 0).chrome || 0).runtime || 0).getURL) === 'function';
  549. const isGMAvailable = () => typeof GM !== 'undefined' && !isMyScriptInChromeRuntime();
  550.  
  551. // https://yqnn.github.io/svg-path-editor/
  552. // https://vecta.io/nano
  553.  
  554. const svgComments = `<path d="M80 27H12A12 12 90 0 0 0 39v42a12 12 90 0 0 12 12h12v20a2 2 90 0 0 3.4 2L47 93h33a12
  555. 12 90 0 0 12-12V39a12 12 90 0 0-12-12zM20 47h26a2 2 90 1 1 0 4H20a2 2 90 1 1 0-4zm52 28H20a2 2 90 1 1 0-4h52a2 2 90
  556. 1 1 0 4zm0-12H20a2 2 90 1 1 0-4h52a2 2 90 1 1 0 4zm36-58H40a12 12 90 0 0-12 12v6h52c9 0 16 7 16 16v42h0v4l7 7a2 2 90
  557. 0 0 3-1V71h2a12 12 90 0 0 12-12V17a12 12 90 0 0-12-12z"/>`.trim();
  558.  
  559. const svgVideos = `<path d="M89 10c0-4-3-7-7-7H7c-4 0-7 3-7 7v70c0 4 3 7 7 7h75c4 0 7-3 7-7V10zm-62 2h13v10H27V12zm-9
  560. 66H9V68h9v10zm0-56H9V12h9v10zm22 56H27V68h13v10zm-3-25V36c0-2 2-3 4-2l12 8c2 1 2 4 0 5l-12 8c-2 1-4 0-4-2zm25
  561. 25H49V68h13v10zm0-56H49V12h13v10zm18 56h-9V68h9v10zm0-56h-9V12h9v10z"/>`.trim();
  562.  
  563. const svgInfo = `<path d="M30 0C13.3 0 0 13.3 0 30s13.3 30 30 30 30-13.3 30-30S46.7 0 30 0zm6.2 46.6c-1.5.5-2.6
  564. 1-3.6 1.3a10.9 10.9 0 0 1-3.3.5c-1.7 0-3.3-.5-4.3-1.4a4.68 4.68 0 0 1-1.6-3.6c0-.4.2-1 .2-1.5a20.9 20.9 90 0 1
  565. .3-2l2-6.8c.1-.7.3-1.3.4-1.9a8.2 8.2 90 0 0 .3-1.6c0-.8-.3-1.4-.7-1.8s-1-.5-2-.5a4.53 4.53 0 0 0-1.6.3c-.5.2-1
  566. .2-1.3.4l.6-2.1c1.2-.5 2.4-1 3.5-1.3s2.3-.6 3.3-.6c1.9 0 3.3.6 4.3 1.3s1.5 2.1 1.5 3.5c0 .3 0 .9-.1 1.6a10.4 10.4
  567. 90 0 1-.4 2.2l-1.9 6.7c-.2.5-.2 1.1-.4 1.8s-.2 1.3-.2 1.6c0 .9.2 1.6.6 1.9s1.1.5 2.1.5a6.1 6.1 90 0 0 1.5-.3 9 9 90
  568. 0 0 1.4-.4l-.6 2.2zm-3.8-35.2a1 1 0 010 8.6 1 1 0 010-8.6z"/>`.trim();
  569.  
  570. const svgPlayList = `<path d="M0 3h12v2H0zm0 4h12v2H0zm0 4h8v2H0zm16 0V7h-2v4h-4v2h4v4h2v-4h4v-2z"/>`.trim();
  571.  
  572. const svgDiag1 = `<svg stroke="currentColor" fill="none"><path d="M8 2h2v2M7 5l3-3m-6 8H2V8m0 2l3-3"/></svg>`;
  573. const svgDiag2 = `<svg stroke="currentColor" fill="none"><path d="M7 3v2h2M7 5l3-3M5 9V7H3m-1 3l3-3"/></svg>`;
  574.  
  575. const REMOVE_DUPLICATE_INFO = true;
  576. const REMOVE_DUPLICATE_META_RECOMMENDATION = true; /* https://www.youtube.com/watch?v=kGihxscQCPE */
  577. const MINIVIEW_BROWSER_ENABLE = true;
  578. const DEBUG_LOG = false;
  579.  
  580.  
  581. /*
  582.  
  583. youtube page
  584.  
  585. = Init::browse
  586. yt-page-data-fetched
  587. data-changed...
  588. yt-page-data-updated
  589. yt-navigate-finish
  590. data-changed...
  591. yt-watch-comments-ready
  592. = browse -> watch
  593. yt-player-updated
  594. yt-navigate
  595. yt-navigate-start
  596. yt-page-type-changed
  597. yt-player-updated
  598. yt-page-data-fetched
  599. yt-navigate-finish
  600. data-changed...
  601. yt-page-data-updated
  602. data-changed...
  603. yt-watch-comments-ready
  604. data-changed...
  605.  
  606. = watch -> watch
  607. = click video on meta panel // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo
  608. yt-navigate
  609. yt-navigate-start
  610. data-changed
  611. yt-player-updated
  612. yt-page-data-fetched
  613. yt-navigate-finish
  614. data-changed...
  615. yt-page-data-updated
  616. data-changed...
  617. yt-watch-comments-ready
  618. data-changed...
  619.  
  620. = watch -> browse (miniview)
  621. yt-navigate-cache
  622. yt-page-data-fetched
  623. yt-page-type-changed
  624. yt-page-data-updated
  625. yt-navigate-finish
  626.  
  627. = browse (miniview) -> watch (Restore)
  628. yt-navigate-cache
  629. yt-page-data-fetched
  630. yt-navigate-finish
  631. yt-page-type-changed
  632. yt-page-data-updated
  633. data-changed...
  634. yt-watch-comments-ready
  635.  
  636. = watch -> search (miniview)
  637. yt-navigate
  638. yt-navigate-start
  639. data-changed
  640. yt-page-data-fetched
  641. yt-page-type-changed
  642. data-changed
  643. yt-page-data-updated
  644. yt-navigate-finish
  645. data-changed...
  646.  
  647. = Init::search
  648. yt-page-data-fetched
  649. data-changed
  650. yt-page-data-updated
  651. yt-navigate-finish
  652. data-changed...
  653. yt-watch-comments-ready
  654.  
  655. = Init::watch
  656. yt-page-data-fetched
  657. yt-navigate-finish
  658. data-changed...
  659. yt-page-data-updated
  660. data-changed...
  661. yt-watch-comments-ready
  662. yt-player-updated
  663. data-changed...
  664.  
  665. = watch -> watch (history back)
  666. yt-player-updated
  667. yt-page-data-fetched
  668. yt-navigate-finish
  669. data-changed...
  670. yt-page-data-updated
  671. data-changed...
  672. yt-watch-comments-ready
  673.  
  674. = watch -> click video time // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo
  675. yt-navigate
  676.  
  677. */
  678.  
  679.  
  680. function generateRandomID() {
  681. return Math.floor(Math.random() * 982451653 + 982451653).toString(36);
  682. }
  683. function generateRandomTimedID() {
  684. return `${generateRandomID()}-${Date.now().toString(36)}`;
  685. }
  686.  
  687.  
  688. const instanceId = generateRandomTimedID();
  689.  
  690. const LAYOUT_VAILD = 1;
  691.  
  692. const LAYOUT_TWO_COLUMNS = 2;
  693. const LAYOUT_THEATER = 4;
  694. const LAYOUT_FULLSCREEN = 8;
  695. const LAYOUT_CHATROOM = 16;
  696. const LAYOUT_CHATROOM_COLLAPSED = 32;
  697. const LAYOUT_TAB_EXPANDED = 64;
  698. const LAYOUT_ENGAGEMENT_PANEL_EXPANDED = 128;
  699. const LAYOUT_CHATROOM_EXPANDED = 256;
  700. const LAYOUT_DONATION_SHELF_EXPANDED = 512;
  701.  
  702.  
  703. const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  704.  
  705. const showMessages_IframeLoaded = false; // typeof GM === 'undefined';
  706.  
  707. const nullFunc = function () { };
  708.  
  709.  
  710. let scriptEnable = false;
  711.  
  712. let comments_loader = 0; // for comment count (might omit)
  713.  
  714. let cmTime = 0;
  715. const mTime = Date.now() - 152000000;
  716.  
  717. //let lastScrollFetch = 0;
  718. //let lastOffsetTop = 0;
  719. let mtf_forceCheckLiveVideo_disable = 0;
  720.  
  721. let tabsUiScript_setclick = false;
  722. let pageFetchedDataVideoId = null; // integer; for comment checking
  723. let pageType = null; // pageType = 'watch', 'browse', 'playlist', ...
  724. let chatroomDetails = null;
  725. let switchTabActivity_lastTab = null;
  726.  
  727. let lstTab = null;
  728.  
  729. let storeLastPanel = null; // WeakRef
  730.  
  731.  
  732. let mtf_chatBlockQ = null; // for chat layout status change
  733.  
  734. let enableHoverSliderDetection = false; // for hover slider
  735.  
  736.  
  737. let firstLoadStatus = 2 | 8; // for page init
  738.  
  739.  
  740. let m_last_count = ''; // for comment count
  741.  
  742.  
  743.  
  744. let sVideosITO = null;
  745.  
  746. /** @type {WeakRef | null} */
  747. let ytdFlexy = null; // WeakRef
  748.  
  749. const Q = {}
  750. const SETTING_DEFAULT_TAB_0 = "#tab-videos"
  751. const settings = {
  752. defaultTab: SETTING_DEFAULT_TAB_0
  753. };
  754.  
  755. const STORE_VERSION = 1;
  756. const STORE_key = 'userscript-tabview-settings';
  757. const key_default_tab = 'my-default-tab';
  758.  
  759. let hiddenTabsByUserCSS = 0;
  760. let defaultTabByUserCSS = 0;
  761. let setupDefaultTabBtnSetting = null;
  762. let isCommentsTabBtnHidden = false;
  763.  
  764. let fetchCounts = {
  765. base: null,
  766. new: null,
  767. fetched: false,
  768. count: null,
  769. };
  770.  
  771. let pageLang = 'en';
  772. const langWords = {
  773. 'en': {
  774. //'share':'Share',
  775. 'info': 'Info',
  776. 'videos': 'Videos',
  777. 'playlist': 'Playlist'
  778. },
  779. 'jp': {
  780. //'share':'共有',
  781. 'info': '情報',
  782. 'videos': '動画',
  783. 'playlist': '再生リスト'
  784. },
  785. 'tw': {
  786. //'share':'分享',
  787. 'info': '資訊',
  788. 'videos': '影片',
  789. 'playlist': '播放清單'
  790. },
  791. 'cn': {
  792. //'share':'分享',
  793. 'info': '资讯',
  794. 'videos': '视频',
  795. 'playlist': '播放列表'
  796. },
  797. 'du': {
  798. //'share':'Teilen',
  799. 'info': 'Info',
  800. 'videos': 'Videos',
  801. 'playlist': 'Playlist'
  802. },
  803. 'fr': {
  804. //'share':'Partager',
  805. 'info': 'Info',
  806. 'videos': 'Vidéos',
  807. 'playlist': 'Playlist'
  808. },
  809. 'kr': {
  810. //'share':'공유',
  811. 'info': '정보',
  812. 'videos': '동영상',
  813. 'playlist': '재생목록'
  814. },
  815. 'ru': {
  816. //'share':'Поделиться',
  817. 'info': 'Описание',
  818. 'videos': 'Видео',
  819. 'playlist': 'Плейлист'
  820. }
  821. };
  822.  
  823. const getGMT = () => {
  824. let m = new Date('2023-01-01T00:00:00Z');
  825. return m.getDate() === 1 ? `+${m.getHours()}` : `-${24 - m.getHours()}`;
  826. };
  827.  
  828. function durationInfoTS(durationInfo) {
  829. /**
  830. * @type {{ hrs: number, mins: number, seconds: number }}
  831. */
  832. let _durationInfo = durationInfo
  833. return _durationInfo
  834. }
  835.  
  836. function formatDateReqTS(req) {
  837. /**
  838. * @type {{ bd1: KDate | undefined, bd2: KDate | undefined, isSameDay: number | undefined, durationInfo: object | undefined, formatDates: object | undefined }}
  839. */
  840. let _req = req
  841. return _req
  842. }
  843.  
  844. function liveDurationLocaleEN(durationInfo) {
  845.  
  846. const { hrs, mins, seconds } = durationInfoTS(durationInfo)
  847. let ret = []
  848. ret.push(`Length:`)
  849. if (hrs > 0) ret.push(`${hrs} ${hrs === 1 ? 'hour' : 'hours'}`)
  850. if (mins > 0) ret.push(`${mins} ${mins === 1 ? 'minute' : 'minutes'}`)
  851. if (seconds !== null) ret.push(`${seconds} ${seconds === 1 ? 'second' : 'seconds'}`)
  852. return ret.join(' ')
  853. }
  854.  
  855. /* liveDurationLocaleEN by ChatGPT @ 2023.05.11 */
  856. function liveDurationLocaleJP(durationInfo) {
  857.  
  858. const { hrs, mins, seconds } = durationInfoTS(durationInfo);
  859. let ret = [];
  860. ret.push(`長さ:`);
  861. if (hrs > 0) ret.push(`${hrs}時間`);
  862. if (mins > 0) ret.push(`${mins}分`);
  863. if (seconds !== null) ret.push(`${seconds}秒`);
  864. return ret.join('');
  865.  
  866. }
  867.  
  868. function formatDateResultEN(type, req) {
  869.  
  870. const { bd1, bd2, durationInfo, formatDates } = formatDateReqTS(req)
  871.  
  872. switch (type) {
  873. case 0x200:
  874. return [
  875. `The livestream was in ${bd1.lokStringDateEN()} from ${bd1.lokStringTime()} to ${bd2.lokStringTime()}. [GMT${getGMT()}]`,
  876. liveDurationLocaleEN(durationInfo)
  877. ].join('\n');
  878. case 0x210:
  879. return [
  880. `The livestream was from ${bd1.lokStringDateEN()} ${bd1.lokStringTime()} to ${bd2.lokStringDateEN()} ${bd2.lokStringTime()}. [GMT${getGMT()}]`,
  881. liveDurationLocaleEN(durationInfo)
  882. ].join('\n');
  883. case 0x300:
  884. return `The livestream started at ${bd1.lokStringTime()} [GMT${getGMT()}] in ${bd1.lokStringDateEN()}.`;
  885. case 0x600:
  886. return `The video was uploaded in ${formatDates.uploadDate} and published in ${formatDates.publishDate}.`;
  887. case 0x610:
  888. return `The video was uploaded in ${formatDates.uploadDate}.`;
  889. case 0x700:
  890. return `The video was published in ${formatDates.publishDate}.`;
  891. }
  892. return '';
  893.  
  894. }
  895.  
  896. /* formatDateResultJP by ChatGPT @ 2023.05.11 */
  897.  
  898. function formatDateResultJP(type, req) {
  899.  
  900. const { bd1, bd2, durationInfo, formatDates } = formatDateReqTS(req);
  901.  
  902. switch (type) {
  903. case 0x200:
  904. return [
  905. `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から開始し、${bd2.lokStringTime()}まで続きました。[GMT${getGMT()}]`,
  906. liveDurationLocaleJP(durationInfo)
  907. ].join('\n');
  908. case 0x210:
  909. return [
  910. `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から${bd2.lokStringDateJP()}の${bd2.lokStringTime()}まで行われました。[GMT${getGMT()}]`,
  911. liveDurationLocaleJP(durationInfo)
  912. ].join('\n');
  913. case 0x300:
  914. return `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から開始しました。[GMT${getGMT()}]`;
  915. case 0x600:
  916. return `この動画は${formatDates.uploadDate}にアップロードされ、${formatDates.publishDate}に公開されました。`;
  917. case 0x610:
  918. return `この動画は${formatDates.uploadDate}にアップロードされました。`;
  919. case 0x700:
  920. return `この動画は${formatDates.publishDate}に公開されました。`;
  921. }
  922. return '';
  923.  
  924. }
  925.  
  926. function getFormatDateResultFunc() {
  927. switch (getLang()) {
  928. case 'jp':
  929. return formatDateResultJP;
  930. case 'en':
  931. default:
  932. return formatDateResultEN;
  933. }
  934. }
  935.  
  936.  
  937. // const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER', 'YTD-MENU-RENDERER']
  938.  
  939. let globalHook_symbols = [];
  940. let globalHook_hashs = {};
  941.  
  942.  
  943. let singleColumnScrolling_dt = 0;
  944.  
  945. let isStickyHeaderEnabled = false;
  946. let isMiniviewForStickyHeadEnabled = false;
  947.  
  948. let theater_mode_changed_dt = 0;
  949. let detailsTriggerReset = false;
  950.  
  951.  
  952. let isMakeHeaderFloatCalled = false;
  953.  
  954. let _viTimeNum = 203;
  955. let _updateTimeAccum = 0;
  956.  
  957. /** @type {WeakMap<HTMLElement>} */
  958. let loadedCommentsDT = new WeakMap();
  959.  
  960.  
  961.  
  962. // for weakref variable management
  963. const es = {
  964. get ytdFlexy() {
  965. /** @type { HTMLElement | null } */
  966. let res = kRef(ytdFlexy);
  967. return res;
  968. },
  969. get storeLastPanel() {
  970. /** @type { HTMLElement | null } */
  971. let res = kRef(storeLastPanel);
  972. return res;
  973. }
  974. }
  975.  
  976.  
  977. const _console = new Proxy(console, {
  978. get(target, prop, receiver) {
  979. if (!DEBUG_LOG && prop === 'log') {
  980. return nullFunc
  981. }
  982. return Reflect.get(...arguments)
  983. }
  984. });
  985.  
  986. let generalLog901 = !DEBUG_LOG ? 0 : (evt) => {
  987. _console.log(901, evt.type)
  988. }
  989. const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
  990. // https://caniuse.com/?search=observer
  991. // https://caniuse.com/?search=addEventListener%20passive
  992.  
  993. const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;
  994. const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;
  995.  
  996.  
  997. /** @type { (str: string) => (HTMLElement | null) } */
  998. const _querySelector = HTMLElement.prototype.__shady_native_querySelector || HTMLElement.prototype.querySelector; // nodeType==1 // since 2022/07/12
  999.  
  1000. /** @type { (str: string) => (NodeList) } */
  1001. const _querySelectorAll = HTMLElement.prototype.__shady_native_querySelectorAll || HTMLElement.prototype.querySelectorAll; // nodeType==1 // since 2022/07/12
  1002. const closestDOM = HTMLElement.prototype.closest;
  1003. //const elementRemove = HTMLElement.prototype.remove;
  1004. //const elementContains = HTMLElement.prototype.contains; // since 2022/07/12
  1005.  
  1006. const querySelectorFromAnchorX = function (parent, childSelector) {
  1007. if (!(parent instanceof HTMLElement)) return null;
  1008. return _querySelector.call(parent, childSelector) || null;
  1009. }
  1010. const closestDOMX = function (child, parentSelector) {
  1011. if (!(child instanceof HTMLElement)) return null;
  1012. return closestDOM.call(child, parentSelector) || null;
  1013. }
  1014.  
  1015.  
  1016. const { elementAppend, _setAttribute, _insertBefore } = (() => {
  1017. let elementAppend = HTMLElement.prototype.appendChild;
  1018. try {
  1019. elementAppend = ShadyDOM.nativeMethods.appendChild || elementAppend;
  1020. } catch (e) { }
  1021. let _setAttribute = Element.prototype.setAttribute;
  1022. try {
  1023. _setAttribute = ShadyDOM.nativeMethods.setAttribute || _setAttribute;
  1024. } catch (e) { }
  1025. let _insertBefore = Node.prototype.insertBefore;
  1026. try {
  1027. _insertBefore = ShadyDOM.nativeMethods.insertBefore || _insertBefore;
  1028. } catch (e) { }
  1029. return { elementAppend, _setAttribute, _insertBefore };
  1030. })();
  1031.  
  1032.  
  1033. const getDMHelper = () => {
  1034. let _dm = document.getElementById('d-m');
  1035. if (!_dm) {
  1036. _dm = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  1037. _dm.id = 'd-m';
  1038. _insertBefore.call(document.documentElement, _dm, document.documentElement.firstChild);
  1039. }
  1040. const dm = _dm;
  1041. dm._setAttribute = _setAttribute;
  1042. let j = 0;
  1043. let attributeName_;
  1044. while (dm.hasAttribute(attributeName_ = `dm-${Math.floor(Math.random() * 314159265359 + 314159265359).toString(36)}`)) {
  1045. // none
  1046. }
  1047. const attributeName = attributeName_;
  1048. let qr = null;
  1049. const mo = new MutationObserver(() => {
  1050. if (qr !== null) {
  1051. if (j > 8) j = 0;
  1052. qr = (qr(), null);
  1053. }
  1054. });
  1055. mo.observe(document, { childList: true, subtree: true, attributes: true });
  1056. return (resolve) => {
  1057. if (!qr) dm._setAttribute(attributeName, ++j);
  1058. return qr = resolve;
  1059. // return qr = afInterupter = resolve;
  1060. };
  1061. };
  1062. const dmPN = getDMHelper();
  1063.  
  1064. let _dmPromise = null;
  1065. const getDMPromise = () => {
  1066. return (_dmPromise || (_dmPromise = (new Promise(dmPN)).then(() => {
  1067. _dmPromise = null;
  1068. })))
  1069. };
  1070.  
  1071. /**
  1072. *
  1073. * @param {number} f bit flag
  1074. * @param {number} w bit flag (with)
  1075. * @param {number} wo bit flag (without)
  1076. * @returns
  1077. */
  1078. const fT = function (f, w, wo) {
  1079. return (f & (w | wo)) === w
  1080. }
  1081.  
  1082. /* globals WeakRef:false */
  1083.  
  1084. /** @type {(o: Object | null) => WeakRef | null} */
  1085. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  1086.  
  1087. /** @type {(wr: Object | null) => Object | null} */
  1088. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  1089.  
  1090.  
  1091. // function prettyElm(/** @type {Element} */ elm) {
  1092. // if (!elm || !elm.nodeName) return null;
  1093. // const eId = elm.id || null;
  1094. // const eClsName = elm.className || null;
  1095. // return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  1096. // }
  1097.  
  1098. // function extractTextContent(/** @type {Node} */ elm) {
  1099. // 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, '')
  1100. // }
  1101.  
  1102. function addScript(/** @type {string} */ scriptText) {
  1103. const scriptNode = document.createElement('script');
  1104. scriptNode.type = 'text/javascript';
  1105. scriptNode.textContent = scriptText;
  1106. try {
  1107. document.documentElement.appendChild(scriptNode);
  1108. } catch (e) {
  1109. console.log('addScript Error', e)
  1110. }
  1111. return scriptNode;
  1112. }
  1113.  
  1114. function addScriptByURL(/** @type {string} */ scriptURL) {
  1115. const scriptNode = document.createElement('script');
  1116. scriptNode.type = 'text/javascript';
  1117. scriptNode.src = scriptURL;
  1118. try {
  1119. document.documentElement.appendChild(scriptNode);
  1120. } catch (e) {
  1121. console.log('addScriptByURL Error', e)
  1122. }
  1123. return scriptNode;
  1124. }
  1125.  
  1126. // function addStyle(/** @type {string} */ styleText, /** @type {HTMLElement | Document} */ container) {
  1127. // const styleNode = document.createElement('style');
  1128. // //styleNode.type = 'text/css';
  1129. // styleNode.textContent = styleText;
  1130. // (container || document.documentElement).appendChild(styleNode);
  1131. // return styleNode;
  1132. // }
  1133.  
  1134.  
  1135.  
  1136. /*
  1137.  
  1138. yt-action yt-add-element-to-app yt-autonav-pause-blur yt-autonav-pause-focus
  1139. yt-autonav-pause-guide-closed yt-autonav-pause-guide-opened yt-autonav-pause-player
  1140. yt-autonav-pause-player-ended yt-autonav-pause-scroll yt-autoplay-on-changed
  1141. yt-close-tou-form yt-consent-bump-display-changed yt-focus-searchbox
  1142. yt-get-context-provider yt-guide-close yt-guide-hover yt-guide-toggle
  1143. yt-history-load yt-history-pop yt-load-invalidation-continuation
  1144. yt-load-next-continuation yt-load-reload-continuation yt-load-tou-form
  1145. yt-masthead-height-changed yt-navigate yt-navigate-cache yt-navigate-error
  1146. yt-navigate-finish yt-navigate-redirect yt-navigate-set-page-offset
  1147. yt-navigate-start yt-next-continuation-data-updated yt-open-hotkey-dialog
  1148. yt-open-tou-form-loading-state yt-page-data-fetched yt-page-data-updated
  1149. yt-page-data-will-update yt-page-manager-navigate-start yt-page-navigate-start
  1150. yt-page-type-changed yt-player-attached yt-player-detached yt-player-released
  1151. yt-player-requested yt-player-updated yt-popup-canceled yt-popup-closed
  1152. yt-popup-opened yt-preconnect-urls yt-register-action yt-report-form-closed
  1153. yt-report-form-opened yt-request-panel-mode-change yt-retrieve-location
  1154. yt-service-request-completed yt-service-request-error yt-service-request-sent
  1155. yt-set-theater-mode-enabled yt-show-survey yt-subscription-changed
  1156. yt-swatch-changed yt-theater-mode-allowed yt-unregister-action yt-update-title
  1157. yt-update-unseen-notification-count yt-viewport-scanned yt-visibility-refresh
  1158.  
  1159. */
  1160.  
  1161.  
  1162. // _console.log(38489)
  1163.  
  1164. class ControllerIDInner {
  1165. constructor() {
  1166. this.q = 0;
  1167. }
  1168. set(v) {
  1169. this.q = v
  1170. }
  1171. valueOf() {
  1172. return this.q;
  1173. }
  1174. inc() {
  1175. if (++this.q > 1e9) this.q = 9;
  1176. return this.q;
  1177. }
  1178. }
  1179.  
  1180. const ControllerID = () => {
  1181. return new ControllerIDInner();
  1182. }
  1183.  
  1184.  
  1185. const psId = ControllerID();
  1186. psId.auto = false;
  1187.  
  1188. const PromiseExternal = ((resolve_, reject_) => {
  1189. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  1190. return class PromiseExternal extends Promise {
  1191. constructor(cb = h) {
  1192. super(cb);
  1193. if (cb === h) {
  1194. /** @type {(value: any) => void} */
  1195. this.resolve = resolve_;
  1196. /** @type {(reason?: any) => void} */
  1197. this.reject = reject_;
  1198. }
  1199. }
  1200. };
  1201. })();
  1202.  
  1203. const tabsInsertedPromise = new PromiseExternal();
  1204.  
  1205. let chatController = {
  1206. allowChatControl: true
  1207. };
  1208.  
  1209. class Deferred {
  1210. constructor() {
  1211. this.reset();
  1212. }
  1213. debounce(f, controllerId) {
  1214. let g = f;
  1215. if (controllerId) {
  1216. const tid = controllerId.auto === false ? controllerId.valueOf() : controllerId.inc();
  1217. g = () => (tid === controllerId.valueOf() ? f() : 0);
  1218. }
  1219. return this.promise.then().then(g).catch(console.warn); // avoid promise.then.then.then ...
  1220. }
  1221. d() {
  1222. return this.promise.then().catch(console.warn);
  1223. }
  1224. reset() {
  1225. this.resolved = false;
  1226. this.promise = new PromiseExternal();
  1227. }
  1228. resolve() {
  1229. if (this.resolved !== false) return false;
  1230. this.resolved = true;
  1231. this.promise.resolve(...arguments);
  1232. return true;
  1233. }
  1234. }
  1235.  
  1236. class Mutex {
  1237.  
  1238. constructor() {
  1239. this.p = Promise.resolve()
  1240. }
  1241.  
  1242. lockWith(f) {
  1243.  
  1244. this.p = this.p.then(() => {
  1245. return new Promise(f).catch(console.warn)
  1246. })
  1247. }
  1248.  
  1249. }
  1250.  
  1251. const rfKey = Symbol();
  1252.  
  1253. const ffReflection = (o, c) => {
  1254. // see https://github.com/erosman/support/issues/526
  1255. if (o.constructor !== c) {
  1256. const a = c[rfKey] || (c[rfKey] = Object.keys(Object.getOwnPropertyDescriptors(c.prototype)));
  1257. for (const k of a) {
  1258. o[k] = c.prototype[k];
  1259. }
  1260. }
  1261. }
  1262.  
  1263. /* FireMonkey unable to extend MutationObserver correctly */
  1264. class AttributeMutationObserver extends MutationObserver {
  1265. constructor(flist) {
  1266. super((mutations, observer) => {
  1267. for (const mutation of mutations) {
  1268. if (mutation.type === 'attributes') {
  1269. this.checker(mutation.target, mutation.attributeName)
  1270. }
  1271. }
  1272. })
  1273. this.flist = flist;
  1274. this.res = {}
  1275. ffReflection(this, AttributeMutationObserver);
  1276. }
  1277.  
  1278. takeRecords() {
  1279. super.takeRecords();
  1280. }
  1281. disconnect() {
  1282. this._target = null;
  1283. super.disconnect();
  1284. }
  1285. observe(/** @type {Node} */ target) {
  1286. if (this._target) return;
  1287. //console.log(123124, target)
  1288. this._target = mWeakRef(target);
  1289.  
  1290. //console.log(123125, kRef(this._target))
  1291. const options = {
  1292. attributes: true,
  1293. attributeFilter: Object.keys(this.flist),
  1294. //attributeFilter: [ "status", "username" ],
  1295. attributeOldValue: true
  1296. }
  1297. super.observe(target, options)
  1298. }
  1299. checker(/** @type {Node} */ target,/** @type {string} */ attributeName) {
  1300. let nv = target.getAttribute(attributeName);
  1301. if (this.res[attributeName] !== nv) {
  1302. this.res[attributeName] = nv
  1303. let f = this.flist[attributeName];
  1304. if (f) f(attributeName, nv);
  1305.  
  1306. }
  1307. }
  1308. check(delay = 0) {
  1309. setTimeout(() => {
  1310. let target = kRef(this._target)
  1311. if (target !== null) {
  1312. for (const key of Object.keys(this.flist)) {
  1313. this.checker(target, key)
  1314. }
  1315. } else {
  1316. console.log('target is null') //disconnected??
  1317. }
  1318. target = null;
  1319. }, delay)
  1320. }
  1321. }
  1322.  
  1323. class KDate extends Date {
  1324.  
  1325. constructor(...args) {
  1326. super(...args)
  1327. this.dayBack = false
  1328. }
  1329.  
  1330. browserSupported() {
  1331.  
  1332. }
  1333.  
  1334. lokStringDateEN() {
  1335. const d = this
  1336.  
  1337. let y = d.getFullYear()
  1338. let m = d.getMonth() + 1
  1339. let date = d.getDate()
  1340.  
  1341. let sy = y < 1000 ? (`0000${y}`).slice(-4) : '' + y
  1342.  
  1343. let sm = m < 10 ? '0' + m : '' + m
  1344. let sd = date < 10 ? '0' + date : '' + date
  1345.  
  1346. return `${sy}.${sm}.${sd}`
  1347.  
  1348. }
  1349.  
  1350. lokStringDateJP() {
  1351. const d = this
  1352.  
  1353. let y = d.getFullYear()
  1354. let m = d.getMonth() + 1
  1355. let date = d.getDate()
  1356.  
  1357. let sy = y < 1000 ? (`0000${y}`).slice(-4) : '' + y
  1358.  
  1359. let sm = m < 10 ? '0' + m : '' + m
  1360. let sd = date < 10 ? '0' + date : '' + date
  1361.  
  1362. return `${sy}/${sm}/${sd}`
  1363.  
  1364. }
  1365.  
  1366. lokStringTime() {
  1367. const d = this
  1368.  
  1369. let h = d.getHours()
  1370. let m = d.getMinutes()
  1371.  
  1372. const k = this.dayBack
  1373.  
  1374. if (k) h += 24
  1375.  
  1376. let sh = h < 10 ? '0' + h : '' + h
  1377. let sm = m < 10 ? '0' + m : '' + m
  1378.  
  1379. return `${sh}:${sm}`
  1380.  
  1381. }
  1382.  
  1383. }
  1384.  
  1385. console.assert('browserSupported' in (new KDate()),
  1386. { error: "0x87FF", errorMsg: "Your userscript manager is not supported. FireMonkey is not recommended." }
  1387. );
  1388.  
  1389. if (!('browserSupported' in (new KDate()))) return;
  1390.  
  1391. let chromeCSSFiles = null;
  1392.  
  1393. try {
  1394. const o = [{
  1395. id: 'tabview-css-content',
  1396. path: 'css/style_content.css'
  1397. }, {
  1398. id: 'tabview-css-chat',
  1399. path: 'css/style_chat.css'
  1400. }, {
  1401. id: 'tabview-css-control',
  1402. path: 'css/style_control.css'
  1403. }];
  1404.  
  1405. for (const e of o) e.url = chrome.runtime.getURL(e.path) || '';
  1406.  
  1407. chromeCSSFiles = o;
  1408.  
  1409. } catch (e) { }
  1410.  
  1411. if (chromeCSSFiles) {
  1412. const target = document.head || document.documentElement;
  1413. for (const { path, url, id } of chromeCSSFiles) {
  1414.  
  1415. if (url && typeof url === 'string' && url.length > 4) {
  1416.  
  1417. const link = document.createElement('link');
  1418. link.id = id;
  1419. link.rel = 'stylesheet';
  1420. link.type = 'text/css';
  1421. link.href = url;
  1422.  
  1423. target.appendChild(link);
  1424.  
  1425. }
  1426.  
  1427. }
  1428. chromeCSSFiles = null;
  1429.  
  1430. }
  1431.  
  1432. /*
  1433.  
  1434. function fixTheaterChat1() {
  1435. let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat:not([collapsed])')
  1436. if (incorrectChat) {
  1437. scriptletDeferred.debounce(()=>{
  1438. incorrectChat.dispatchEvent(new CustomEvent("collapsed-true"));
  1439. });
  1440. }
  1441. }
  1442.  
  1443.  
  1444. function fixTheaterChat2() {
  1445. let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat[collapsed]')
  1446. if (incorrectChat) {
  1447. scriptletDeferred.debounce(()=>{
  1448. incorrectChat.dispatchEvent(new CustomEvent("collapsed-false"));
  1449. });
  1450. }
  1451. }
  1452. */
  1453.  
  1454.  
  1455. function fixTheaterChat1A() {
  1456. let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat:not([collapsed])')
  1457. if (incorrectChat) {
  1458. ytBtnCollapseChat();
  1459. }
  1460. }
  1461.  
  1462.  
  1463. function fixTheaterChat2A() {
  1464. let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat[collapsed]')
  1465. if (incorrectChat) {
  1466. ytBtnExpandChat();
  1467. }
  1468. }
  1469.  
  1470. function check1885() {
  1471.  
  1472. if (chatController.ytlstmTheaterMode) {
  1473. if (document.fullscreenElement || document.querySelector('ytd-watch-flexy[fullscreen]')) {
  1474. chatController.allowChatControl = true;
  1475. chatController.ytlstmTheaterMode = false;
  1476. return;
  1477. }
  1478. }
  1479. if (chatController.allowChatControl) {
  1480. if (document.body.hasAttribute('data-ytlstm-theater-mode')) {
  1481. chatController.allowChatControl = false;
  1482. chatController.ytlstmTheaterMode = true;
  1483. }
  1484. } else {
  1485. if (!document.body.hasAttribute('data-ytlstm-theater-mode')) {
  1486. chatController.allowChatControl = true;
  1487. chatController.ytlstmTheaterMode = false;
  1488. }
  1489. }
  1490. }
  1491.  
  1492. function check1886() {
  1493.  
  1494. let cssContentNode = document.getElementById('tabview-css-content')
  1495. if (!cssContentNode) return;
  1496. let cssChatNode = document.getElementById('tabview-css-chat')
  1497. if (!cssChatNode) return;
  1498.  
  1499. if (cssContentNode.disabled === false) {
  1500. if (chatController.ytlstmTheaterMode) {
  1501. cssContentNode.disabled = true;
  1502. cssChatNode.disabled = true;
  1503. let t = document.querySelector('.youtube-genius-lyrics-found-hide-btn');
  1504. if (t) t.click();
  1505. ytBtnCloseEngagementPanels();
  1506. return 1;
  1507. }
  1508. } else if (cssContentNode.disabled === true) {
  1509. if (!chatController.ytlstmTheaterMode) {
  1510. cssContentNode.disabled = false;
  1511. cssChatNode.disabled = false;
  1512. return 2;
  1513. }
  1514. }
  1515.  
  1516. }
  1517.  
  1518. function dnsPrefetch() {
  1519.  
  1520. function linker(link, rel, href, _as) {
  1521. return new Promise(resolve => {
  1522. if (!link) link = document.createElement('link');
  1523. link.rel = rel;
  1524. if (_as) link.setAttribute('as', _as);
  1525. link.onload = function () {
  1526. resolve({
  1527. link: this,
  1528. success: true
  1529. })
  1530. this.remove();
  1531. };
  1532. link.onerror = function () {
  1533. resolve({
  1534. link: this,
  1535. success: false
  1536. });
  1537. this.remove();
  1538. };
  1539. link.href = href;
  1540. (document.head || document.documentElement).appendChild(link);
  1541. link = null;
  1542. });
  1543. }
  1544.  
  1545. new Promise(resolve => {
  1546.  
  1547. if (document.documentElement) {
  1548. resolve();
  1549. } else {
  1550.  
  1551. let mo = new MutationObserver(() => {
  1552. if (mo !== null && document.documentElement) {
  1553. mo.takeRecords();
  1554. mo.disconnect();
  1555. mo = null;
  1556. resolve();
  1557. }
  1558. });
  1559. mo.observe(document, { childList: true, subtree: false })
  1560.  
  1561. }
  1562.  
  1563. }).then(() => {
  1564.  
  1565. const hosts = [
  1566. 'https://i.ytimg.com',
  1567. 'https://www.youtube.com',
  1568. 'https://rr2---sn-5n5ip-ioqd.googlevideo.com',
  1569. 'https://googlevideo.com',
  1570. 'https://accounts.youtube.com',
  1571. 'https://jnn-pa.googleapis.com',
  1572. 'https://googleapis.com',
  1573. 'https://fonts.gstatic.com',
  1574. 'https://www.gstatic.com',
  1575. 'https://yt3.ggpht.com',
  1576. 'https://yt4.ggpht.com'
  1577. ]
  1578. for (const h of hosts) {
  1579.  
  1580.  
  1581. linker(null, 'preconnect', h);
  1582.  
  1583. }
  1584.  
  1585.  
  1586.  
  1587. });
  1588. }
  1589. dnsPrefetch();
  1590.  
  1591. const tabsDeferred = new Deferred();
  1592. tabsDeferred.resolve();
  1593.  
  1594. const discardableFn = function (f) { // logic to be reviewed; unstable
  1595. if (!scriptEnable && tabsDeferred.resolved) { } // ignore all before first yt-navigate-finished
  1596. else tabsDeferred.debounce(f, psId);
  1597. }
  1598.  
  1599. let layoutStatusMutex = new Mutex();
  1600.  
  1601. let sliderMutex = new Mutex();
  1602. const renderDeferred = new Deferred(); //pageRendered
  1603. let pageRendered = 0;
  1604. let renderIdentifier = ControllerID();
  1605. renderIdentifier.auto = false;
  1606.  
  1607. const scriptletDeferred = new Deferred();
  1608.  
  1609.  
  1610. function scriptInjector(script_id, url_chrome, response_id) {
  1611.  
  1612. let res = {
  1613. script_id: script_id,
  1614. inject: function () {
  1615.  
  1616. let res = this, script_id = this.script_id;
  1617.  
  1618. if (!document.querySelector(`script#${script_id}`)) {
  1619. if (res.runtime_url) {
  1620. addScriptByURL(res.runtime_url).id = script_id;
  1621. } else {
  1622. addScript(`${res.injection_script}`).id = script_id;
  1623. }
  1624. }
  1625.  
  1626. }
  1627. }
  1628. res.script_id = script_id;
  1629.  
  1630. if (isMyScriptInChromeRuntime()) {
  1631. res.runtime_url = window.chrome.runtime.getURL(url_chrome)
  1632. } else {
  1633. res.injection_script = GM_getResourceText(response_id);
  1634. }
  1635.  
  1636. return res;
  1637.  
  1638.  
  1639. }
  1640.  
  1641. const script_inject_js1 = scriptInjector(
  1642. 'userscript-tabview-injection-1',
  1643. 'js/injection_script_1.js',
  1644. "injectionJS1");
  1645.  
  1646. let scriptReady = false;
  1647. scriptletDeferred.debounce(() => {
  1648. scriptReady = true;
  1649. });
  1650. const sendToPageScriptPendings = new Map();
  1651. async function sendToPageScript(element, id, ...args) {
  1652. if (!scriptReady) {
  1653. const tid = (sendToPageScriptPendings.get(id) || 0) + 1
  1654. sendToPageScriptPendings.set(id, tid);
  1655. await scriptletDeferred.d();
  1656. const cid = sendToPageScriptPendings.get(id);
  1657. if (tid !== cid) return;
  1658. }
  1659. element.dispatchEvent(new CustomEvent("tabview-page-script", {
  1660. detail: {
  1661. id: id,
  1662. args: args.length ? args : null
  1663. }
  1664. }));
  1665. }
  1666.  
  1667.  
  1668. function nonCryptoRandStr(/** @type {number} */ n) {
  1669. const result = new Array(n);
  1670. const baseStr = nonCryptoRandStr_base;
  1671. const bLen = baseStr.length;
  1672. for (let i = 0; i < n; i++) {
  1673. let t = null
  1674. do {
  1675. t = baseStr.charAt(Math.floor(Math.random() * bLen));
  1676. } while (i === 0 && 10 - t > 0)
  1677. result[i] = t;
  1678. }
  1679. return result.join('');
  1680. }
  1681.  
  1682. const uidMAP = new Map();
  1683.  
  1684. function uidGEN(s) {
  1685. let uid = uidMAP.get(s);
  1686. if (!uid) {
  1687. const uidStore = ObserverRegister.uidStore;
  1688. do {
  1689. uid = nonCryptoRandStr(5);
  1690. } while (uidStore[uid])
  1691. uidMAP.set(s, uid);
  1692. }
  1693. return uid;
  1694. }
  1695.  
  1696. /**
  1697. * Class definition
  1698. * @property {string} propName - propriety description
  1699. * ...
  1700. */
  1701. class ObserverRegister {
  1702.  
  1703. constructor(/** @type {()=>MutationObserver | IntersectionObserver} */ observerCreator) {
  1704. let uid = null;
  1705. const uidStore = ObserverRegister.uidStore;
  1706. do {
  1707. uid = nonCryptoRandStr(5);
  1708. } while (uidStore[uid])
  1709. uidStore[uid] = true;
  1710.  
  1711. /**
  1712. * uid is the unique string for each observer
  1713. * @type {string}
  1714. * @public
  1715. */
  1716. this.uid = uid;
  1717.  
  1718. /**
  1719. * observerCreator is a function to create the observer
  1720. * @type {Function}
  1721. * @public
  1722. */
  1723. this.observerCreator = observerCreator
  1724.  
  1725. /**
  1726. * observer is the actual observer object
  1727. * @type {MutationObserver | IntersectionObserver}
  1728. * @public
  1729. */
  1730. this.observer = null;
  1731. this.bindCount = 0;
  1732. ffReflection(this, ObserverRegister);
  1733. }
  1734. bindElement(/** @type {HTMLElement} */ elm, ...args) {
  1735. if (elm.hasAttribute(`o3r-${this.uid}`)) return false;
  1736. elm.setAttribute(`o3r-${this.uid}`, '')
  1737. this.bindCount++;
  1738. if (this.observer === null) {
  1739. this.observer = this.observerCreator();
  1740. }
  1741. this.observer.observe(elm, ...args)
  1742. return true
  1743. }
  1744. clear(/** @type {boolean} */ flag) {
  1745. if (this.observer !== null) {
  1746. //const uidStore = ObserverRegister.uidStore;
  1747. if (flag === true) {
  1748. this.observer.takeRecords();
  1749. this.observer.disconnect();
  1750. }
  1751. this.observer = null;
  1752. this.bindCount = 0;
  1753. for (const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`)
  1754. //uidStore[this.uid]=false;
  1755. //this.uid = null;
  1756. }
  1757. }
  1758. }
  1759.  
  1760. /**
  1761. * 'uidStore' is the static store of strings used.
  1762. * @static
  1763. */
  1764. ObserverRegister.uidStore = {}; // backward compatible with FireFox 55.
  1765.  
  1766.  
  1767. const mtoObservationDetails = new ObserverRegister(() => {
  1768. return new IntersectionObserver(ito_details, {
  1769. root: null,
  1770. rootMargin: "0px"
  1771. })
  1772. });
  1773.  
  1774.  
  1775. const mtoFlexyAttr = new ObserverRegister(() => {
  1776. return new MutationObserver(mtf_attrFlexy)
  1777. });
  1778.  
  1779. const mtoBodyAttr = new ObserverRegister(() => {
  1780. return new MutationObserver(mtf_attrBody)
  1781. });
  1782.  
  1783. const mtoVisibility_EngagementPanel = new ObserverRegister(() => {
  1784. return new MutationObserver(FP.mtf_attrEngagementPanel)
  1785. });
  1786.  
  1787. const mtoVisibility_Playlist = new ObserverRegister(() => {
  1788. return new AttributeMutationObserver({
  1789. "hidden": FP.mtf_attrPlaylist
  1790. })
  1791. })
  1792. const sa_playlist = mtoVisibility_Playlist.uid;
  1793.  
  1794. const mtoVisibility_Comments = new ObserverRegister(() => {
  1795. return new AttributeMutationObserver({
  1796. "hidden": FP.mtf_attrComments
  1797. })
  1798. })
  1799. const sa_comments = mtoVisibility_Comments.uid;
  1800.  
  1801.  
  1802. const mtoVisibility_Chatroom = new ObserverRegister(() => {
  1803. return new AttributeMutationObserver({
  1804. "collapsed": FP.mtf_attrChatroom
  1805. })
  1806. })
  1807. // const sa_chatroom = mtoVisibility_Chatroom.uid;
  1808.  
  1809.  
  1810.  
  1811.  
  1812. function isDOMVisible(/** @type {HTMLElement} */ elem) {
  1813. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  1814. return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
  1815. }
  1816.  
  1817. function isNonEmptyString(s) {
  1818. return typeof s == 'string' && s.length > 0;
  1819. }
  1820.  
  1821. async function dispatchWindowResize() {
  1822. // for youtube to detect layout resize for adjusting Player tools
  1823. return window.dispatchEvent(new Event('resize'));
  1824. }
  1825.  
  1826. async function dispatchCommentRowResize() {
  1827.  
  1828. if (pageType !== "watch") return;
  1829.  
  1830. const ytdFlexyElm = es.ytdFlexy;
  1831. if (!ytdFlexyElm) return;
  1832. if (ytdFlexyElm.getAttribute('tyt-tab') !== '#tab-comments') return;
  1833.  
  1834. sendToPageScript(document, 'tabview-resize-comments-rows');
  1835.  
  1836.  
  1837. }
  1838.  
  1839. function enterPIP(video, errorHandler) { // ignore audio
  1840. return new Promise(resolve => {
  1841. if (video && typeof video.requestPictureInPicture === 'function' && typeof document.exitPictureInPicture === 'function') {
  1842. if (isVideoPlaying(video) && document.pictureInPictureElement === null) {
  1843. video.requestPictureInPicture().then(res => {
  1844. resolve(true);
  1845. }).catch((e) => {
  1846. if (errorHandler === undefined) console.warn(e);
  1847. else if (typeof errorHandler == 'function') errorHandler(e);
  1848. resolve(false);
  1849. });
  1850. } else {
  1851. resolve(false);
  1852. }
  1853. } else {
  1854. resolve(null);
  1855. }
  1856. })
  1857. }
  1858.  
  1859. function exitPIP() {
  1860. if (document.pictureInPictureElement !== null && typeof document.exitPictureInPicture === 'function') {
  1861. document.exitPictureInPicture().then(res => {
  1862.  
  1863. }).catch(console.warn)
  1864. }
  1865. }
  1866.  
  1867. function setToggleBtnTxt() {
  1868.  
  1869. if (chatroomDetails && typeof chatroomDetails.txt_expand === 'string' && typeof chatroomDetails.txt_collapse === 'string') { // toggle button (show-hide-button)
  1870. // _console.log(124234, 'c=== ')
  1871.  
  1872. let chat = document.querySelector('ytd-live-chat-frame#chat');
  1873. if (!chat) return;
  1874. let txt = _querySelector.call(chat, 'span.yt-core-attributed-string[role="text"]');
  1875. let c = (txt || 0).textContent;
  1876.  
  1877. if (typeof c === 'string' && c.length > 2) {
  1878. if (chat.hasAttribute('collapsed')) {
  1879. // _console.log(124234, 'collapsed show expand ', chatroomDetails.txt_expand)
  1880. if (c !== chatroomDetails.txt_expand) {
  1881. txt.textContent = chatroomDetails.txt_expand;
  1882. }
  1883. } else {
  1884. // _console.log(124234, 'not collapsed show collapse ', chatroomDetails.txt_collapse)
  1885. if (c !== chatroomDetails.txt_collapse) {
  1886. txt.textContent = chatroomDetails.txt_collapse;
  1887. }
  1888. }
  1889. }
  1890. } else if (chatroomDetails && typeof chatroomDetails.txt_expand === 'string') { // show button only
  1891.  
  1892.  
  1893. let chat = document.querySelector('ytd-live-chat-frame#chat');
  1894. if (!chat) return;
  1895. let txt = _querySelector.call(chat, 'span.yt-core-attributed-string[role="text"]');
  1896. let c = (txt || 0).textContent;
  1897.  
  1898. if (typeof c === 'string' && c.length > 2) {
  1899. if (chat.hasAttribute('collapsed')) {
  1900. // _console.log(124234, 'collapsed show expand ', chatroomDetails.txt_expand)
  1901. if (c !== chatroomDetails.txt_expand) {
  1902. txt.textContent = chatroomDetails.txt_expand;
  1903. }
  1904. }
  1905. }
  1906.  
  1907. }
  1908. }
  1909.  
  1910.  
  1911. function handlerTabExpanderClick() {
  1912.  
  1913. scriptletDeferred.debounce(() => {
  1914.  
  1915.  
  1916.  
  1917.  
  1918. async function b() {
  1919.  
  1920. let h1 = document.documentElement.clientHeight;
  1921. let h2 = (document.querySelector('#right-tabs') || 0).clientHeight;
  1922.  
  1923. await Promise.resolve(0);
  1924. if (h1 > 300 && h2 > 300) {
  1925. let ratio = h2 / h1; // positive below 1.0
  1926.  
  1927. return ratio;
  1928. }
  1929. return 0;
  1930. }
  1931.  
  1932. async function a() {
  1933.  
  1934.  
  1935. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  1936. if (secondary) {
  1937.  
  1938.  
  1939. if (!secondary.classList.contains('tabview-hover-slider-enable')) {
  1940.  
  1941. let secondaryInner = _querySelector.call(secondary, '#secondary-inner.ytd-watch-flexy');
  1942.  
  1943. if (secondaryInner) {
  1944.  
  1945. if (!secondary.classList.contains('tabview-hover-slider')) {
  1946. // without hover
  1947.  
  1948. //let rect = secondary.getBoundingClientRect();
  1949. //let rectI = secondaryInner.getBoundingClientRect();
  1950.  
  1951. secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`)
  1952.  
  1953. }
  1954.  
  1955. let ratio = await b();
  1956. if (ratio > 0.0 && ratio <= 1.0) {
  1957.  
  1958. secondaryInner.style.setProperty('--ytd-watch-flexy-sidebar-width-d', `${Math.round(100 * ratio * 10) / 10}vw`);
  1959. secondary.classList.add('tabview-hover-slider');
  1960. secondary.classList.add('tabview-hover-slider-enable');
  1961.  
  1962. let video = document.querySelector('#player video'); // ignore audio
  1963. enterPIP(video);
  1964.  
  1965. }
  1966.  
  1967. }
  1968.  
  1969.  
  1970. } else {
  1971.  
  1972.  
  1973. secondary.dispatchEvent(new CustomEvent("tabview-hover-slider-restore"));
  1974. //console.log(1994)
  1975.  
  1976. }
  1977.  
  1978. // no animation event triggered for hover -> enable
  1979. dispatchCommentRowResize();
  1980.  
  1981. }
  1982.  
  1983.  
  1984.  
  1985. }
  1986.  
  1987.  
  1988. a();
  1989.  
  1990.  
  1991. })
  1992.  
  1993.  
  1994. }
  1995.  
  1996. let global_columns_end_ito = null;
  1997.  
  1998. function setupHoverSlider(secondary, columns) {
  1999.  
  2000. if (!secondary || !columns) return;
  2001. let attrName = `o4r-${uidGEN('tabview-hover-slider-restore')}`;
  2002.  
  2003. if (secondary.hasAttribute(attrName)) return;
  2004. secondary.setAttribute(attrName, '');
  2005.  
  2006. let elmB = document.querySelector('tabview-view-secondary-xpander');
  2007. if (!elmB) {
  2008. elmB = document.createElement('tabview-view-secondary-xpander');
  2009. prependTo(elmB, secondary);
  2010. }
  2011.  
  2012. let elmA = document.querySelector('tabview-view-columns-endpos');
  2013. if (elmA) elmA.remove();
  2014. elmA = document.createElement('tabview-view-columns-endpos');
  2015.  
  2016. let itoA = new IntersectionObserver((entries) => {
  2017. let t = null;
  2018. let w = enableHoverSliderDetection
  2019. for (const entry of entries) {
  2020. if (entry.rootBounds === null) continue;
  2021. let bcr = entry.boundingClientRect;
  2022. let rb = entry.rootBounds;
  2023. t = !entry.isIntersecting && (bcr.left > rb.right) && (rb.left <= 0);
  2024. // if entries.length>1 (unlikely); take the last intersecting
  2025. // supplement cond 1. ensure the col element is in the right side
  2026. // supplement cond 2. ensure column is wide enough for overflow checking
  2027. // it can also avoid if the layout change happened but attribute not yet changed during the intersection observation
  2028. }
  2029.  
  2030. let columns = document.querySelector('#columns.style-scope.ytd-watch-flexy');
  2031. if (columns) columns.classList.toggle('tyt-column-overflow', t);
  2032.  
  2033. if (w !== t && t !== null) {
  2034. // t can be true when the layout enters single column mode
  2035. enableHoverSliderDetection = t;
  2036. }
  2037. //console.log(entries, enableHoverSliderDetection, t)
  2038. })
  2039. elementAppend.call(columns, elmA); // append to dom first before observe
  2040. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  2041. //to trigger observation at the time layout being changed
  2042. itoA.observe(elmA);
  2043. }
  2044. global_columns_end_ito = itoA;
  2045.  
  2046.  
  2047. secondary.addEventListener('tabview-hover-slider-restore', function (evt) {
  2048.  
  2049. let secondary = evt.target;
  2050.  
  2051. if (!secondary.classList.contains('tabview-hover-slider-enable')) return;
  2052.  
  2053. let secondaryInner = _querySelector.call(secondary, '#secondary-inner.ytd-watch-flexy')
  2054.  
  2055. if (!secondaryInner) return;
  2056.  
  2057. if (secondary.classList.contains('tabview-hover-slider-hover')) {
  2058.  
  2059. Promise.resolve(0).then(() => {
  2060. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  2061. }).then(() => {
  2062. secondary.classList.remove('tabview-hover-slider-enable')
  2063. exitPIP();
  2064. })
  2065.  
  2066. } else {
  2067.  
  2068. let secondary = evt.target;
  2069. secondary.classList.remove('tabview-hover-slider')
  2070. secondary.classList.remove('tabview-hover-slider-enable')
  2071.  
  2072. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  2073. secondaryInner.style.removeProperty('--tabview-slider-right')
  2074.  
  2075. exitPIP();
  2076.  
  2077. }
  2078.  
  2079. getDMPromise().then(() => {
  2080. updateFloatingSlider()
  2081. });
  2082.  
  2083. }, false);
  2084.  
  2085. }
  2086.  
  2087. function addTabExpander(tabContent) {
  2088.  
  2089. if (!tabContent) return null;
  2090. let id = tabContent.id;
  2091. if (!id || typeof id !== 'string') return null;
  2092.  
  2093. if (_querySelector.call(tabContent, `#${id} > tabview-view-tab-expander`)) return false;
  2094.  
  2095. let elm = document.createElement('tabview-view-tab-expander')
  2096. prependTo(elm, tabContent);
  2097. elm.innerHTML = createHTML(`<div>${svgElm(16, 16, 12, 12, svgDiag1, 'svg-expand')}${svgElm(16, 16, 12, 12, svgDiag2, 'svg-collapse')}</div>`);
  2098. elm.addEventListener('click', handlerTabExpanderClick, false);
  2099. return true;
  2100.  
  2101. }
  2102.  
  2103. function getColumnOverflowWidth() {
  2104.  
  2105. let screenWidth = document.documentElement.getBoundingClientRect().width;
  2106.  
  2107. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  2108.  
  2109. if (posElm1) {
  2110.  
  2111. let offset = posElm1.getBoundingClientRect().x - screenWidth;
  2112. return offset
  2113.  
  2114. }
  2115. return null
  2116. }
  2117.  
  2118. function getSecondaryInnerRight() {
  2119.  
  2120. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  2121.  
  2122. let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander');
  2123.  
  2124. if (posElm1 && posElm2) {
  2125.  
  2126. let offset = posElm1.getBoundingClientRect().x - posElm2.getBoundingClientRect().right;
  2127. return offset
  2128.  
  2129. }
  2130. return null
  2131.  
  2132. }
  2133.  
  2134. const setFloatingSliderOffset = (secondaryInner) => {
  2135.  
  2136.  
  2137. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  2138.  
  2139. let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander');
  2140.  
  2141. if (posElm1 && posElm2) {
  2142.  
  2143. let offset = getColumnOverflowWidth();
  2144.  
  2145. let k = 1.0
  2146. if (offset >= 125) {
  2147. k = 1.0
  2148. } else if (offset >= 75) {
  2149. k = 1.0;
  2150. } else if (offset >= 25) {
  2151. k = 0.25;
  2152. } else {
  2153. k = 0.0
  2154. }
  2155. secondaryInner.style.setProperty('--tabview-slider-offset-k2', `${k}`);
  2156. secondaryInner.style.setProperty('--tabview-slider-offset', `${offset}px`) // unnecessary
  2157.  
  2158. let oriWidth = posElm2.getBoundingClientRect().width;
  2159. secondaryInner.style.setProperty('--tabview-slider-ow', `${oriWidth}px`)
  2160.  
  2161. let s1 = 'var(--ytd-watch-flexy-sidebar-width-d)';
  2162. // new width
  2163.  
  2164. let s2 = `var(--tabview-slider-ow)`;
  2165. // ori width - youtube changing the code -> not reliable to use css prop.
  2166.  
  2167. let s3 = `${offset}px`;
  2168. // how many px wider than the page
  2169.  
  2170. secondaryInner.style.setProperty('--tabview-slider-offset-actual', `calc(${s1} - ${s2} + ${s3})`)
  2171.  
  2172. }
  2173.  
  2174. }
  2175.  
  2176. async function updateFloatingSlider_A(secondaryInner) {
  2177.  
  2178. // [is-extra-wide-video_]
  2179. await getDMPromise(); // time allowed for dom changes and value change of enableHoverSliderDetection
  2180.  
  2181. let secondary = nodeParent(secondaryInner);
  2182. if (!secondary) return;
  2183.  
  2184. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  2185. return;
  2186. }
  2187.  
  2188. if (!secondary.matches('#columns.ytd-watch-flexy #primary.ytd-watch-flexy ~ #secondary.ytd-watch-flexy')) {
  2189. return;
  2190. }
  2191.  
  2192. const bool = enableHoverSliderDetection === true;
  2193. const hasClassHover = secondary.classList.contains('tabview-hover-slider-hover') === true;
  2194.  
  2195. if (bool || hasClassHover) {
  2196. } else {
  2197. return;
  2198. }
  2199.  
  2200. await Promise.resolve(0);
  2201.  
  2202. secondary.classList.add('tabview-hover-final')
  2203.  
  2204. if (hasClassHover && !bool) {
  2205. secondaryInner.style.removeProperty('--tabview-slider-right')
  2206. secondaryInner.style.removeProperty('--tabview-slider-offset')
  2207. } else {
  2208.  
  2209. if (!hasClassHover) {
  2210. secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`)
  2211. }
  2212.  
  2213. setFloatingSliderOffset(secondaryInner);
  2214. }
  2215.  
  2216. if (bool ^ hasClassHover) {
  2217. secondary.classList.toggle('tabview-hover-slider', bool)
  2218. secondary.classList.toggle('tabview-hover-slider-hover', bool)
  2219. }
  2220.  
  2221. await Promise.resolve(0);
  2222.  
  2223.  
  2224. setTimeout(() => {
  2225. secondary.classList.remove('tabview-hover-final')
  2226. }, 350)
  2227.  
  2228.  
  2229. }
  2230.  
  2231.  
  2232. function updateFloatingSlider() {
  2233.  
  2234. let secondaryInner = document.querySelector('ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')
  2235.  
  2236. if (!secondaryInner) return;
  2237.  
  2238. let secondary = nodeParent(secondaryInner);
  2239. if (!secondary) return;
  2240.  
  2241. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  2242. return;
  2243. }
  2244.  
  2245. let t = document.documentElement.clientWidth; //integer
  2246.  
  2247. sliderMutex.lockWith(unlock => {
  2248.  
  2249. let v = document.documentElement.clientWidth; //integer
  2250.  
  2251. if (t === v && secondaryInner.matches('body ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')) {
  2252.  
  2253. updateFloatingSlider_A(secondaryInner).then(unlock);
  2254. } else {
  2255. unlock();
  2256. }
  2257.  
  2258. })
  2259.  
  2260. }
  2261.  
  2262. // iframe loadprocess interupted when expanded -> collasped
  2263. async function checkChatIframeOnExpanded() {
  2264. const iframe = document.getElementById('chatframe');
  2265. if (!(iframe instanceof HTMLIFrameElement)) return;
  2266. if (((iframeLoadStatusWM.get(iframe) || 0) % 2) === 1) return;
  2267. await iframePipeline(() => { });
  2268. if (((iframeLoadStatusWM.get(iframe) || 0) % 2) === 1) return;
  2269. let iframeLocation;
  2270. try {
  2271. iframeLocation = iframe.contentDocument.location;
  2272. } catch (e) { }
  2273. if (!iframeLocation) return;
  2274. if (iframeLocation.href.includes('youtube.com')) {
  2275.  
  2276. await iframePipeline(async () => {
  2277. if (((iframeLoadStatusWM.get(iframe) || 0) % 2) === 1) return;
  2278. await iframeLoadHookHandlerPromise.then();
  2279. if (iframe.isConnected === true && iframe.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')) {
  2280. await iframeLoadProcess(iframe);
  2281. }
  2282. });
  2283.  
  2284. }
  2285.  
  2286. // console.log(1421, iframeLocation.href);
  2287. }
  2288.  
  2289.  
  2290. const canScrollIntoViewWithOptions = (() => {
  2291.  
  2292. const element = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  2293. let i = 0;
  2294. try {
  2295. element.scrollIntoView({
  2296. get behavior() { i++ },
  2297. get block() { i++ }
  2298. });
  2299. } catch (e) {
  2300.  
  2301. }
  2302. return i === 2;
  2303.  
  2304.  
  2305. })();
  2306. // Chrome >= 61, Edge >= 79, Firefox >= 36, Opera >=48, Safari >=14
  2307.  
  2308. /** @param {Element} elm */
  2309. const stableScroll = async (elm, options) => {
  2310.  
  2311. const f = (p) => `${p.height}|${p.width}|${p.left}|${p.top}`
  2312. let i = 0;
  2313. while (i++ < 4) {
  2314. const p = elm.getBoundingClientRect();
  2315. const ps = f(p);
  2316. const rr = new Promise(resolve => {
  2317. document.addEventListener('scroll', resolve, {
  2318. capture: true, passive: true, once: true
  2319. })
  2320. });
  2321. elm.scrollIntoView(options);
  2322. await rr;
  2323. await new Promise(resolve => {
  2324. let io = new IntersectionObserver(() => {
  2325. resolve();
  2326. io.disconnect();
  2327. io = null;
  2328. });
  2329. io.observe(elm)
  2330. });
  2331. const q = elm.getBoundingClientRect();
  2332. const qs = f(q);
  2333.  
  2334. if (ps === qs) break;
  2335. }
  2336.  
  2337.  
  2338. }
  2339.  
  2340. const fullScreenTabScrollIntoView = canScrollIntoViewWithOptions ? () => {
  2341. const scrollElement = document.querySelector('ytd-app[scrolling]')
  2342. const b = scrollElement && isFullScreen() && isWideScreenWithTwoColumns() && (isChatExpand() || isTabExpanded() || isEngagementPanelExpanded() || isDonationShelfExpanded());
  2343. if (!b) return;
  2344. // single column view; click button; scroll to tab content area 100%
  2345. const rightTabs = document.querySelector('#right-tabs');
  2346. const pTop = rightTabs.getBoundingClientRect().top - scrollElement.getBoundingClientRect().top
  2347. if (rightTabs && pTop > 0) {
  2348. const secondaryInner = rightTabs.closest('#secondary-inner');
  2349. if (secondaryInner && secondaryInner.getBoundingClientRect().height < document.documentElement.clientHeight && document.documentElement.clientHeight > 80) {
  2350. stableScroll(secondaryInner, { behavior: "instant", block: "end", inline: "nearest" });
  2351. }
  2352. }
  2353. } : null;
  2354.  
  2355.  
  2356. function setToActiveTab(defaultTab) {
  2357. if (isTheater() && isWideScreenWithTwoColumns()) return;
  2358. const jElm = document.querySelector(`a[tyt-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  2359. document.querySelector(`a[tyt-tab-content="${(defaultTab || settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  2360. document.querySelector(`a[tyt-tab-content="${(SETTING_DEFAULT_TAB_0)}"]:not(.tab-btn-hidden)`) ||
  2361. document.querySelector("a[tyt-tab-content]:not(.tab-btn-hidden)") ||
  2362. null;
  2363.  
  2364. switchTabActivity(jElm);
  2365. return !!jElm;
  2366. }
  2367.  
  2368. let enableLivePopupCheck = false
  2369.  
  2370. function layoutStatusChanged(/** @type {number} */ old_layoutStatus, /** @type {number} */ new_layoutStatus) {
  2371.  
  2372.  
  2373. if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === 0) makeHeaderFloat();
  2374.  
  2375. //if (old_layoutStatus === new_layoutStatus) return;
  2376.  
  2377. const cssElm = es.ytdFlexy;
  2378.  
  2379. if (!cssElm) return;
  2380.  
  2381.  
  2382. const BF_TWOCOL_N_THEATER = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER
  2383.  
  2384. let new_isExpandedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_EXPANDED)
  2385. let new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  2386.  
  2387. let new_isTabExpanded = !!(new_layoutStatus & LAYOUT_TAB_EXPANDED);
  2388. let new_isFullScreen = !!(new_layoutStatus & LAYOUT_FULLSCREEN);
  2389. let new_isExpandedEPanel = !!(new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPANDED);
  2390. let new_isExpandedDonationShelf = !!(new_layoutStatus & LAYOUT_DONATION_SHELF_EXPANDED);
  2391.  
  2392. function showTabOrChat() {
  2393.  
  2394. layoutStatusMutex.lockWith(unlock => {
  2395.  
  2396. if (lstTab.lastPanel == '#chatroom') {
  2397.  
  2398. if (new_isTabExpanded) switchTabActivity(null)
  2399. if (!new_isExpandedChat) ytBtnExpandChat();
  2400.  
  2401. } else if (lstTab.lastPanel && lstTab.lastPanel.indexOf('#engagement-panel-') == 0) {
  2402.  
  2403. if (new_isTabExpanded) switchTabActivity(null)
  2404. if (!new_isExpandedEPanel) ytBtnOpenEngagementPanel(lstTab.lastPanel);
  2405.  
  2406. } else if (lstTab.lastPanel == '#donation-shelf') {
  2407.  
  2408. if (new_isTabExpanded) switchTabActivity(null)
  2409. if (!new_isExpandedDonationShelf) openDonationShelf();
  2410.  
  2411. } else {
  2412.  
  2413. if (new_isExpandedChat) ytBtnCollapseChat()
  2414. if (!new_isTabExpanded) setToActiveTab();
  2415.  
  2416. }
  2417. getDMPromise().then(unlock);
  2418.  
  2419. })
  2420. }
  2421.  
  2422. function hideTabAndChat() {
  2423.  
  2424. layoutStatusMutex.lockWith(unlock => {
  2425.  
  2426. if (new_isTabExpanded) switchTabActivity(null)
  2427. if (new_isExpandedChat) ytBtnCollapseChat()
  2428. if (new_isExpandedEPanel) ytBtnCloseEngagementPanels();
  2429. if (new_isExpandedDonationShelf) closeDonationShelf();
  2430.  
  2431. getDMPromise().then(unlock);
  2432.  
  2433. })
  2434.  
  2435. }
  2436.  
  2437. const statusCollapsedFalse = !!(new_layoutStatus & (LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED))
  2438. const statusCollapsedTrue = !statusCollapsedFalse
  2439.  
  2440. let changes = (old_layoutStatus & LAYOUT_VAILD) ? old_layoutStatus ^ new_layoutStatus : 0;
  2441.  
  2442. let chat_collapsed_changed = !!(changes & LAYOUT_CHATROOM_COLLAPSED)
  2443. let chat_expanded_changed = !!(changes & LAYOUT_CHATROOM_EXPANDED)
  2444. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  2445. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  2446. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  2447. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  2448. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPANDED)
  2449. let ds_expanded_changed = !!(changes & LAYOUT_DONATION_SHELF_EXPANDED)
  2450.  
  2451. // _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)
  2452.  
  2453.  
  2454. //console.log(169, 1, chat_collapsed_changed, tab_expanded_changed)
  2455. //console.log(169, 2, new_isExpandedChat, new_isCollapsedChat, new_isTabExpanded)
  2456.  
  2457. let BF_LayoutCh_Panel = (changes & (LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED))
  2458. let tab_change = BF_LayoutCh_Panel;
  2459. let isChatOrTabExpandTriggering = !!((new_layoutStatus) & BF_LayoutCh_Panel);
  2460. let isChatOrTabCollaspeTriggering = !!((~new_layoutStatus) & BF_LayoutCh_Panel);
  2461.  
  2462.  
  2463. const moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandedEPanel + new_isExpandedDonationShelf) > 1
  2464.  
  2465.  
  2466. const base_twoCol_NoTheather_chatExpand_a = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED
  2467. const base_twoCol_NoTheather_chatExpand_b = LAYOUT_TWO_COLUMNS | 0 | LAYOUT_CHATROOM | 0
  2468.  
  2469. // two column; not theater; tab collapse; chat expand; ep expand
  2470. const IF_01a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  2471. const IF_01b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  2472.  
  2473.  
  2474. // two column; not theater; tab collapse; chat expand; ep expand
  2475. const IF_07a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED;
  2476. const IF_07b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_DONATION_SHELF_EXPANDED;
  2477.  
  2478.  
  2479. // two column; not theater;
  2480. const IF_02a = BF_TWOCOL_N_THEATER;
  2481. const IF_02b = LAYOUT_TWO_COLUMNS;
  2482.  
  2483. // two column; not theater; tab expand; chat expand;
  2484. const IF_03a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED;
  2485. const IF_03b = base_twoCol_NoTheather_chatExpand_b | LAYOUT_TAB_EXPANDED;
  2486.  
  2487.  
  2488. // two column; tab expand; chat expand;
  2489. const IF_06a = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED;
  2490. const IF_06b = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | 0;
  2491.  
  2492.  
  2493. // two column; theater;
  2494. const IF_04a = BF_TWOCOL_N_THEATER;
  2495. const IF_04b = BF_TWOCOL_N_THEATER;
  2496.  
  2497. // not fullscreen; two column; not theater; not tab expand; not EP expand; not expand chat; not donation shelf
  2498. const IF_05a = LAYOUT_FULLSCREEN | LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED;
  2499. const IF_05b = 0 | LAYOUT_TWO_COLUMNS | 0 | 0 | 0 | 0 | 0;
  2500.  
  2501. let _isChatPopupedF = null
  2502. let isChatPopupedF = () => {
  2503. return _isChatPopupedF === null ? (_isChatPopupedF = cssElm.classList.contains('tyt-chat-popup')) : _isChatPopupedF
  2504. }
  2505.  
  2506. if (chat_expanded_changed && new_isExpandedChat) {
  2507. checkChatIframeOnExpanded();
  2508. }
  2509.  
  2510. const checkForMoreThanOne = () => {
  2511.  
  2512. if (moreThanOneShown && (new_layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  2513.  
  2514. layoutStatusMutex.lockWith(unlock => {
  2515. if (new_isTabExpanded && lstTab.lastPanel === null) {
  2516. if (new_isExpandedChat) ytBtnCollapseChat();
  2517. if (new_isExpandedEPanel) ytBtnCloseEngagementPanels();
  2518. if (new_isExpandedDonationShelf) closeDonationShelf();
  2519. } else if (lstTab.lastPanel) {
  2520. let lastPanel = lstTab.lastPanel || '';
  2521. if (typeof lastPanel !== 'string') lastPanel = '';
  2522. if (new_isExpandedChat && lastPanel !== '#chatroom') ytBtnCollapseChat();
  2523. if (new_isExpandedEPanel && lastPanel.indexOf('#engagement-panel-') < 0) ytBtnCloseEngagementPanels();
  2524. if (new_isExpandedDonationShelf && lastPanel !== '#donation-shelf') closeDonationShelf();
  2525. switchTabActivity(null);
  2526. }
  2527. getDMPromise().then(unlock);
  2528. });
  2529. }
  2530. }
  2531.  
  2532. let waitingPromise = Promise.resolve();
  2533.  
  2534. if (new_isFullScreen) {
  2535.  
  2536.  
  2537. if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_06a) === IF_06b && statusCollapsedFalse && !column_mode_changed) {
  2538.  
  2539. // two column; tab expand; chat expand;
  2540.  
  2541. switchTabActivity(null);
  2542.  
  2543. }
  2544.  
  2545. if (!!(tab_change & LAYOUT_CHATROOM_EXPANDED) && new_isExpandedChat) {
  2546. //tab_change = LAYOUT_CHATROOM_EXPANDED
  2547. //tab_change = LAYOUT_CHATROOM_EXPANDED|LAYOUT_TAB_EXPANDED
  2548.  
  2549.  
  2550. /*
  2551. triggered by iframe close, not by button click
  2552. */
  2553.  
  2554.  
  2555. canScrollIntoViewWithOptions && waitingPromise.then(async () => {
  2556. await getDMPromise();
  2557. let scrollElement = document.querySelector('ytd-app[scrolling]')
  2558. if (!scrollElement) return;
  2559. // single column view; click button; scroll to tab content area 100%
  2560. let chatFrame = document.querySelector('ytd-live-chat-frame#chat');
  2561. if (chatFrame && isChatExpand()) {
  2562. // _console.log(7290, 1)
  2563. fullScreenTabScrollIntoView();
  2564. }
  2565. })
  2566.  
  2567. }
  2568.  
  2569. if (!!(tab_change & LAYOUT_ENGAGEMENT_PANEL_EXPANDED) && new_isExpandedEPanel) {
  2570.  
  2571. waitingPromise.then(async () => {
  2572. await getDMPromise();
  2573. let scrollElement = document.querySelector('ytd-app[scrolling]')
  2574. if (!scrollElement) return;
  2575. // single column view; click button; scroll to tab content area 100%
  2576. 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])');
  2577. if (epPanel) {
  2578. // _console.log(7290, 2)
  2579.  
  2580. let pi = 50;
  2581. let cid = setInterval(() => {
  2582. if (--pi) epPanel.scrollIntoView(true); else clearInterval(cid)
  2583. }, 17)
  2584. //
  2585. }
  2586. })
  2587.  
  2588. }
  2589.  
  2590.  
  2591. } else if (fullscreen_mode_changed) {
  2592.  
  2593. // new_isFullScreen: false
  2594. // fullscreen_mode_changed: true
  2595.  
  2596.  
  2597. if (!new_isFullScreen && statusCollapsedTrue && isWideScreenWithTwoColumns() && !isTheater()) {
  2598.  
  2599. showTabOrChat();
  2600. } else if (!new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns() && isTheater()) {
  2601.  
  2602. if (isChatPopupedF()) {
  2603. } else {
  2604.  
  2605. ytBtnCancelTheater();
  2606.  
  2607. }
  2608. }
  2609.  
  2610. if (!new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns()) {
  2611. // check more than one shown
  2612.  
  2613. Promise.resolve().then(checkForMoreThanOne);
  2614. }
  2615.  
  2616. } else if ((new_layoutStatus & IF_01a) === IF_01b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED)) {
  2617.  
  2618. // new_isFullScreen: false
  2619. // fullscreen_mode_changed: false
  2620.  
  2621. // two column; not theater; tab collapse; chat expand; ep expand
  2622.  
  2623. if (epanel_expanded_changed) {
  2624. layoutStatusMutex.lockWith(unlock => {
  2625. ytBtnCollapseChat();
  2626. getDMPromise().then(unlock);
  2627. })
  2628. } else if (chat_collapsed_changed) {
  2629. layoutStatusMutex.lockWith(unlock => {
  2630. ytBtnCloseEngagementPanels();
  2631. getDMPromise().then(unlock);
  2632. })
  2633.  
  2634. }
  2635.  
  2636. } else if ((new_layoutStatus & IF_07a) === IF_07b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_DONATION_SHELF_EXPANDED)) {
  2637.  
  2638. // new_isFullScreen: false
  2639. // fullscreen_mode_changed: false
  2640.  
  2641. // two column; not theater; tab collapse; chat expand; ds expand
  2642.  
  2643. if (ds_expanded_changed) {
  2644. layoutStatusMutex.lockWith(unlock => {
  2645. ytBtnCollapseChat();
  2646. getDMPromise().then(unlock);
  2647. })
  2648. } else if (chat_collapsed_changed) {
  2649. layoutStatusMutex.lockWith(unlock => {
  2650. closeDonationShelf();
  2651. getDMHelper().then(unlock);
  2652. })
  2653.  
  2654. }
  2655.  
  2656. } else if (!tab_change && column_mode_changed && (new_layoutStatus & IF_02a) === IF_02b && moreThanOneShown) {
  2657.  
  2658. // new_isFullScreen: false
  2659. // fullscreen_mode_changed: false
  2660.  
  2661. // two column; not theater;
  2662. // moreThanOneShown
  2663.  
  2664. showTabOrChat();
  2665.  
  2666. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_03a) === IF_03b && statusCollapsedFalse && !column_mode_changed) {
  2667.  
  2668. // new_isFullScreen: false
  2669. // fullscreen_mode_changed: false
  2670.  
  2671. // two column; not theater; tab expand; chat expand;
  2672.  
  2673. switchTabActivity(null);
  2674.  
  2675. } else if (isChatOrTabExpandTriggering && (new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse && (changes & BF_TWOCOL_N_THEATER) === 0) {
  2676.  
  2677. // new_isFullScreen: false
  2678. // fullscreen_mode_changed: false
  2679.  
  2680. ytBtnCancelTheater();
  2681.  
  2682. } else if ((new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse) {
  2683.  
  2684. // new_isFullScreen: false
  2685. // fullscreen_mode_changed: false
  2686.  
  2687. if (isChatPopupedF()) {
  2688.  
  2689. } else {
  2690.  
  2691. hideTabAndChat();
  2692. }
  2693.  
  2694. } else if (isChatOrTabCollaspeTriggering && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue && !column_mode_changed) {
  2695.  
  2696. // new_isFullScreen: false
  2697. // fullscreen_mode_changed: false
  2698.  
  2699. if (tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED) {
  2700.  
  2701. lstTab.lastPanel = null;
  2702.  
  2703. if (new_isFullScreen) {
  2704.  
  2705. } else {
  2706. showTabOrChat();
  2707. }
  2708. } else if (tab_change == LAYOUT_DONATION_SHELF_EXPANDED) {
  2709.  
  2710. lstTab.lastPanel = null;
  2711.  
  2712. if (new_isFullScreen) {
  2713.  
  2714. } else {
  2715. showTabOrChat();
  2716. }
  2717. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED) {
  2718.  
  2719. lstTab.lastPanel = null;
  2720.  
  2721. if (new_isFullScreen) {
  2722.  
  2723. } else {
  2724. showTabOrChat();
  2725. }
  2726. } else {
  2727.  
  2728.  
  2729. if (new_isFullScreen) {
  2730.  
  2731. } else {
  2732.  
  2733. ytBtnSetTheater();
  2734.  
  2735. }
  2736.  
  2737. }
  2738.  
  2739. } else if (!tab_change && !!(changes & BF_TWOCOL_N_THEATER) && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue) {
  2740.  
  2741. // new_isFullScreen: false
  2742. // fullscreen_mode_changed: false
  2743.  
  2744. showTabOrChat();
  2745.  
  2746. } else if ((new_layoutStatus & IF_05a) === IF_05b) {
  2747. // bug fix for restoring from mini player
  2748.  
  2749. layoutStatusMutex.lockWith(unlock => {
  2750. setToActiveTab();
  2751. getDMPromise().then(unlock);
  2752. });
  2753.  
  2754. }
  2755.  
  2756. if (theater_mode_changed) {
  2757. let tdt = Date.now();
  2758. theater_mode_changed_dt = tdt
  2759. setTimeout(() => {
  2760. if (theater_mode_changed_dt !== tdt) return;
  2761. updateFloatingSlider();
  2762. }, 130)
  2763. }
  2764.  
  2765. let secondaryElement = null;
  2766. if (secondaryElement = document.querySelector('.tabview-hover-slider-enable')) {
  2767. scriptletDeferred.debounce(() => {
  2768. secondaryElement.isConnected && secondaryElement.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  2769. });
  2770. }
  2771.  
  2772.  
  2773. if (fullscreen_mode_changed) {
  2774. detailsTriggerReset = true;
  2775. getDMPromise().then(setHiddenStateForDesc);
  2776. }
  2777.  
  2778. // resize => is-two-columns_
  2779. if (column_mode_changed) {
  2780.  
  2781. Promise.resolve(0).then(() => {
  2782.  
  2783. checkForMoreThanOne();
  2784.  
  2785. pageCheck();
  2786. if (global_columns_end_ito !== null) {
  2787. //to trigger observation at the time layout being changed
  2788. if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  2789. let endpos = document.querySelector('tabview-view-columns-endpos')
  2790. if (endpos !== null) {
  2791. global_columns_end_ito.observe(endpos)
  2792. }
  2793. } else {
  2794. global_columns_end_ito.disconnect();
  2795. }
  2796. }
  2797. getDMPromise().then(() => {
  2798. singleColumnScrolling(true); //initalize sticky
  2799. });
  2800. })
  2801. }
  2802.  
  2803. if (enableLivePopupCheck === true) {
  2804.  
  2805. const new_isTwoColumnsTheater = fT(new_layoutStatus, LAYOUT_TWO_COLUMNS | LAYOUT_THEATER, 0)
  2806.  
  2807. let currentIsTheaterPopupChat = new_isTwoColumnsTheater && new_isExpandedChat && isChatPopupedF()
  2808. if (!currentIsTheaterPopupChat) {
  2809. enableLivePopupCheck = false;
  2810. sendToPageScript(document, "tyt-close-popup");
  2811. }
  2812.  
  2813. }
  2814.  
  2815.  
  2816. }
  2817.  
  2818. function fixLayoutStatus(x) {
  2819. const new_isExpandedChat = !(x & LAYOUT_CHATROOM_COLLAPSED) && (x & LAYOUT_CHATROOM)
  2820. return new_isExpandedChat ? (x | LAYOUT_CHATROOM_EXPANDED) : (x & ~LAYOUT_CHATROOM_EXPANDED);
  2821. }
  2822.  
  2823. const wls = new Proxy({
  2824. /** @type {number | null} */
  2825. layoutStatus: undefined
  2826. }, {
  2827. get: function (target, prop) {
  2828. return target[prop];
  2829. },
  2830. set: function (target, prop, value) {
  2831. if (prop == 'layoutStatus') {
  2832.  
  2833. if (value === 0) {
  2834. target[prop] = value;
  2835. return true;
  2836. } else if (target[prop] === value) {
  2837. return true;
  2838. } else {
  2839. if (!target.layoutStatus_pending) {
  2840. target.layoutStatus_pending = true;
  2841. const old_layoutStatus = target[prop];
  2842. target[prop] = value;
  2843. layoutStatusMutex.lockWith(unlock => {
  2844. target.layoutStatus_pending = false;
  2845. let new_layoutStatus = target[prop];
  2846. if (old_layoutStatus !== new_layoutStatus) {
  2847. layoutStatusChanged(old_layoutStatus, new_layoutStatus);
  2848. getDMPromise().then(unlock);
  2849. } else {
  2850. unlock();
  2851. }
  2852. })
  2853. return true;
  2854. }
  2855. }
  2856. }
  2857. target[prop] = value;
  2858. return true;
  2859. },
  2860. has: function (target, prop) {
  2861. return (prop in target);
  2862. }
  2863. });
  2864.  
  2865. const svgElm = (w, h, vw, vh, p, m) => `<svg${m ? ` class=${m}` : ''} width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`
  2866.  
  2867. function isVideoPlaying(video) {
  2868. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  2869. }
  2870.  
  2871. function wAttr(elm, attr, kv) {
  2872. if (elm) {
  2873. if (kv === true) {
  2874. elm.setAttribute(attr, '');
  2875. } else if (kv === false) {
  2876. elm.removeAttribute(attr);
  2877. } else if (kv === null) {
  2878. //;
  2879. } else if (typeof kv == 'string') {
  2880. elm.setAttribute(attr, kv);
  2881. }
  2882. }
  2883. }
  2884.  
  2885. function setTabBtnVisible(tabBtn, toVisible) {
  2886. let doClassListChange = false;
  2887. if (tabBtn.getAttribute('tyt-tab-content') === '#tab-comments') {
  2888. isCommentsTabBtnHidden = !toVisible;
  2889. if ((hiddenTabsByUserCSS & 2) !== 2) {
  2890. doClassListChange = true;
  2891. }
  2892. } else {
  2893. doClassListChange = true;
  2894. }
  2895. if (doClassListChange) {
  2896. if (toVisible) {
  2897. tabBtn.classList.remove("tab-btn-hidden");
  2898. } else {
  2899. tabBtn.classList.add("tab-btn-hidden");
  2900. }
  2901. }
  2902. }
  2903.  
  2904. function hideTabBtn(tabBtn) {
  2905. //console.log('hideTabBtn', tabBtn)
  2906. let isActiveBefore = tabBtn.classList.contains('active');
  2907. setTabBtnVisible(tabBtn, false);
  2908. if (isActiveBefore) {
  2909. setToActiveTab();
  2910. }
  2911. }
  2912.  
  2913. // function hasAttribute(obj, key) {
  2914. // return obj && obj.hasAttribute(key);
  2915. // }
  2916.  
  2917. function isTheater() {
  2918. const cssElm = es.ytdFlexy;
  2919. return (cssElm && cssElm.hasAttribute('theater'))
  2920. }
  2921.  
  2922. function isFullScreen() {
  2923. const cssElm = es.ytdFlexy;
  2924. return (cssElm && cssElm.hasAttribute('fullscreen'))
  2925. }
  2926.  
  2927. function isTabExpanded() {
  2928. const cssElm = es.ytdFlexy;
  2929. return cssElm && (cssElm.getAttribute('tyt-tab') || '').charAt(0) === '#'
  2930. }
  2931.  
  2932. function isChatExpand() {
  2933. const cssElm = es.ytdFlexy;
  2934. return cssElm && (cssElm.getAttribute('tyt-chat') || '').charAt(0) === '+'
  2935. }
  2936.  
  2937. function isWideScreenWithTwoColumns() {
  2938. const cssElm = es.ytdFlexy;
  2939. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  2940. }
  2941.  
  2942.  
  2943. function isAnyActiveTab() {
  2944. return document.querySelector('#right-tabs .tab-btn.active') !== null
  2945. }
  2946. // function isAnyActiveTab2() {
  2947. // return document.querySelectorAll('#right-tabs .tab-btn.active').length > 0
  2948. // }
  2949.  
  2950. function isEngagementPanelExpanded() { //note: not checking the visual elements
  2951. const cssElm = es.ytdFlexy;
  2952. return (cssElm && +cssElm.getAttribute('tyt-ep-visible') > 0)
  2953. }
  2954.  
  2955. function isDonationShelfExpanded() {
  2956. const cssElm = es.ytdFlexy;
  2957. return (cssElm && cssElm.hasAttribute('tyt-donation-shelf'))
  2958. }
  2959.  
  2960. const engagementIdMap = new Map();
  2961. let engagementIdNext = 1; // max 1 << 62
  2962.  
  2963. function engagement_panels_() {
  2964.  
  2965. let res = [];
  2966. // let shownRes = [];
  2967.  
  2968. let v = 0;
  2969. // let k = 1;
  2970. // let count = 0;
  2971.  
  2972. for (const ePanel of document.querySelectorAll(
  2973. `ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
  2974. )) {
  2975. let targetId = ePanel.getAttribute('target-id')
  2976. if (typeof targetId !== 'string') continue;
  2977. let eId = engagementIdMap.get(targetId)
  2978. if (!eId) {
  2979. engagementIdMap.set(targetId, eId = engagementIdNext)
  2980. if (engagementIdNext === (1 << 62)) {
  2981. engagementIdNext = 1;
  2982. console.warn('engagementId reached 1 << 62')
  2983. } else {
  2984. engagementIdNext = engagementIdNext << 1;
  2985. }
  2986. }
  2987. // console.log(55,eId, targetId)
  2988.  
  2989. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  2990.  
  2991. let k = eId
  2992. switch (visibility) {
  2993. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED':
  2994. v |= k;
  2995. // count++;
  2996. // shownRes.push(ePanel)
  2997. res.push({ ePanel, k, visible: true });
  2998. break;
  2999. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN':
  3000. res.push({ ePanel, k, visible: false });
  3001. break;
  3002. default:
  3003. res.push({ ePanel, k, visible: false });
  3004. }
  3005.  
  3006. //k = k << 1;
  3007.  
  3008. }
  3009. return { list: res, value: v };
  3010. // return { list: res, value: v, count: count, shownRes };
  3011. }
  3012.  
  3013.  
  3014. function ytBtnOpenEngagementPanel(/** @type {number | string} */ panel_id) {
  3015.  
  3016. // console.log(panel_id)
  3017. if (typeof panel_id == 'string') {
  3018. panel_id = panel_id.replace('#engagement-panel-', '');
  3019. panel_id = parseInt(panel_id);
  3020. }
  3021. if (panel_id >= 0) { } else return false;
  3022.  
  3023. let panels = engagement_panels_();
  3024. // console.log(panels)
  3025.  
  3026. let actions = []
  3027. for (const { ePanel, k, visible } of panels.list) {
  3028. if ((panel_id & k) === k) {
  3029. if (!visible) {
  3030. actions.push({
  3031. panelId: ePanel.getAttribute('target-id'),
  3032. toShow: true
  3033. })
  3034. }
  3035. } else {
  3036. if (visible) {
  3037. actions.push({
  3038. panelId: ePanel.getAttribute('target-id'),
  3039. toHide: true
  3040. })
  3041. }
  3042. }
  3043. }
  3044.  
  3045. if (actions.length > 0) {
  3046. // console.log(4545,actions)
  3047. const _actions = actions;
  3048. scriptletDeferred.debounce(() => {
  3049. document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
  3050. detail: _actions
  3051. }));
  3052. });
  3053. }
  3054. actions = null;
  3055.  
  3056. }
  3057.  
  3058. function ytBtnCloseEngagementPanel(/** @type {HTMLElement} */ s) {
  3059. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  3060.  
  3061. let panelId = s.getAttribute('target-id')
  3062. scriptletDeferred.debounce(() => {
  3063. document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
  3064. detail: {
  3065. panelId,
  3066. toHide: true
  3067. }
  3068. }))
  3069. })
  3070.  
  3071. }
  3072.  
  3073. function ytBtnCloseEngagementPanels() {
  3074. if (isEngagementPanelExpanded()) {
  3075. for (const s of document.querySelectorAll(
  3076. `ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
  3077. )) {
  3078. if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  3079. }
  3080. }
  3081. }
  3082.  
  3083. function openDonationShelf() {
  3084. if (!isDonationShelfExpanded()) {
  3085. let btn = document.querySelector('#tyt-donation-shelf-toggle-btn')
  3086. if (btn) {
  3087. btn.click();
  3088. return true;
  3089. }
  3090. }
  3091. return false;
  3092. }
  3093. function closeDonationShelf() {
  3094. if (isDonationShelfExpanded()) {
  3095. let btn = document.querySelector('#tyt-donation-shelf-toggle-btn')
  3096. if (btn) {
  3097. btn.click();
  3098. return true;
  3099. }
  3100. }
  3101. return false;
  3102. }
  3103.  
  3104. function ytBtnSetTheater() {
  3105. if (!isTheater()) {
  3106. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  3107. if (sizeBtn) sizeBtn.click();
  3108. }
  3109. }
  3110.  
  3111. function ytBtnCancelTheater() {
  3112. if (isTheater()) {
  3113. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  3114. if (sizeBtn) sizeBtn.click();
  3115. }
  3116. }
  3117.  
  3118. function ytBtnExpandChat() {
  3119. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button')
  3120. if (button) {
  3121. button =
  3122. _querySelector.call(button, 'div.yt-spec-touch-feedback-shape') ||
  3123. _querySelector.call(button, 'ytd-toggle-button-renderer');
  3124. if (button) button.click();
  3125. }
  3126. }
  3127.  
  3128. function ytBtnCollapseChat() {
  3129. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button')
  3130. if (button) {
  3131. button =
  3132. _querySelector.call(button, 'div.yt-spec-touch-feedback-shape') ||
  3133. _querySelector.call(button, 'ytd-toggle-button-renderer');
  3134. if (button) button.click();
  3135. }
  3136. }
  3137.  
  3138.  
  3139. async function makeVideosAutoLoad2() {
  3140. let sVideosList = document.querySelector('ytd-watch-flexy #tab-videos [placeholder-videos]');
  3141.  
  3142. if (!sVideosList) return null;
  3143.  
  3144. //let ab = sVideosList.getAttribute('tabview-videos-autoload')
  3145. await Promise.resolve(0);
  3146.  
  3147. let endPosDOM = document.querySelector('tabview-view-videos-endpos')
  3148. if (endPosDOM) endPosDOM.remove(); // just in case
  3149. endPosDOM = document.createElement('tabview-view-videos-endpos')
  3150. insertAfterTo(endPosDOM, sVideosList);
  3151.  
  3152. await Promise.resolve(0);
  3153.  
  3154.  
  3155. //sVideosList.setAttribute('tabview-videos-autoload', '1')
  3156.  
  3157. // _console.log(9333)
  3158. if (!sVideosITO) {
  3159.  
  3160. sVideosITO = new IntersectionObserver((entries) => {
  3161.  
  3162. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) return;
  3163.  
  3164. // _console.log(9334, entries)
  3165. if (entries.length !== 1) return;
  3166. if (entries[0].isIntersecting !== true) return;
  3167. let elm = ((entries[0] || 0).target || 0);
  3168. if (!elm) return;
  3169. elm = null;
  3170. entries = null;
  3171.  
  3172. new Promise(resolve => {
  3173.  
  3174. // compatibile with Search While Watching Video
  3175. 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]');
  3176. if (isSearchGeneratedWithHiddenContinuation) return;
  3177.  
  3178. // native YouTube coding use different way to handle custom videos, unknown condition for the continutation loading.
  3179. 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"]')
  3180. if (isOtherChipSelected) return;
  3181.  
  3182. getDMPromise().then(resolve); // delay required to allow YouTube generate the continuation elements
  3183.  
  3184.  
  3185. }).then(() => {
  3186.  
  3187. let res = setVideosTwoColumns(2 | 4, true)
  3188.  
  3189. // _console.log(9335, res)
  3190.  
  3191. if (res.m2 && res.m3) {
  3192. const m4 = closestDOM.call(res.m2, 'ytd-continuation-item-renderer');
  3193.  
  3194. if (m4) {
  3195.  
  3196.  
  3197. const m5 = _querySelector.call(m4, 'ytd-button-renderer.style-scope.ytd-continuation-item-renderer, yt-button-renderer.style-scope.ytd-continuation-item-renderer');
  3198.  
  3199. // YouTube coding bug - correct is 'ytd-button-renderer'. If the page is redirected under single column mode, the tag become 'yt-button-renderer'
  3200. // under 'yt-button-renderer', the
  3201.  
  3202. const m6 = querySelectorFromAnchorX(m5, 'button.yt-spec-button-shape-next--call-to-action'); // main
  3203.  
  3204. // _console.log(9337, m4, m5, m6)
  3205.  
  3206. if (m6) {
  3207. m6.click() // generic solution
  3208. } else if (m5) {
  3209. m5.click(); // not sure
  3210. } else {
  3211. m4.dispatchEvent(new Event('yt-service-request-sent-button-renderer')); // only for correct YouTube coding
  3212. }
  3213. }
  3214. }
  3215. res = null;
  3216.  
  3217. });
  3218.  
  3219. }, {
  3220. rootMargin: `0px`, // refer to css margin-top:-30vh
  3221. threshold: [0]
  3222. })
  3223. sVideosITO.observe(endPosDOM);
  3224. } else {
  3225. sVideosITO.disconnect();
  3226. sVideosITO.observe(endPosDOM);
  3227. }
  3228.  
  3229.  
  3230. }
  3231.  
  3232.  
  3233. function fixTabs() {
  3234.  
  3235. scriptletDeferred.debounce(() => {
  3236.  
  3237.  
  3238. // if(document.documentElement.hasAttribute('p355')) return;
  3239.  
  3240. if (!scriptEnable) return;
  3241.  
  3242. let queryElement = document.querySelector('*:not(#tab-videos) > #related.ytd-watch-flexy > ytd-watch-next-secondary-results-renderer');
  3243.  
  3244. let isRelocated = !!queryElement;
  3245.  
  3246. if (isRelocated) {
  3247. // if(1885) return;
  3248.  
  3249. // _console.log(3202, 2)
  3250.  
  3251. let relatedElm = closestDOM.call(queryElement, '#related.ytd-watch-flexy'); // NOT NULL
  3252.  
  3253. let right_tabs = document.querySelector('#right-tabs'); // can be NULL
  3254.  
  3255. let tab_videos = querySelectorFromAnchorX(right_tabs, "#tab-videos"); // can be NULL
  3256.  
  3257. if (tab_videos !== null) {
  3258.  
  3259. // _console.log(3202, 4)
  3260.  
  3261. 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')
  3262. if (target_container) elementAppend.call(target_container, right_tabs) // last-child
  3263.  
  3264. elementAppend.call(tab_videos, relatedElm);
  3265. // no any other element set these attr. only init / relocation
  3266. relatedElm.setAttribute('placeholder-for-youtube-play-next-queue', '')
  3267. relatedElm.setAttribute('placeholder-videos', '')
  3268.  
  3269. makeVideosAutoLoad2();
  3270.  
  3271. }
  3272.  
  3273. }
  3274.  
  3275. /** @type {HTMLElement | null} */
  3276. check1885();
  3277. if (!chatController.allowChatControl) return;
  3278.  
  3279. let chatroom = null;
  3280. if (chatroom = document.querySelector('ytd-live-chat-frame#chat')) {
  3281.  
  3282. const container = chatroom.parentNode.id === 'chat-container' ? chatroom.parentNode : chatroom;
  3283.  
  3284. let pHolderElm = document.querySelector('tabview-view-pholder[data-positioner="before|#chat"]');
  3285.  
  3286. if (!pHolderElm || pHolderElm.nextElementSibling !== container) {
  3287.  
  3288.  
  3289. if (pHolderElm) pHolderElm.remove();
  3290.  
  3291.  
  3292.  
  3293. // if (1885 && document.body.hasAttribute('data-ytlstm-theater-mode')) {
  3294.  
  3295. // } else
  3296. if (document.querySelector('.YouTubeLiveFilledUpView')) {
  3297. // no relocation
  3298. } else {
  3299.  
  3300. let rightTabs = document.querySelector('#right-tabs');
  3301. // console.log(28784, rightTabs.previousSibling)
  3302. if (rightTabs && rightTabs.previousSibling !== container) {
  3303. const parentNode = rightTabs.parentNode;
  3304. let useDefault = true;
  3305. if (parentNode === container.parentNode && parentNode instanceof HTMLElement
  3306. && typeof docFragmentCreate === 'function'
  3307. && typeof docFragmentAppend === 'function') {
  3308. const previousNodes = [];
  3309. const nextNodes = [];
  3310. let dv = 0;
  3311. for (let node = parentNode.firstChild; node instanceof Node; node = node.nextSibling) {
  3312. if (node === rightTabs) {
  3313. dv |= 1;
  3314. } else if (node === container) {
  3315. dv |= 2;
  3316. } else {
  3317. if (node instanceof HTMLIFrameElement || (node instanceof Element && node.querySelector('iframe'))) {
  3318. dv |= 4;
  3319. break;
  3320. }
  3321. if (dv & 1) {
  3322. nextNodes.push(node);
  3323. } else {
  3324. previousNodes.push(node);
  3325. }
  3326. }
  3327. }
  3328. if (dv === 3 && (previousNodes.length + nextNodes.length) < 8000) {
  3329. if (previousNodes.length > 0) {
  3330. const nw1 = docFragmentCreate.call(document);
  3331. docFragmentAppend.call(nw1, ...previousNodes);
  3332. parentNode.insertBefore(nw1, parentNode.firstChild);
  3333. }
  3334. const nw2 = docFragmentCreate.call(document);
  3335. docFragmentAppend.call(nw2, rightTabs, ...nextNodes);
  3336. parentNode.appendChild(nw2);
  3337. // parentNode.replaceChildren(...previousNodes, container, rightTabs, ...nextNodes);
  3338. useDefault = false;
  3339. }
  3340. }
  3341. if (useDefault) {
  3342.  
  3343. // console.log(28784, [...parentNode.childNodes])
  3344. insertBeforeTo(container, rightTabs);
  3345. // const _chatroom = chatroom;
  3346. // _chatroom && (async ()=>{
  3347. // await scriptletDeferred.d();
  3348. // if (typeof webkitRequestAnimationFrame === 'function' && typeof mozRequestAnimationFrame === 'undefined') {
  3349. // await new Promise(r => setTimeout(r, 650)); // 650ms to avoid Brave Bug
  3350. // }
  3351.  
  3352. // // sendToPageScript(_chatroom, "tabview-chat-call-urlchange");
  3353.  
  3354. // })();
  3355. }
  3356. }
  3357.  
  3358. }
  3359.  
  3360. if (!pHolderElm) {
  3361. pHolderElm = document.createElement('tabview-view-pholder');
  3362. pHolderElm.setAttribute('data-positioner', 'before|#chat');
  3363. }
  3364.  
  3365. insertBeforeTo(pHolderElm, container)
  3366. }
  3367.  
  3368. }
  3369.  
  3370.  
  3371. })
  3372.  
  3373. }
  3374.  
  3375.  
  3376. async function isDocumentInFullScreenMode() {
  3377. return document.fullscreenElement !== null;
  3378. }
  3379. // let zatt = Date.now();
  3380.  
  3381. const dbId = `ep5wbmokDB-${instanceId}`
  3382. async function tabviewEnergizedFn() {
  3383.  
  3384. let db;
  3385. const indexedDB = window.indexedDB;
  3386. try {
  3387. let dbReq = indexedDB.open(dbId, 3);
  3388. db = await new Promise((resolve, reject) => {
  3389. dbReq.onupgradeneeded = function (event) {
  3390. /** @type {IDBDatabase} */
  3391. const db = event.target.result;
  3392. let objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
  3393. objectStore.createIndex("name", "name", { unique: false });
  3394. objectStore.createIndex("email", "email", { unique: true });
  3395. objectStore = null;
  3396. db.deleteObjectStore("customers");
  3397. resolve(db);
  3398. }
  3399. dbReq.onsuccess = function (event) {
  3400. const db = event.target.result;
  3401. resolve(db);
  3402. }
  3403. dbReq.onerror = function () {
  3404. reject();
  3405. }
  3406. });
  3407. } finally {
  3408. if (db) db.close();
  3409. }
  3410. try {
  3411. let request = indexedDB.deleteDatabase(dbId);
  3412. await new Promise((resolve, reject) => {
  3413. request.onsuccess = function (event) {
  3414. resolve(1);
  3415. };
  3416. request.onerror = function (event) {
  3417. resolve(-1);
  3418. };
  3419. request.onblocked = function (event) {
  3420. resolve(-2);
  3421. };
  3422. });
  3423. } catch (e) {
  3424. }
  3425.  
  3426. postMessage({ tabviewEnergized: true }, 'https://www.youtube.com'); // post message to make alive
  3427.  
  3428. }
  3429.  
  3430. async function energizedByMediaTimeUpdate() {
  3431.  
  3432. const isFullscreen = await isDocumentInFullScreenMode();
  3433. if (isFullscreen) return;
  3434.  
  3435. // force browser to load the videostream during playing (primarily for music videos)
  3436. // both background and foreground
  3437.  
  3438. _updateTimeAccum++;
  3439.  
  3440. if (_updateTimeAccum >= 88000000 && (_updateTimeAccum % 88000000) === 0) _updateTimeAccum = 0;
  3441.  
  3442. if ((_updateTimeAccum + _viTimeNum) % 11 === 0) {
  3443. // console.log(document.querySelector('video').currentTime) // 2.55, 2.64, 3.12, ...
  3444. // about 2.66s
  3445.  
  3446. if (_viTimeNum > 208) {
  3447. _viTimeNum = 200;
  3448. _updateTimeAccum = (_updateTimeAccum % 8) + 1; // reset to 1 ~ 8
  3449. }
  3450.  
  3451. document.head.dataset.viTime = `${_viTimeNum + 1}`;
  3452. await Promise.resolve(0)
  3453. _viTimeNum = +document.head.dataset.viTime || 0;
  3454. }
  3455.  
  3456.  
  3457. }
  3458.  
  3459. function autoCompletePosCreate() {
  3460.  
  3461. let positioner = document.createElement("tabview-view-autocomplete-pos");
  3462. let oldPositioner = document.querySelector("tabview-view-autocomplete-pos");
  3463. if (oldPositioner) oldPositioner.remove();
  3464. return positioner
  3465. }
  3466.  
  3467. function handlerAutoCompleteExist() {
  3468. // Youtube - Search While Watching Video
  3469.  
  3470. if (document.querySelector('autocomplete-holder')) return;
  3471.  
  3472. /** @type {HTMLElement} */
  3473. let searchBox, autoComplete;
  3474. searchBox = this;
  3475. this.removeEventListener('tyt-autocomplete-sc-exist', handlerAutoCompleteExist, false)
  3476. let domId = this.getAttribute('data-autocomplete-results-id')
  3477.  
  3478. autoComplete = document.querySelector(`[data-autocomplete-input-id="${domId}"]`)
  3479.  
  3480. if (!domId || !searchBox) return;
  3481.  
  3482. let positioner = nodePrevSibling(searchBox);
  3483. if (!positioner || positioner.nodeName.toLowerCase() !== "tabview-view-autocomplete-pos") {
  3484. positioner = autoCompletePosCreate();
  3485. insertBeforeTo(positioner, searchBox);
  3486. }
  3487. prependTo(autoComplete, positioner);
  3488.  
  3489. setupSearchBox(searchBox, positioner);
  3490.  
  3491. }
  3492.  
  3493. async function setupSearchBox(searchBox, positioner) {
  3494.  
  3495. let h = searchBox.offsetHeight + 'px'
  3496.  
  3497. positioner.style.setProperty('--tyt-swwv-searchbox-h', h)
  3498.  
  3499. mtf_autocomplete_search()
  3500.  
  3501. }
  3502.  
  3503. function mtf_autocomplete_search() {
  3504. // Youtube - Search While Watching Video
  3505.  
  3506. /** @type {HTMLElement | null} */
  3507. const ytdFlexyElm = es.ytdFlexy;
  3508. if (!scriptEnable || !ytdFlexyElm) return;
  3509.  
  3510. 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])')
  3511.  
  3512. if (autocomplete) {
  3513.  
  3514. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  3515.  
  3516.  
  3517. if (searchBox) {
  3518.  
  3519. const rAutoComplete = mWeakRef(autocomplete);
  3520.  
  3521. function setVisible(autocomplete, b) {
  3522. autocomplete.style.display = (b ? 'block' : 'none');
  3523. }
  3524.  
  3525. function isContentNotEmpty(searchbox, autocomplete) {
  3526. return (searchbox.value || '').length > 0 && (autocomplete.textContent || '').length > 0;
  3527. }
  3528.  
  3529. nodeParent(autocomplete).setAttribute('position-fixed-by-tabview-youtube', '');
  3530. autocomplete.setAttribute('position-fixed-by-tabview-youtube', '');
  3531. autocomplete.setAttribute('userscript-scrollbar-render', '')
  3532.  
  3533. //let cancelClickToggle = false;
  3534.  
  3535. if (!searchBox.hasAttribute('is-set-click-to-toggle')) {
  3536. searchBox.setAttribute('is-set-click-to-toggle', '')
  3537.  
  3538. searchBox.addEventListener('click', function () {
  3539.  
  3540. Promise.resolve(0).then(() => {
  3541.  
  3542. const autocomplete = kRef(rAutoComplete);
  3543.  
  3544. if (!autocomplete) return;
  3545.  
  3546. const isNotEmpty = isContentNotEmpty(this, autocomplete);
  3547.  
  3548. if (isNotEmpty) {
  3549.  
  3550. let elmVisible = isDOMVisible(autocomplete);
  3551.  
  3552. if (elmVisible) {
  3553. setVisible(autocomplete, false)
  3554. }
  3555. else {
  3556. setVisible(autocomplete, true)
  3557. }
  3558.  
  3559. }
  3560.  
  3561. })
  3562.  
  3563. }, bubblePassive)
  3564.  
  3565. let cacheScrollIntoView = null;
  3566. let rafXC = 0;
  3567. searchBox.addEventListener('keydown', function (evt) {
  3568. //cancelClickToggle = true;
  3569. switch (evt.code) {
  3570. case 'ArrowUp':
  3571. case 'ArrowDown':
  3572.  
  3573. let t = Date.now();
  3574. if (rafXC === 0) {
  3575. getRAFPromise().then(() => {
  3576. rafXC = 0;
  3577. let d = Date.now();
  3578. if (d - t > 300) return;
  3579.  
  3580. const autocomplete = kRef(rAutoComplete);
  3581. if (!autocomplete) return;
  3582.  
  3583. let selected = _querySelector.call(autocomplete, '.autocomplete-suggestion.selected');
  3584. let bool = selected && selected !== cacheScrollIntoView;
  3585. cacheScrollIntoView = selected;
  3586. if (bool) {
  3587.  
  3588. try {
  3589. selected.scrollIntoView({ block: "nearest", inline: "nearest" });
  3590. } catch (e) { }
  3591.  
  3592. }
  3593.  
  3594. });
  3595. rafXC = 1;
  3596. }
  3597. default:
  3598. //
  3599. }
  3600.  
  3601.  
  3602. }, bubblePassive)
  3603.  
  3604. searchBox.addEventListener('tyt-autocomplete-suggestions-change', function (evt) {
  3605.  
  3606. //cancelClickToggle = true;
  3607. if (evt.target !== document.activeElement) return;
  3608. getDMPromise().then(() => {
  3609. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${this.getAttribute('data-autocomplete-results-id')}"]`);
  3610. if (!autocomplete) return;
  3611. const isNotEmpty = isContentNotEmpty(this, autocomplete);
  3612. if (isNotEmpty) {
  3613. // dont detect visibility; just set to visible
  3614. setVisible(autocomplete, true);
  3615. }
  3616. });
  3617.  
  3618. }, bubblePassive)
  3619.  
  3620. }
  3621.  
  3622. }
  3623.  
  3624. }
  3625.  
  3626. }
  3627.  
  3628. const insertBeforeTo = HTMLElement.prototype.before ? (elm, target) => {
  3629. if (!target || !elm) return null;
  3630. // using before
  3631. HTMLElement.prototype.before.call(target, elm);
  3632. return true;
  3633. } : (elm, target) => {
  3634. if (!target || !elm) return null;
  3635. // using insertBefore
  3636. try {
  3637. HTMLElement.prototype.insertBefore.call(nodeParent(target), elm, target);
  3638. return true;
  3639. } catch (e) {
  3640. console.log('element insert failed in old browser CE')
  3641. }
  3642. return false;
  3643. }
  3644.  
  3645. const insertAfterTo = HTMLElement.prototype.after ? (elm, target) => {
  3646. if (!target || !elm) return null;
  3647. // using after
  3648. HTMLElement.prototype.after.call(target, elm);
  3649. return true;
  3650. } : (elm, target) => {
  3651. if (!target || !elm) return null;
  3652. // using insertBefore
  3653. try {
  3654. HTMLElement.prototype.insertBefore.call(nodeParent(target), elm, nodeNextSibling(target));
  3655. return true;
  3656. } catch (e) {
  3657. console.log('element insert failed in old browser CE')
  3658. }
  3659. return false;
  3660. }
  3661.  
  3662. const prependTo = HTMLElement.prototype.prepend ? (elm, target) => {
  3663. if (!target || !elm) return null;
  3664. // using prepend
  3665. HTMLElement.prototype.prepend.call(target, elm);
  3666. return true;
  3667. } : (elm, target) => {
  3668. if (!target || !elm) return null;
  3669. // using insertBefore
  3670. try {
  3671. HTMLElement.prototype.insertBefore.call(target, elm, nodeFirstChild(target));
  3672. return true;
  3673. } catch (e) {
  3674. console.log('element insert failed in old browser CE')
  3675. }
  3676. return false;
  3677. }
  3678.  
  3679. const appends = HTMLElement.prototype.append ? (target, ...args) => {
  3680. HTMLElement.prototype.append.call(target, ...args);
  3681. return true;
  3682. } : (target, ...args) => {
  3683. for (const s of args) {
  3684. target.appendChild(s)
  3685. }
  3686. return true;
  3687. }
  3688.  
  3689.  
  3690.  
  3691.  
  3692. function getWrapper(wrapperId) {
  3693. let wrapper = document.getElementById(wrapperId);
  3694. if (!wrapper) {
  3695. wrapper = document.createElement('div');
  3696. wrapper.id = wrapperId;
  3697. }
  3698. return wrapper;
  3699. }
  3700.  
  3701. // continuous check for element relocation
  3702. // fired at begining & window resize, etc
  3703. // might moved to #primary
  3704. function mtf_append_playlist(/** @type {HTMLElement | null} */ playlist) {
  3705.  
  3706. if (playlist === null) {
  3707. playlist = document.querySelector('ytd-watch-flexy[playlist] *:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer.style-scope.ytd-watch-flexy#playlist:not(.ytd-miniplayer)');
  3708. // this playlist is highly possible to have '#items'
  3709. if (!playlist) return;
  3710. }
  3711.  
  3712. /** @type {HTMLElement | null} */
  3713. const ytdFlexyElm = es.ytdFlexy;
  3714. if (!scriptEnable || !ytdFlexyElm) return;
  3715.  
  3716. let items = _querySelector.call(playlist, "*:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer#playlist:not(.ytd-miniplayer) #items.ytd-playlist-panel-renderer:not(:empty)");
  3717.  
  3718. if (items !== null && playlist.nodeName.toUpperCase() === 'YTD-PLAYLIST-PANEL-RENDERER') {
  3719.  
  3720. let tab_list = document.querySelector("#tab-list");
  3721.  
  3722. if (!tab_list) return;
  3723.  
  3724. let w = getWrapper("tabview-playlist-wrapper");
  3725. let docFrag = new DocumentFragment();
  3726. // avoid immediate reflow for append playlist before append to tab_list
  3727. docFrag.appendChild(w);
  3728. elementAppend.call(w, playlist);
  3729. elementAppend.call(tab_list, docFrag);
  3730. docFrag = null;
  3731. w = null;
  3732.  
  3733. }
  3734. }
  3735.  
  3736.  
  3737. function getCountHText(elm) {
  3738. return `${pageFetchedDataVideoId || 0}...${elm.textContent}`
  3739. }
  3740.  
  3741. function mtf_fix_collapsible_playlist() {
  3742.  
  3743. // just in case the playlist is collapsed
  3744. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  3745. if (playlist && playlist.matches('[collapsed], [collapsible]')) {
  3746.  
  3747. const domElement = playlist;
  3748. playlist = null;
  3749. // if(!domElement.parentElement || domElement.nodeType!==1) return; // not working in pseudo custom element - parentNode = documentFragment
  3750. const tablist = closestDOM.call(domElement, 'ytd-watch-flexy #tab-list')
  3751.  
  3752. if (!tablist || tablist.nodeType !== 1) return; // checking whether it is still on the page
  3753.  
  3754. if (domElement.hasAttribute('collapsed')) wAttr(domElement, 'collapsed', false);
  3755. if (domElement.hasAttribute('collapsible')) wAttr(domElement, 'collapsible', false);
  3756.  
  3757. }
  3758. }
  3759.  
  3760. // content fix - info & playlist
  3761. // fired at begining, and keep for in case any change
  3762. async function mtf_fix_details() {
  3763.  
  3764. if (!scriptEnable) return 0; // in case
  3765.  
  3766. await scriptletDeferred.d();
  3767. await Promise.all([
  3768. Promise.resolve().then(() => {
  3769.  
  3770. 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])');
  3771.  
  3772. if (contentToggleBtn) {
  3773.  
  3774. const domElement = contentToggleBtn;
  3775. contentToggleBtn = null;
  3776. // if(!domElement.parentElement) return; // not working in pseudo custom element - parentNode = documentFragment
  3777. const expander = closestDOM.call(domElement, 'ytd-watch-flexy #tab-info ytd-expander')
  3778.  
  3779. if (!expander || expander.nodeType !== 1) return; // checking whether it is still on the page
  3780.  
  3781. if (expander.style.getPropertyValue('--ytd-expander-collapsed-height')) {
  3782. expander.style.setProperty('--ytd-expander-collapsed-height', '')
  3783. }
  3784. sendToPageScript(expander, "tabview-expander-config");
  3785.  
  3786. }
  3787.  
  3788. }),
  3789.  
  3790. Promise.resolve().then(() => {
  3791.  
  3792. let strcturedInfo = document.querySelector('ytd-watch-flexy #tab-info ytd-structured-description-content-renderer.style-scope.ytd-video-secondary-info-renderer')
  3793. if (strcturedInfo) {
  3794. const isHidden = closestDOM.call(strcturedInfo, '[hidden]');
  3795. if (isHidden) {
  3796. if (strcturedInfo.hasAttribute('hidden')) strcturedInfo.removeAttribute('hidden');
  3797. const descriptionElement = closestDOM.call(strcturedInfo, 'ytd-expander.style-scope.ytd-video-secondary-info-renderer #description.style-scope.ytd-video-secondary-info-renderer');
  3798. if (descriptionElement && descriptionElement.hasAttribute('hidden')) {
  3799. descriptionElement.removeAttribute('hidden');
  3800. }
  3801. }
  3802. isHidden && setTimeout(() => {
  3803. let e = closestDOM.call(strcturedInfo, 'ytd-watch-flexy #tab-info ytd-expander');
  3804. if (!e) return;
  3805. let s = _querySelectorAll.call(e, '#tab-info .more-button.style-scope.ytd-video-secondary-info-renderer[role="button"]');
  3806. if (s.length === 1) {
  3807. let sp = nodeParent(s[0]);
  3808. if (sp.nodeName.toUpperCase() === 'TP-YT-PAPER-BUTTON') {
  3809. sp.click();
  3810. }
  3811. }
  3812. }, 300);
  3813. }
  3814.  
  3815. }),
  3816.  
  3817. Promise.resolve().then(() => {
  3818.  
  3819.  
  3820. let subscribersCount = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #owner #owner-sub-count');
  3821.  
  3822. if (subscribersCount) {
  3823. if (!subscribersCount.hasAttribute('title')) {
  3824. // assume YouTube native coding would not implement [title]
  3825.  
  3826. let ytdWatchMetaDataElm = closestDOM.call(subscribersCount, 'body #primary.ytd-watch-flexy #below ytd-watch-metadata:not([tabview-uploader-hover])');
  3827. if (ytdWatchMetaDataElm) {
  3828. ytdWatchMetaDataElm.setAttribute('tabview-uploader-hover', '')
  3829. let _h = 0;
  3830. ytdWatchMetaDataElm.addEventListener('transitionend', function (evt) {
  3831. // no css selector rule required; no delay js function call required
  3832.  
  3833. let selection = evt.propertyName === 'background-position-y' ? 1 : evt.propertyName === 'background-position-x' ? 2 : 0;
  3834.  
  3835. if (selection && evt.target) {
  3836. let cssRoot = this; // no querySelector is required
  3837. if (cssRoot.classList.contains('tabview-uploader-hover')) {
  3838. if (evt.target.id !== 'owner') return;
  3839. cssRoot.classList.toggle('tabview-uploader-hover', false);
  3840. }
  3841. }
  3842.  
  3843. if (selection === 1) { // string comparision only
  3844.  
  3845. // If the cursor initially stayed at the owner info area,
  3846. // the mechanism will be broken
  3847. // so implement the true leave detection at their parent
  3848.  
  3849. let isHover = evt.elapsedTime < 0.03; // 50ms @normal; 10ms @hover;
  3850.  
  3851. let cssRoot = this; // no querySelector is required
  3852. if (!isHover) {
  3853. // 50ms is slowest than sub element leave effect
  3854. if (_h > 0) cssRoot.classList.toggle('tabview-uploader-hover', false) // even the order is incorrect, removal of class is safe.
  3855. _h = 0; // in case
  3856. } else if (isHover) {
  3857. // 10ms is faster than sub element hover effect
  3858. // no removal of class in case browser's transition implemention order is incorrect
  3859. _h = 0; // in case
  3860. }
  3861.  
  3862. } else if (selection === 2) { // string comparision only
  3863.  
  3864. //from one element to another element; hover effect of 2nd element transition end first.
  3865. // _h: 0 -> 1 -> 2 -> 1
  3866.  
  3867. let isHover = evt.elapsedTime < 0.03; // 40ms @normal; 20ms @hover;
  3868. if (isHover) _h++; else _h--;
  3869. let cssRoot = this; // no querySelector is required
  3870. if (_h <= 0) {
  3871. cssRoot.classList.toggle('tabview-uploader-hover', false)
  3872. _h = 0; // in case
  3873. } else if (_h === 1) {
  3874. cssRoot.classList.toggle('tabview-uploader-hover', true)
  3875. }
  3876.  
  3877. }
  3878.  
  3879. }, capturePassive) // capture the hover effect inside the cssRoot
  3880. }
  3881.  
  3882. }
  3883. subscribersCount.setAttribute('title', subscribersCount.textContent); // set at every page update
  3884.  
  3885. }
  3886.  
  3887. })
  3888.  
  3889.  
  3890. ]);
  3891.  
  3892.  
  3893. }
  3894.  
  3895.  
  3896. const innerCommentsFuncs = [
  3897. // comments
  3898. function () {
  3899.  
  3900. let elm = kRef(this.elm);
  3901. // _console.log(2907, 1, !!elm)
  3902. if (!elm) return;
  3903.  
  3904. let span = document.querySelector("span#tyt-cm-count")
  3905. // let r = '0';
  3906. // let txt = elm.textContent
  3907. // if (typeof txt == 'string') {
  3908. // let m = txt.match(/[\d\,\.\s]+/)
  3909. // if (m) {
  3910. // let d = +m[0].replace(/\D+/g, '');
  3911. // let ds = d.toLocaleString(document.documentElement.lang);
  3912. // let rtxt = txt.replace(ds, '')
  3913. // if (rtxt !== txt && !/\d/.test(rtxt)) {
  3914. // r = ds;
  3915. // }
  3916. // }
  3917. // }
  3918.  
  3919. if (span) {
  3920. let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]')
  3921. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'normal');
  3922. sendToPageScript(document, 'tyt-update-cm-count');
  3923. // span.textContent = r;
  3924. }
  3925.  
  3926.  
  3927. setCommentSection(1);
  3928. m_last_count = getCountHText(elm);
  3929. // _console.log(2907, 2, m_last_count)
  3930. return true;
  3931. },
  3932. // message
  3933. function () {
  3934.  
  3935. let elm = kRef(this.elm);
  3936. // _console.log(2907, 2, !!elm)
  3937. if (!elm) return;
  3938.  
  3939. let span = document.querySelector("span#tyt-cm-count")
  3940. if (span) {
  3941. let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]')
  3942. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'message')
  3943. span.textContent = '\u200B';
  3944. }
  3945.  
  3946. setCommentSection(1);
  3947. m_last_count = getCountHText(elm);
  3948. // _console.log(2907, 2, m_last_count)
  3949. return true;
  3950. }
  3951. ]
  3952.  
  3953.  
  3954. let innerDOMCommentsCountTextCache = null;
  3955. /**
  3956. *
  3957. * @param {boolean} [requireResultCaching]
  3958. * @returns
  3959. */
  3960. function innerDOMCommentsCountLoader(requireResultCaching) {
  3961. // independent of tabs initialization
  3962. // f() is executed after tabs being ready
  3963.  
  3964. /** @type {HTMLElement | null} */
  3965. const ytdFlexyElm = es.ytdFlexy;
  3966. if (!scriptEnable || !ytdFlexyElm) return;
  3967.  
  3968. // _console.log(3434, pageType)
  3969. if (pageType !== 'watch') return;
  3970.  
  3971. /** @type {HTMLElement | null} */
  3972. const qtElm = document.querySelector('ytd-comments#comments');
  3973. if (!qtElm) return;
  3974.  
  3975. /** @type {Array<HTMLElement>} */
  3976. 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');
  3977.  
  3978. const eTime = +`${Date.now() - mTime}00`;
  3979.  
  3980. let res = new Array(qmElms.length);
  3981. res.newFound = false;
  3982.  
  3983.  
  3984. let ci = 0;
  3985. let latest = -1;
  3986.  
  3987. let retrival = cmTime;
  3988. cmTime = eTime;
  3989.  
  3990. for (const qmElm of qmElms) {
  3991.  
  3992. let mgz = 0
  3993. if (qmElm.id === 'count') {
  3994. //#count.ytd-comments-header-renderer
  3995. mgz = 1;
  3996.  
  3997. } else if ((qmElm.textContent || '').trim()) {
  3998. //ytd-message-renderer.ytd-item-section-renderer
  3999. mgz = 2;
  4000. // it is possible to get the message before the header generation.
  4001. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  4002. // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  4003. }
  4004.  
  4005. if (mgz > 0) {
  4006.  
  4007.  
  4008. let lastUpdate = loadedCommentsDT.get(qmElm) || 0;
  4009. let diff = retrival - lastUpdate
  4010. // _console.log(2907, diff)
  4011. let isNew = (diff > 4 || diff < -4);
  4012. if (!isNew) {
  4013. loadedCommentsDT.set(qmElm, eTime);
  4014. } else {
  4015. loadedCommentsDT.set(qmElm, eTime + 1);
  4016. res.newFound = true;
  4017. latest = ci;
  4018. }
  4019.  
  4020. res[ci] = {
  4021. elm: mWeakRef(qmElm),
  4022. isNew: isNew,
  4023. isLatest: false, //set afterwards
  4024. f: innerCommentsFuncs[mgz - 1]
  4025. }
  4026.  
  4027. if (DEBUG_LOG) {
  4028. res[ci].status = mgz;
  4029. res[ci].text = qmElm.textContent;
  4030. }
  4031.  
  4032. ci++;
  4033.  
  4034. }
  4035.  
  4036. }
  4037. if (res.length > ci) res.length = ci;
  4038.  
  4039. if (latest >= 0) {
  4040.  
  4041. res[latest].isLatest = true;
  4042.  
  4043.  
  4044. let elm = kRef(res[latest].elm);
  4045. if (elm)
  4046. innerDOMCommentsCountTextCache = elm.textContent;
  4047.  
  4048. } else if (res.length === 1) {
  4049.  
  4050. let qmElm = kRef(res[0].elm);
  4051. let t = null;
  4052. if (qmElm) {
  4053.  
  4054. let t = qmElm.textContent;
  4055. if (t !== innerDOMCommentsCountTextCache) {
  4056.  
  4057.  
  4058. loadedCommentsDT.set(qmElm, eTime + 1);
  4059. res.newFound = true;
  4060.  
  4061. res[0].isNew = true;
  4062. latest = 0;
  4063.  
  4064. res[latest].isLatest = true;
  4065.  
  4066. }
  4067.  
  4068. innerDOMCommentsCountTextCache = t;
  4069.  
  4070.  
  4071. }
  4072.  
  4073.  
  4074. }
  4075.  
  4076.  
  4077. // _console.log(2908, res, Q.comments_section_loaded)
  4078.  
  4079. // _console.log(696, res.map(e => ({
  4080.  
  4081. // text: kRef(e.elm).textContent,
  4082. // isNew: e.isNew,
  4083. // isLatest: e.isLatest
  4084.  
  4085. // })))
  4086.  
  4087. if (requireResultCaching) {
  4088. resultCommentsCountCaching(res);
  4089. }
  4090.  
  4091. return res;
  4092.  
  4093. }
  4094.  
  4095. function restoreFetching() {
  4096.  
  4097.  
  4098. if (mtf_forceCheckLiveVideo_disable === 2) return;
  4099.  
  4100.  
  4101. const ytdFlexyElm = es.ytdFlexy;
  4102. if (!ytdFlexyElm) return;
  4103.  
  4104. // _console.log(2901)
  4105.  
  4106. if ((ytdFlexyElm.getAttribute('tyt-comments') || '').indexOf('K') >= 0) return;
  4107.  
  4108. // _console.log(2902)
  4109.  
  4110. let visibleComments = _querySelector.call(ytdFlexyElm, 'ytd-comments#comments:not([hidden])')
  4111. if (!visibleComments) return;
  4112.  
  4113. // _console.log(2903)
  4114.  
  4115.  
  4116. ytdFlexyElm.setAttribute('tyt-comments', 'Kz');
  4117.  
  4118. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]');
  4119. let span = _querySelector.call(tabBtn, 'span#tyt-cm-count');
  4120. tabBtn.removeAttribute('loaded-comment')
  4121. span.innerHTML = createHTML('');
  4122.  
  4123. if (tabBtn) {
  4124. setTabBtnVisible(tabBtn, true);
  4125. }
  4126.  
  4127. // _console.log(2905)
  4128.  
  4129.  
  4130. }
  4131.  
  4132. function checkAndMakeNewCommentFetch() {
  4133. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  4134. fetchCounts.new.f();
  4135. fetchCounts.fetched = true;
  4136. fetchCommentsFinished();
  4137. // _console.log(9972, 'fetched = true')
  4138. }
  4139. }
  4140. function onCommentsReady(e) {
  4141. // e => from commentsHeaderAppended
  4142. let b = false;
  4143. if (e && mtf_forceCheckLiveVideo_disable !== 2 && document.querySelector('ytd-comments#comments:not([hidden])')) {
  4144. // 'YTD-COMMENTS-HEADER-RENDERER', native DOM
  4145. setCommentSection(0);
  4146. checkAndMakeNewCommentFetch();
  4147. b = true;
  4148. }
  4149. _onCommentsReady(b);
  4150. }
  4151.  
  4152. function _onCommentsReady(b) {
  4153. if (mtf_forceCheckLiveVideo_disable !== 2) {
  4154. if (document.querySelector('ytd-comments#comments').hasAttribute('hidden')) {
  4155. // unavailable but not due to live chat
  4156. _disableComments();
  4157. } else if (Q.comments_section_loaded === 0) {
  4158. if (b || (comments_loader & 3) === 3) {
  4159. getFinalComments();
  4160. }
  4161. }
  4162. }
  4163. }
  4164.  
  4165. const resultCommentsCountCaching = (res) => {
  4166. // update fetchCounts by res
  4167. // indepedent of previous state of fetchCounts
  4168. // _console.log(2908, 10, res)
  4169. if (!res) return;
  4170. fetchCounts.count = res.length;
  4171. //if(fetchCounts.new && !document.documentElement.contains(fetchCounts.new.elm)) fetchCounts.new = null;
  4172. //if(fetchCounts.base && !document.documentElement.contains(fetchCounts.base.elm)) fetchCounts.base = null;
  4173. if (fetchCounts.new) return;
  4174. let newFound = res.newFound;
  4175. if (!newFound) {
  4176. if (res.length === 1) {
  4177. fetchCounts.base = res[0];
  4178. return false;
  4179. }
  4180. } else if (res.length === 1) {
  4181. fetchCounts.new = res[0];
  4182. return true;
  4183. } else if (res.length > 1) {
  4184. for (const entry of res) {
  4185. if (entry.isLatest === true && entry.isNew) {
  4186. fetchCounts.new = entry;
  4187. return true;
  4188. }
  4189. }
  4190. }
  4191. }
  4192.  
  4193. const domInit_comments = () => {
  4194.  
  4195.  
  4196. let comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  4197. if (!comments) return;
  4198.  
  4199. // once per {ytd-comments#comments} detection
  4200.  
  4201. const ytdFlexyElm = es.ytdFlexy;
  4202. if (!scriptEnable || !ytdFlexyElm) return;
  4203.  
  4204. if (mtoVisibility_Comments.bindElement(comments)) {
  4205. mtoVisibility_Comments.observer.check(9);
  4206. }
  4207.  
  4208.  
  4209. };
  4210.  
  4211. const fixLiveChatToggleButtonDispatchEvent = () => {
  4212. sendToPageScript(document, "tabview-fix-live-chat-toggle-btn");
  4213. }
  4214.  
  4215. // let chatroomAttrCollapseCount = 0;
  4216.  
  4217. const FP = {
  4218.  
  4219. mtf_attrPlaylist: (attrName, newValue) => {
  4220. //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single
  4221. //::attr ~ hidden
  4222. //console.log(1210)
  4223.  
  4224. // _console.log(21311)
  4225. if (!scriptEnable) return;
  4226. if (pageType !== 'watch') return;
  4227. /** @type {HTMLElement|null} */
  4228. let cssElm = es.ytdFlexy;
  4229. if (!cssElm) return;
  4230.  
  4231. // _console.log(21312)
  4232.  
  4233. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist'); // can be null if it is manually triggered
  4234. let isAnyPlaylistExist = playlist && !playlist.hasAttribute('hidden');
  4235. const tabBtn = document.querySelector('[tyt-tab-content="#tab-list"]');
  4236. //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden'))
  4237. if (tabBtn) {
  4238. //console.log('attr playlist changed')
  4239. let isPlaylistTabHidden = tabBtn.classList.contains('tab-btn-hidden')
  4240. if (isPlaylistTabHidden && isAnyPlaylistExist) {
  4241. //console.log('attr playlist changed - no hide')
  4242. tabBtn.classList.remove("tab-btn-hidden");
  4243. } else if (!isPlaylistTabHidden && !isAnyPlaylistExist) {
  4244. //console.log('attr playlist changed - add hide')
  4245. hideTabBtn(tabBtn);
  4246. }
  4247. }
  4248. /* visible layout for triggering hidden removal */
  4249.  
  4250. },
  4251. mtf_attrComments: (attrName, newValue) => {
  4252. //attr mutation checker - {ytd-comments#comments} \single
  4253. //::attr ~ hidden
  4254.  
  4255. // *** consider this can happen immediately after pop state. timeout / interval might clear out.
  4256.  
  4257. renderDeferred.resolved && innerDOMCommentsCountLoader(true);
  4258. // this is triggered by mutationobserver, the comment count update might have ouccred
  4259.  
  4260. if (pageType !== 'watch') return;
  4261.  
  4262. let comments = document.querySelector('ytd-comments#comments')
  4263. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]');
  4264. if (!comments || !tabBtn) return;
  4265. let isCommentHidden = comments.hasAttribute('hidden')
  4266. //console.log('attr comments changed')
  4267.  
  4268.  
  4269. const ytdFlexyElm = es.ytdFlexy;
  4270. if (!scriptEnable || !ytdFlexyElm) return;
  4271.  
  4272. if (mtf_forceCheckLiveVideo_disable === 2) {
  4273.  
  4274. } else if (!isCommentHidden) {
  4275.  
  4276. ytdFlexyElm.setAttribute('tyt-comments', 'Kv');
  4277. if (!fetchCounts.fetched) {
  4278. emptyCommentSection();
  4279. }
  4280. //_console.log(9360, 71);
  4281. setTabBtnVisible(tabBtn, true); //if contains
  4282.  
  4283. } else if (isCommentHidden) {
  4284.  
  4285. ytdFlexyElm.setAttribute('tyt-comments', 'Kh');
  4286. if (pageType === 'watch' && Q.comments_section_loaded === 1) {
  4287. emptyCommentSection();
  4288. // _console.log(9360, 72);
  4289. }
  4290.  
  4291. }
  4292.  
  4293.  
  4294. },
  4295.  
  4296. mtf_attrChatroom: () => {
  4297. //attr mutation checker - {ytd-live-chat-frame#chat} \single
  4298. //::attr ~ collapsed
  4299.  
  4300. const ytdFlexyElm = es.ytdFlexy;
  4301. if (!scriptEnable || !ytdFlexyElm) return;
  4302. if (pageType !== 'watch') return;
  4303.  
  4304. setToggleBtnTxt();
  4305.  
  4306. layoutStatusMutex.lockWith(unlock => {
  4307.  
  4308. const chatBlock = document.querySelector('ytd-live-chat-frame#chat');
  4309. /** @type {HTMLElement | null} */
  4310. const cssElm = es.ytdFlexy;
  4311. if (!chatBlock || !cssElm) {
  4312. unlock();
  4313. return;
  4314. }
  4315.  
  4316. if (pageType !== 'watch') {
  4317. unlock();
  4318. return;
  4319. }
  4320.  
  4321. let newAttrV = '';
  4322. //mtf_attrChatroom => chat exist => tyt-chat non-null
  4323.  
  4324. let isCollapsed = !!chatBlock.hasAttribute('collapsed');
  4325.  
  4326. let currentAttr = cssElm.getAttribute('tyt-chat');
  4327.  
  4328. if (currentAttr !== null) { //string // [+-]?[az]+[az\$]+
  4329. let isPlusMinus = currentAttr.charCodeAt(0) < 46; // 43 OR 45
  4330. if (isPlusMinus) newAttrV = currentAttr.substring(1);
  4331. }
  4332.  
  4333. if (isCollapsed) newAttrV = `-${newAttrV}`;
  4334. if (!isCollapsed) newAttrV = `+${newAttrV}`;
  4335.  
  4336. wAttr(cssElm, 'tyt-chat', newAttrV);
  4337.  
  4338.  
  4339. if (typeof newAttrV === 'string' && !isCollapsed) lstTab.lastPanel = '#chatroom';
  4340.  
  4341. if (!isCollapsed && isAnyActiveTab() && isWideScreenWithTwoColumns() && !isTheater()) {
  4342. switchTabActivity(null);
  4343. getDMPromise().then(unlock);
  4344. } else {
  4345. unlock();
  4346. }
  4347.  
  4348. if (isCollapsed) {
  4349. // ++chatroomAttrCollapseCount;
  4350. // chatBlock.removeAttribute('tyt-iframe-loaded');
  4351. chatBlock.classList.remove('tyt-chat-frame-ready');
  4352. // console.log(922,1)
  4353. // buggy; this section might not be correctly executed.
  4354. // guess no collapse change but still iframe will distory and reload.
  4355. let btn = document.querySelector('tyt-iframe-popup-btn')
  4356. if (btn) btn.remove();
  4357. } else {
  4358.  
  4359. const iframe = _querySelector.call(chatBlock, 'body iframe.style-scope.ytd-live-chat-frame#chatframe');
  4360. // console.log("iframe.xx",501,iframe)
  4361. // showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 0B');
  4362. if (iframe) Promise.resolve(iframe).then(iframeToVisible); // fix empty
  4363.  
  4364. }
  4365.  
  4366. fixLiveChatToggleButtonDispatchEvent();
  4367.  
  4368. })
  4369.  
  4370.  
  4371. },
  4372.  
  4373. mtf_attrEngagementPanel: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  4374. //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple
  4375. //::attr ~ visibility
  4376.  
  4377. const cssElm = es.ytdFlexy;
  4378. if (!scriptEnable || !cssElm) return;
  4379. let found = null
  4380. if (mutations === 9) {
  4381. found = observer
  4382. } else {
  4383. 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])')) {
  4384. // do nothing
  4385. } else {
  4386. mtoVisibility_EngagementPanel.clear(true)
  4387. storeLastPanel = null;
  4388. wAttr(cssElm, 'tyt-ep-visible', false);
  4389. }
  4390. return
  4391. }
  4392. let nextValue = engagement_panels_().value;
  4393. let previousValue = +cssElm.getAttribute('tyt-ep-visible') || 0;
  4394. if (nextValue === 0 || previousValue === nextValue) return
  4395. cssElm.setAttribute('tyt-ep-visible', nextValue);
  4396. lstTab.lastPanel = `#engagement-panel-${nextValue}`;
  4397. storeLastPanel = mWeakRef(found)
  4398. discardableFn(() => {
  4399. if (es.storeLastPanel !== found) return
  4400. layoutStatusMutex.lockWith(unlock => {
  4401. if (es.storeLastPanel === found && whenEngagemenetPanelVisible()) {
  4402. getDMPromise().then(unlock);
  4403. } else {
  4404. unlock();
  4405. }
  4406. });
  4407. });
  4408. }
  4409.  
  4410. }
  4411.  
  4412.  
  4413. function variableResets() {
  4414.  
  4415. // reset variables when it is confirmed a new page is loaded
  4416.  
  4417. lstTab =
  4418. {
  4419. lastTab: null, //tab-xxx
  4420. lastPanel: null,
  4421. last: null
  4422. };
  4423.  
  4424. scriptEnable = false;
  4425. ytdFlexy = null;
  4426. wls.layoutStatus = 0;
  4427.  
  4428. mtoVisibility_Playlist.clear(true)
  4429. mtoVisibility_Comments.clear(true)
  4430.  
  4431. mtoVisibility_Chatroom.clear(true)
  4432. mtoFlexyAttr.clear(true)
  4433. mtoBodyAttr.clear(true)
  4434.  
  4435.  
  4436. mtf_chatBlockQ = null;
  4437.  
  4438. }
  4439.  
  4440.  
  4441.  
  4442. function contentExtractor(elm) {
  4443. if (!(elm instanceof HTMLElement)) {
  4444. return null;
  4445. }
  4446. let m = elm.textContent;
  4447. let isEmpty = m.trim().length === 0;
  4448. if (isEmpty) return null;
  4449. let s = elm.nodeName;
  4450. let id = elm.id;
  4451. if (id) s += '#' + id;
  4452. return s + '\n' + m;
  4453. }
  4454.  
  4455. async function checkDuplicatedMetaRecommendation() {
  4456. let mainContent0 = document.querySelector('#primary.ytd-watch-flexy #above-the-fold.ytd-watch-metadata');
  4457. let mainContent1 = document.querySelector('#tab-info ytd-expander > #content');
  4458. if (mainContent0 && mainContent1) {
  4459. const hashedContents = new Set();
  4460. for (let s1 = elementNextSibling(mainContent1); s1 instanceof HTMLElement; s1 = elementNextSibling(s1)) {
  4461. let m = contentExtractor(s1);
  4462. if (m === null) continue;
  4463. hashedContents.add(m);
  4464. }
  4465.  
  4466. for (let s0 = elementNextSibling(mainContent0); s0 instanceof HTMLElement; s0 = elementNextSibling(s0)) {
  4467. let m = contentExtractor(s0);
  4468. if (m !== null && hashedContents.has(m)) {
  4469. s0.classList.add('tyt-hidden-duplicated-meta');
  4470. } else {
  4471. s0.classList.remove('tyt-hidden-duplicated-meta');
  4472. }
  4473. }
  4474. hashedContents.clear();
  4475. }
  4476. }
  4477.  
  4478. const elementMapper = new WeakMap();
  4479.  
  4480.  
  4481. const isContentDuplicationCheckAllow = () => {
  4482. return document.querySelectorAll('[tyt-info-expander-placeholder]').length === 1 && document.querySelectorAll('[tyt-info-expander-content]').length === 1;
  4483. }
  4484.  
  4485. let waitForContentReady = new PromiseExternal(); // dummy initial value
  4486.  
  4487. async function removeContentMismatch() {
  4488.  
  4489. if (!document.querySelector('#tab-info')) return;
  4490. const dmysOnPage = document.querySelectorAll('[tyt-info-expander-placeholder]');
  4491. if (dmysOnPage.length === 1) {
  4492.  
  4493. const dmyElm = dmysOnPage[0];
  4494. const expander = elementMapper.get(dmyElm);
  4495. if (!expander) return;
  4496.  
  4497. for (const s of document.querySelectorAll('[tyt-info-expander-content]')) {
  4498. if (expander !== s) s.remove();
  4499. }
  4500. if (expander.isConnected === false) {
  4501. document.querySelector('#tab-info').appendChild(expander);
  4502. }
  4503.  
  4504. isContentDuplicationCheckAllow() && waitForContentReady.resolve();
  4505.  
  4506. }
  4507.  
  4508.  
  4509. }
  4510.  
  4511. function getWord(tag) {
  4512. return langWords[pageLang][tag] || langWords['en'][tag] || '';
  4513. }
  4514.  
  4515.  
  4516. function getTabsHTML() {
  4517.  
  4518. const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}<span>${getWord('videos')}</span>`;
  4519. const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}<span>${getWord('info')}</span>`;
  4520. const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}<span>${getWord('playlist')}</span>`;
  4521.  
  4522. let str1 = `
  4523. <paper-ripple class="style-scope yt-icon-button">
  4524. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  4525. <div id="waves" class="style-scope paper-ripple"></div>
  4526. </paper-ripple>
  4527. `;
  4528.  
  4529. let str_fbtns = `
  4530. <div class="font-size-right">
  4531. <div class="font-size-btn font-size-plus" tyt-di="8rdLQ">
  4532. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  4533. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  4534. <path d="M12 25H38M25 12V38"/>
  4535. </svg>
  4536. </div><div class="font-size-btn font-size-minus" tyt-di="8rdLQ">
  4537. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  4538. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  4539. <path d="M12 25h26"/>
  4540. </svg>
  4541. </div>
  4542. </div>
  4543. `.replace(/[\r\n]+/g, '');
  4544.  
  4545. const str_tabs = [
  4546. `<a id="tab-btn1" tyt-di="q9Kjc" tyt-tab-content="#tab-info" class="tab-btn${(hiddenTabsByUserCSS & 1) === 1 ? ' tab-btn-hidden' : ''}">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
  4547. `<a id="tab-btn3" tyt-di="q9Kjc" tyt-tab-content="#tab-comments" class="tab-btn${(hiddenTabsByUserCSS & 2) === 2 ? ' tab-btn-hidden' : ''}">${svgElm(16, 16, 120, 120, svgComments)}<span id="tyt-cm-count"></span>${str1}${str_fbtns}</a>`,
  4548. `<a id="tab-btn4" tyt-di="q9Kjc" tyt-tab-content="#tab-videos" class="tab-btn${(hiddenTabsByUserCSS & 4) === 4 ? ' tab-btn-hidden' : ''}">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
  4549. `<a id="tab-btn5" tyt-di="q9Kjc" tyt-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`
  4550. ].join('');
  4551.  
  4552. let addHTML = `
  4553. <div id="right-tabs">
  4554. <tabview-view-pos-thead></tabview-view-pos-thead>
  4555. <header>
  4556. <div id="material-tabs">
  4557. ${str_tabs}
  4558. </div>
  4559. </header>
  4560. <div class="tab-content">
  4561. <div id="tab-info" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  4562. <div id="tab-comments" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  4563. <div id="tab-videos" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  4564. <div id="tab-list" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  4565. </div>
  4566. </div>
  4567. `;
  4568.  
  4569. return addHTML;
  4570.  
  4571. }
  4572.  
  4573.  
  4574. function getLang() {
  4575.  
  4576. let lang = 'en';
  4577. let htmlLang = ((document || 0).documentElement || 0).lang || '';
  4578. switch (htmlLang) {
  4579. case 'en':
  4580. case 'en-GB':
  4581. lang = 'en';
  4582. break;
  4583. case 'de':
  4584. case 'de-DE':
  4585. lang = 'du';
  4586. break;
  4587. case 'fr':
  4588. case 'fr-CA':
  4589. case 'fr-FR':
  4590. lang = 'fr';
  4591. break;
  4592. case 'zh-Hant':
  4593. case 'zh-Hant-HK':
  4594. case 'zh-Hant-TW':
  4595. lang = 'tw';
  4596. break;
  4597. case 'zh-Hans':
  4598. case 'zh-Hans-CN':
  4599. lang = 'cn';
  4600. break;
  4601. case 'ja':
  4602. case 'ja-JP':
  4603. lang = 'jp';
  4604. break;
  4605. case 'ko':
  4606. case 'ko-KR':
  4607. lang = 'kr';
  4608. break;
  4609. case 'ru':
  4610. case 'ru-RU':
  4611. lang = 'ru';
  4612. break;
  4613. default:
  4614. lang = 'en';
  4615. }
  4616.  
  4617. return lang;
  4618.  
  4619. }
  4620.  
  4621. function getLangForPage() {
  4622.  
  4623. let lang = getLang();
  4624.  
  4625. if (langWords[lang]) pageLang = lang; else pageLang = 'en';
  4626.  
  4627. }
  4628.  
  4629. // function checkEvtTarget(evt, nodeNames) {
  4630. // return nodeNames.includes((((evt || 0).target || 0).nodeName || 0));
  4631. // }
  4632.  
  4633. function pageCheck() {
  4634. // yt-player-updated
  4635. // yt-page-data-updated
  4636. // [is-two-columns_] attr changed => layout changed
  4637.  
  4638. /** @type {HTMLElement | null} */
  4639. const ytdFlexyElm = es.ytdFlexy;
  4640. if (!scriptEnable || !ytdFlexyElm) return;
  4641.  
  4642. const rootDom = document.documentElement;
  4643. rootDom.setAttribute('sxmq8', rootDom.getAttribute('sxmq8') === '1' ? '0' : '1');
  4644. console.log('sxmq8 r1', document.documentElement.getAttribute('sxmq8') );
  4645.  
  4646. let comments = _querySelector.call(ytdFlexyElm, '#primary.ytd-watch-flexy ytd-watch-metadata ~ ytd-comments#comments');
  4647. if (comments) {
  4648. let tabComments = document.querySelector('#tab-comments');
  4649. if (tabComments) {
  4650. elementAppend.call(tabComments, comments);
  4651. }
  4652. }
  4653.  
  4654. mtf_append_playlist(null); // playlist relocated after layout changed
  4655.  
  4656. fixTabs();
  4657.  
  4658. mtf_autocomplete_search();
  4659.  
  4660. }
  4661.  
  4662. function globalHook(eventType, func) {
  4663. if (!func) return;
  4664.  
  4665. const count = (globalHook_hashs[eventType] || 0) + 1;
  4666.  
  4667. globalHook_hashs[eventType] = count;
  4668.  
  4669. const s = globalHook_symbols[count - 1] || (globalHook_symbols[count - 1] = Symbol());
  4670.  
  4671. document.addEventListener(eventType, function (evt) {
  4672. if (evt[s]) return;
  4673. evt[s] = true;
  4674. Promise.resolve().then(() => {
  4675. func(evt);
  4676. })
  4677.  
  4678. }, capturePassive)
  4679.  
  4680. }
  4681.  
  4682. async function makeHeaderFloat() {
  4683. if (isMakeHeaderFloatCalled) return;
  4684. isMakeHeaderFloatCalled = true;
  4685. await Promise.resolve(0);
  4686.  
  4687.  
  4688. const [header, headerP, navElm] = await Promise.all([
  4689. Promise.resolve().then(() => document.querySelector("#right-tabs header")),
  4690.  
  4691. Promise.resolve().then(() => document.querySelector("#right-tabs tabview-view-pos-thead")),
  4692.  
  4693. Promise.resolve().then(() => document.querySelector('#masthead-container, #masthead'))
  4694.  
  4695. ]);
  4696.  
  4697.  
  4698. let ito_dt = 0;
  4699. let ito = new IntersectionObserver((entries) => {
  4700.  
  4701. let xyStatus = null;
  4702.  
  4703. //console.log(entries);
  4704.  
  4705. let xRect = null;
  4706. let rRect = null;
  4707.  
  4708. for (const entry of entries) {
  4709. if (!entry.boundingClientRect || !entry.rootBounds) continue; // disconnected from DOM tree
  4710. if (!entry.isIntersecting && entry.boundingClientRect.y <= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  4711. xyStatus = 2;
  4712. xRect = entry.boundingClientRect;
  4713. rRect = entry.rootBounds;
  4714. } else if (entry.isIntersecting && entry.boundingClientRect.y >= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  4715. xyStatus = 1;
  4716. xRect = entry.boundingClientRect;
  4717. rRect = entry.rootBounds;
  4718. }
  4719. }
  4720. let p = wls.layoutStatus;
  4721. //console.log(document.documentElement.clientWidth)
  4722. if (xyStatus !== null) {
  4723.  
  4724. if (xyStatus === 2 && isStickyHeaderEnabled === true) {
  4725.  
  4726. } else if (xyStatus === 1 && isStickyHeaderEnabled === false) {
  4727.  
  4728. } else {
  4729. singleColumnScrolling2(xyStatus, xRect.width, {
  4730. left: xRect.left,
  4731. right: rRect.width - xRect.right
  4732. });
  4733. }
  4734.  
  4735. }
  4736.  
  4737. let tdt = Date.now();
  4738. ito_dt = tdt;
  4739. setTimeout(() => {
  4740. if (ito_dt !== tdt) return;
  4741. if (p !== wls.layoutStatus) singleColumnScrolling();
  4742. }, 300)
  4743.  
  4744. },
  4745. {
  4746. rootMargin: `0px 0px 0px 0px`,
  4747. threshold: [0]
  4748. })
  4749.  
  4750. ito.observe(headerP)
  4751.  
  4752. }
  4753.  
  4754. function setupVideoTitleHover() {
  4755.  
  4756. let h1 = document.querySelector('#below h1.ytd-watch-metadata yt-formatted-string');
  4757. if (h1) {
  4758.  
  4759.  
  4760. let s = '';
  4761.  
  4762. try {
  4763. if (formatDates && Object.keys(formatDates).length > 0) {
  4764.  
  4765. function getDurationInfo(bd1, bd2) {
  4766.  
  4767. let bdd = bd2 - bd1
  4768. let hrs = Math.floor(bdd / 3600000)
  4769. bdd = bdd - hrs * 3600000
  4770. let mins = Math.round(bdd / 60000)
  4771. let seconds = null
  4772. if (mins < 10 && hrs === 0) {
  4773. mins = Math.floor(bdd / 60000)
  4774. bdd = bdd - mins * 60000
  4775. seconds = Math.round(bdd / 1000)
  4776. if (seconds === 0) seconds = null
  4777. }
  4778.  
  4779. return { hrs, mins, seconds }
  4780.  
  4781. }
  4782.  
  4783. const formatDateResult = getFormatDateResultFunc();
  4784.  
  4785. if (formatDates.broadcastBeginAt && formatDates.isLiveNow === false) {
  4786.  
  4787. let bd1 = new KDate(formatDates.broadcastBeginAt)
  4788. let bd2 = formatDates.broadcastEndAt ? new KDate(formatDates.broadcastEndAt) : null
  4789.  
  4790. let isSameDay = 0
  4791. if (bd2 && bd1.toLocaleDateString() === bd2.toLocaleDateString()) {
  4792. isSameDay = 1
  4793.  
  4794. } else if (bd2 && +bd2 > +bd1 && bd2 - bd1 < 86400000) {
  4795.  
  4796. if (bd1.getHours() >= 6 && bd2.getHours() < 6) {
  4797. isSameDay = 2
  4798. }
  4799.  
  4800. }
  4801.  
  4802. let durationInfo = getDurationInfo(bd1, bd2)
  4803. if (isSameDay > 0) {
  4804.  
  4805. bd2.dayBack = (isSameDay === 2)
  4806.  
  4807. s = formatDateResult(0x200, { bd1, bd2, isSameDay, durationInfo, formatDates })
  4808.  
  4809. } else if (bd2 && isSameDay === 0) {
  4810.  
  4811. s = formatDateResult(0x210, { bd1, bd2, isSameDay, durationInfo, formatDates })
  4812.  
  4813. }
  4814.  
  4815.  
  4816. } else if (formatDates.broadcastBeginAt && formatDates.isLiveNow === true) {
  4817.  
  4818. let bd1 = new KDate(formatDates.broadcastBeginAt)
  4819.  
  4820. s = formatDateResult(0x300, { bd1, formatDates })
  4821.  
  4822. } else {
  4823. if (formatDates.uploadDate) {
  4824.  
  4825. if (formatDates.publishDate && formatDates.publishDate !== formatDates.uploadDate) {
  4826.  
  4827. s = formatDateResult(0x600, { formatDates })
  4828. } else {
  4829. s = formatDateResult(0x610, { formatDates })
  4830.  
  4831. }
  4832. } else if (!formatDates.uploadDate && formatDates.publishDate) {
  4833.  
  4834. s = formatDateResult(0x700, { formatDates })
  4835.  
  4836.  
  4837. }
  4838. }
  4839.  
  4840.  
  4841. }
  4842. } catch (e) {
  4843. s = '';
  4844. }
  4845.  
  4846. if (s) {
  4847. h1.setAttribute('data-title-details', s)
  4848. } else {
  4849. h1.removeAttribute('data-title-details')
  4850. }
  4851.  
  4852. }
  4853.  
  4854. }
  4855.  
  4856. const skPlayListDataReassign = ControllerID();
  4857.  
  4858. function checkPlaylistForInitialization() {
  4859. // if the page url is with playlist; renderer event might not occur.
  4860.  
  4861. // playlist already added to dom; this is to set the visibility event and change hidden status
  4862.  
  4863. let m_playlist = document.querySelector(`#tab-list ytd-playlist-panel-renderer#playlist:not([o3r-${sa_playlist}])`)
  4864.  
  4865. // once per {ytd-playlist-panel-renderer#playlist} detection
  4866.  
  4867. // _console.log(3902, !!m_playlist)
  4868.  
  4869. const ytdFlexyElm = es.ytdFlexy;
  4870. if (!scriptEnable || !ytdFlexyElm) { }
  4871. else if (m_playlist) {
  4872.  
  4873. if (mtoVisibility_Playlist.bindElement(m_playlist)) {
  4874. mtoVisibility_Playlist.observer.check(9); // delay check required for browser bug - hidden changed not triggered
  4875. }
  4876. let playlist_wr = mWeakRef(m_playlist);
  4877. scriptletDeferred.debounce(() => {
  4878. let m_playlist = kRef(playlist_wr);
  4879. playlist_wr = null;
  4880. if (m_playlist && m_playlist.isConnected === true) {
  4881. sendToPageScript(m_playlist, "tabview-yt-data-reassign");
  4882. }
  4883. m_playlist = null;
  4884. }, skPlayListDataReassign)
  4885. m_playlist = null;
  4886.  
  4887. }
  4888.  
  4889. FP.mtf_attrPlaylist();
  4890.  
  4891. Promise.resolve(0).then(() => {
  4892. // ['tab-btn', 'tab-btn', 'tab-btn active', 'tab-btn tab-btn-hidden']
  4893. // bug
  4894. const ytdFlexyElm = es.ytdFlexy;
  4895. if (!scriptEnable || !ytdFlexyElm) return;
  4896. if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tyt-tab') + '').indexOf('#tab-') === 0 && location.pathname === '/watch') {
  4897. if (/[\?\&]list=[\w\-\_]+/.test(location.search)) {
  4898. if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  4899. } else if (/[\?\&]lc=[\w\-\_]+/.test(location.search)) {
  4900. if (setToActiveTab('#tab-comments')) switchTabActivity_lastTab = '#tab-comments';
  4901. }
  4902. }
  4903. })
  4904.  
  4905. Promise.resolve(0).then(() => {
  4906. mtf_fix_collapsible_playlist();
  4907. });
  4908.  
  4909. }
  4910.  
  4911.  
  4912. const _pageBeingInit = function () {
  4913.  
  4914. psId.inc(); // add one
  4915.  
  4916. fetchCounts = {
  4917. base: null,
  4918. new: null,
  4919. fetched: false,
  4920. count: null
  4921. }
  4922. pageFetchedDataVideoId = null;
  4923. chatroomDetails = null;
  4924. }
  4925.  
  4926. const pageBeingInit = function () {
  4927. // trigger at pageSeq1/2: yt-navigate-start / yt-navigate-cache / yt-navigate-redirect / yt-page-data-fetched
  4928.  
  4929. // call regardless pageType
  4930. // run once on / before pageSeq2 (yt-page-data-fetched)
  4931.  
  4932. const rootDom = document.documentElement;
  4933. // rootDom.setAttribute('sxmq7', rootDom.getAttribute('sxmq7') === '1' ? '0' : '1');
  4934. rootDom.setAttribute('sxmq8', rootDom.getAttribute('sxmq8') === '1' ? '0' : '1');
  4935. rootDom.removeAttribute('pnzgu');
  4936.  
  4937. console.log('sxmq8 r2', document.documentElement.getAttribute('sxmq8') );
  4938.  
  4939. infoContentDS = 0;
  4940. renderIdentifier.inc(); // add 1
  4941. renderDeferred.reset(); // clear quene of pending renderDeferreds
  4942.  
  4943.  
  4944. if (!scriptletDeferred.resolved && (firstLoadStatus & 2) === 2) {
  4945. // insert on first pageBeingInit(), regardless pageType
  4946. firstLoadStatus -= 2;
  4947. script_inject_js1.inject();
  4948. } else if (pageRendered === 2) { // (pageRendered = 2 after pageSeq2)
  4949. // reset on 2nd+ pageBeingInit(), regardless pageType
  4950. pageRendered = 0;
  4951. let elmPL = document.querySelector('tabview-view-ploader');
  4952. if (elmPL) elmPL.remove();
  4953. // tabview-view-ploader is appended in pageBeingFetched (pageSeq2: yt-page-data-fetched)
  4954. }
  4955.  
  4956. if (tabsDeferred.resolved) {
  4957. comments_loader = 1;
  4958. tabsDeferred.reset(); // tabsDeferred.resolve() again in yt-navigate-finish[page=watch]
  4959. if ((firstLoadStatus & 8) === 0) {
  4960. innerDOMCommentsCountLoader(false); //ensure the previous record is saved
  4961. // no need to cache to the rendering state
  4962. _pageBeingInit();
  4963. }
  4964. // _console.log('pageBeingInit', firstLoadStatus)
  4965. }
  4966.  
  4967. };
  4968.  
  4969. function getFinalComments() {
  4970.  
  4971. comments_loader = 0;
  4972.  
  4973. let ei = 0;
  4974.  
  4975. function execute() {
  4976. //sync -> animateLoadDeferred.resolved always true
  4977.  
  4978. if (!renderDeferred.resolved) return;
  4979.  
  4980. // _console.log(2323)
  4981.  
  4982. if (Q.comments_section_loaded !== 0) return;
  4983. if (fetchCounts.fetched) return;
  4984.  
  4985.  
  4986. let ret = innerDOMCommentsCountLoader(true);
  4987.  
  4988. if (fetchCounts.new && !fetchCounts.fetched) {
  4989. if (fetchCounts.new.f()) {
  4990. fetchCounts.fetched = true;
  4991. fetchCommentsFinished();
  4992. // _console.log(9972, 'fetched = true')
  4993. }
  4994. return;
  4995. }
  4996.  
  4997.  
  4998. ei++;
  4999.  
  5000. if (fetchCounts.base && !fetchCounts.new && !fetchCounts.fetched && fetchCounts.count === 1) {
  5001.  
  5002.  
  5003. let elm = kRef(fetchCounts.base.elm);
  5004. let txt = elm ? getCountHText(elm) : null;
  5005. let condi1 = ei > 7;
  5006. let condi2 = txt === m_last_count;
  5007. if (condi1 || condi2) {
  5008.  
  5009. if (fetchCounts.base.f()) {
  5010. fetchCounts.fetched = true;
  5011. fetchCommentsFinished();
  5012. // _console.log(9972, 'fetched = true')
  5013. }
  5014.  
  5015. }
  5016.  
  5017. }
  5018.  
  5019. if (!fetchCounts.fetched) {
  5020. if (ei > 7) {
  5021. let elm = ret.length === 1 ? kRef(ret[0].elm) : null;
  5022. let txt = elm ? getCountHText(elm) : null;
  5023. if (elm && txt !== m_last_count) {
  5024. fetchCounts.base = null;
  5025. fetchCounts.new = ret[0];
  5026. fetchCounts.new.f();
  5027. fetchCounts.fetched = true;
  5028. // _console.log(9979, 'fetched = true')
  5029. fetchCommentsFinished();
  5030. }
  5031. return;
  5032. }
  5033. return true;
  5034. }
  5035.  
  5036. }
  5037.  
  5038.  
  5039. async function alCheckFn(ks) {
  5040.  
  5041. let alCheckInterval = 420;
  5042.  
  5043. for (let alCheckCount = 9; --alCheckCount > 0;) {
  5044.  
  5045. if (renderIdentifier.valueOf() !== ks) break;
  5046. if (execute() !== true) break;
  5047.  
  5048. await new Promise(r => setTimeout(r, alCheckInterval));
  5049.  
  5050. }
  5051.  
  5052. }
  5053.  
  5054. let ks = renderIdentifier.valueOf();
  5055. renderDeferred.debounce(() => {
  5056. if (ks !== renderIdentifier.valueOf()) return
  5057. if (mvideoState & 32) return;
  5058. mvideoState |= 32;
  5059. alCheckFn(ks);
  5060.  
  5061. });
  5062.  
  5063.  
  5064. }
  5065.  
  5066. const { removeDuplicateInfoFn, setHiddenStateForDesc } = (() => {
  5067.  
  5068. let g_check_detail_A = 0;
  5069. function setHiddenStateForDesc() {
  5070. let ytdFlexyElm = es.ytdFlexy;
  5071. if (!ytdFlexyElm) return;
  5072. let hiddenBool = !document.fullscreenElement ? ytdFlexyElm.classList.contains('tabview-info-duplicated') : false;
  5073. let elm;
  5074. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata #plain-snippet-text');
  5075. if (elm) {
  5076. wAttr(elm, 'hidden', hiddenBool);
  5077. }
  5078. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata #formatted-snippet-text');
  5079. if (elm) {
  5080. wAttr(elm, 'hidden', hiddenBool);
  5081. }
  5082. }
  5083. function checkDuplicatedInfo_then() {
  5084.  
  5085. const ytdFlexyElm = es.ytdFlexy;
  5086. if (!ytdFlexyElm) return; //unlikely
  5087.  
  5088. let cssbool_c1 = false, cssbool_c2 = false;
  5089.  
  5090. if (ytdFlexyElm.matches('.tabview-info-duplicated[flexy]')) {
  5091. cssbool_c1 = !!_querySelector.call(ytdFlexyElm, '#description.style-scope.ytd-watch-metadata > #description-inner:only-child');
  5092. cssbool_c2 = !!_querySelector.call(ytdFlexyElm, '#tab-info ytd-expander #description.ytd-video-secondary-info-renderer');
  5093. }
  5094.  
  5095. setHiddenStateForDesc();
  5096.  
  5097. ytdFlexyElm.setAttribute('tyt-has', `${cssbool_c1 ? 'A' : 'a'}${cssbool_c2 ? 'B' : 'b'}`);
  5098.  
  5099. }
  5100.  
  5101. async function contentPairSet(firstElement, secondElement) {
  5102.  
  5103. // const firstElementSelector = "ytd-text-inline-expander#description-inline-expander";
  5104. // const secondElementSelector = "#tab-info ytd-expander #description";
  5105.  
  5106. // const firstElement = document.querySelector(firstElementSelector);
  5107. // const secondElement = document.querySelector(secondElementSelector);
  5108. if (firstElement && secondElement) {
  5109.  
  5110. // checked that e1 e2 shall be considered with matching pair
  5111. firstElement.setAttribute('tyt-du744', '');
  5112. secondElement.setAttribute('tyt-du744', '');
  5113. }
  5114.  
  5115. }
  5116.  
  5117. async function _checkDuplicatedInfoAug2023(_firstElement, _secondElement, spk) {
  5118. /** @type {HTMLElement} */
  5119. const firstElement = _firstElement;
  5120. /** @type {HTMLElement} */
  5121. const secondElement = _secondElement;
  5122.  
  5123. // dont detect the content change of main info box
  5124. // if second info box is checked okay before, skip
  5125. if (firstElement.hasAttribute('tyt-du744') || secondElement.hasAttribute('tyt-du744')) return true; // assume still ok as we checked before
  5126.  
  5127. if (!firstElement || !secondElement) return false;
  5128. if (firstElement.hasAttribute('hidden') || secondElement.hasAttribute('hidden')) return false;
  5129.  
  5130. const isCryptoRandomUUIDAvailable = typeof crypto === 'object' && typeof crypto.randomUUID === 'function' ? `${crypto.randomUUID()}` : '';
  5131. const isReplaceAllAvailable = typeof String.prototype.replaceAll === 'function';
  5132. const doNameReplace = isCryptoRandomUUIDAvailable && isReplaceAllAvailable;
  5133.  
  5134. let nameText = doNameReplace ? (sessionStorage.getItem('js-yt-usernames') || '') : '';
  5135.  
  5136. {
  5137. const ownerEndpoints = document.querySelectorAll('#owner a.yt-simple-endpoint[href]');
  5138. let handle = null;
  5139. let displayName = null;
  5140. for (const anchor of ownerEndpoints) {
  5141. if (displayName === null && anchor.firstElementChild === null && (anchor.textContent || "").length > 0) {
  5142. displayName = anchor.textContent
  5143. }
  5144. let m;
  5145. if (handle === null && (m = /\/(\@[-_a-zA-Z0-9.]{3,30})(\/|$)/.exec(anchor.getAttribute('href')))) {
  5146. handle = m[1];
  5147. }
  5148. }
  5149.  
  5150. if (handle !== null && displayName !== null) {
  5151. if (nameText.indexOf(`\n${handle}\t`) < 0 && `${nameText}\n`.indexOf(`\t${displayName}\n`) < 0) {
  5152. nameText = `\n${handle}\t${displayName}\n${nameText}`.trim();
  5153. }
  5154. }
  5155. }
  5156.  
  5157. const replaceArr = [];
  5158.  
  5159. let rid;
  5160. if (nameText) {
  5161. do {
  5162. rid = crypto.randomUUID();
  5163. } while (nameText.includes(rid));
  5164. let nni = 0;
  5165. const nameMap = new Map();
  5166. for (const nameTextS of nameText.split('\n')) {
  5167. if (nameTextS.length < 4) continue;
  5168. const nameTextA = nameTextS.split('\t');
  5169. const m = nameTextA[1].replaceAll(' ', '');
  5170. let t = nameMap.get(m);
  5171. if (!t) {
  5172. t = ++nni;
  5173. nameMap.set(m, t);
  5174. }
  5175. replaceArr.push([nameTextA[0], t]);
  5176. replaceArr.push([m, t]);
  5177. }
  5178. nameMap.clear();
  5179. if (replaceArr.length > 1) replaceArr.sort((a, b) => b[0].length - a[0].length);
  5180. }
  5181.  
  5182. const fixNameConversion = doNameReplace && rid && replaceArr.length > 0
  5183. ? (text) => {
  5184. if (typeof text === 'string') {
  5185. text = text.replaceAll(' ', '');
  5186. for (const s of replaceArr) {
  5187. const w = `[[${rid}::${s[1]}]]`;
  5188. text = text.replaceAll(s[0], w);
  5189. }
  5190. }
  5191. return text;
  5192. }
  5193. : (text) => text;
  5194.  
  5195. const hiddenTexts = new Map();
  5196.  
  5197. const hiddenTextReplacement = (element) => {
  5198.  
  5199. for (const hiddenElement of _querySelectorAll.call(element, '[hidden]')) {
  5200.  
  5201. const walker = document.createTreeWalker(hiddenElement, NodeFilter.SHOW_TEXT, null, null);
  5202. let node;
  5203. while (node = walker.nextNode()) {
  5204. const text = node.nodeValue;
  5205. if (text && !text.startsWith('\uF204')) {
  5206. hiddenTexts.set(node, text);
  5207. node.nodeValue = `\uF204${text.replace(/[\uF204\uF205]/g, '')}\uF205`;
  5208. }
  5209. }
  5210.  
  5211. }
  5212.  
  5213. }
  5214.  
  5215.  
  5216. const addContent = (currentNode, contentArray) => {
  5217.  
  5218. if (currentNode instanceof HTMLElement) {
  5219. hiddenTextReplacement(currentNode);
  5220. }
  5221.  
  5222. /** @type {string} */
  5223. let trText = currentNode.textContent.trim();
  5224. let withText = trText.length > 0;
  5225. if (withText && trText.includes('\uF204')) {
  5226. trText = trText.replace(/\uF204[^\uF204\uF205]+\uF205/g, '');
  5227. trText = trText.replace(/[\uF204\uF205]/g, '');
  5228. withText = trText.length > 0;
  5229. }
  5230. if (withText) {
  5231. trText = trText.replace(/\n[\n\x20]+\n/g, '\n\n');
  5232. trText = trText.replace(/[\u0020\u00A0\u16A0\u180E\u2000-\u200A\u202F\u205F\u3000]/g, ' ');
  5233. trText = trText.replace(/[\u200b\uFEFF]/g, '');
  5234. let loop = 64;
  5235. while (loop-- > 0) {
  5236. const before = trText;
  5237. trText = trText.replace(/([\u1000-\uDF77])\x20([\x21-\x7E])/g, '$1$2'); // 中英文之间加空白 ?
  5238. trText = trText.replace(/([\x21-\x7E])\x20([\u1000-\uDF77])/g, '$1$2'); // 中英文之间加空白 ?
  5239. if (before === trText) loop = 0;
  5240. }
  5241. // "白州大根\n \n チャンネル登録者数 698人\n \n \n\n\n 動画\n \n\n\n \n \n概要"
  5242. // "白州大根\n \n チャンネル登録者数 698人\n \n \n\n\n 動画\n \n \n概要"
  5243. trText = fixNameConversion(trText);
  5244. trText = trText.replace(/[,,.。、]/g, ' ');
  5245. trText = trText.replace(/[##]/g, '#');
  5246. trText = trText.replace(/[**]/g, '*');
  5247. trText = trText.replace(/[「」『』”’<>"'<>\[\]\{\}]/g, '"');
  5248. trText = trText.replace(/[::]/g, ':');
  5249. trText = trText.replace(/\s+/g, ' ');
  5250. // trText = trText.replace(/[12345678901234567890]/g, '0');
  5251. trText = trText.trim();
  5252. if (trText) contentArray.push(trText);
  5253. }
  5254. }
  5255.  
  5256. const filterNode = (currentNode) => {
  5257.  
  5258.  
  5259. if (currentNode.nodeType === Node.ELEMENT_NODE) {
  5260. if (currentNode.nodeName === "STYLE") {
  5261. // <style is-scoped>
  5262. return false;
  5263. }
  5264. if (currentNode.getAttribute('role') === 'button') { // .role is not working in Firefox
  5265. // tp-yt-paper-button#expand-sizer
  5266. // currentNode.matches('#collapse[role="button"]:not([hidden])')
  5267.  
  5268. return false;
  5269. }
  5270. if (currentNode.hasAttribute("hidden")) {
  5271.  
  5272. return false;
  5273. }
  5274. if (currentNode.id === "snippet") {
  5275. let allHidden = true;
  5276. for (let child = nodeFirstChild(currentNode); child; child = nodeNextSibling(child)) {
  5277. if (filterNode(child) === false) continue;
  5278. if (child.textContent.trim().length === 0) continue;
  5279. allHidden = false;
  5280. break;
  5281. }
  5282. if (allHidden) {
  5283. return false;
  5284. }
  5285. }
  5286.  
  5287. } else if (currentNode.nodeType === Node.TEXT_NODE) {
  5288. } else {
  5289. return false;
  5290. }
  5291.  
  5292. return true;
  5293. }
  5294.  
  5295.  
  5296. const getTextContentArr = async (element, contentArray = []) => {
  5297.  
  5298. try {
  5299.  
  5300. await Promise.resolve();
  5301.  
  5302. for (let currentNode = nodeFirstChild(element); currentNode; currentNode = nodeNextSibling(currentNode)) {
  5303.  
  5304. if (filterNode(currentNode) === false) continue;
  5305.  
  5306. if (currentNode instanceof HTMLElement && currentNode.firstElementChild && !currentNode.nodeName.includes("-")) {
  5307. await getTextContentArr(currentNode, contentArray);
  5308. } else {
  5309. addContent(currentNode, contentArray);
  5310. }
  5311.  
  5312. await Promise.resolve();
  5313. }
  5314.  
  5315. } catch (e) {
  5316. console.log(e)
  5317. }
  5318.  
  5319. // if (spk) return [contentArray.join(' ').replace(/\s+/g, ' ').trim()];
  5320. return contentArray;
  5321. };
  5322.  
  5323. hiddenTexts.clear();
  5324. const [firstElementTextArr, secondElementTextArr] = await Promise.all([getTextContentArr(firstElement), getTextContentArr(secondElement)]);
  5325. hiddenTexts.forEach((t, text) => {
  5326. if ((text.nodeValue || '') === `\uF204${t.replace(/[\uF204\uF205]/g, '')}\uF205`) text.nodeValue = t;
  5327. });
  5328. hiddenTexts.clear();
  5329.  
  5330. if (typeof GM === 'undefined') {
  5331.  
  5332. console.log(7191, firstElementTextArr)
  5333. console.log(7192, secondElementTextArr)
  5334.  
  5335. if (firstElementTextArr.length === secondElementTextArr.length) {
  5336.  
  5337. let n = firstElementTextArr.length;
  5338. const texts = [];
  5339. for (let j = 0; j < n; j++) {
  5340. let text1 = firstElementTextArr[j];
  5341. let text2 = secondElementTextArr[j];
  5342. let s1 = text1.split(/([\w\-]+)/)
  5343. let s2 = text2.split(/([\w\-]+)/)
  5344. let kn = Math.min(s1.length, s2.length);
  5345. let p = -1;
  5346. for (let k = 0; k < kn; k++) {
  5347. if (s1[k] !== s2[k]) break
  5348. p = k;
  5349. }
  5350. if (p >= 0) {
  5351. text1 = s1.slice(p + 1).join('');
  5352. text2 = s2.slice(p + 1).join('');
  5353. }
  5354. texts.push([text1, text2])
  5355. }
  5356. console.log(7194, texts)
  5357.  
  5358. }
  5359.  
  5360. }
  5361.  
  5362. /**
  5363. @param {any[]} arr1
  5364. @param {any[]} arr2 */
  5365. function isSubset(arr1, arr2) {
  5366. const set = new Set(arr2);
  5367. const r = arr1.every(item => set.has(item));
  5368. set.clear();
  5369. return r;
  5370. }
  5371.  
  5372. // console.log(828, window.dbx1 = firstElementTextArr, window.dbx2 = secondElementTextArr);
  5373.  
  5374. // document.documentElement.setAttribute('firstElementTextArr', JSON.stringify(firstElementTextArr))
  5375. // document.documentElement.setAttribute('secondElementTextArr', JSON.stringify(secondElementTextArr))
  5376. // window.dbx1 = JSON.parse(document.documentElement.getAttribute('firstElementTextArr'))
  5377. // window.dbx2 = JSON.parse(document.documentElement.getAttribute('secondElementTextArr'))
  5378. const result = isSubset(firstElementTextArr, secondElementTextArr);
  5379.  
  5380. if (result) {
  5381.  
  5382. contentPairSet(firstElement, secondElement);
  5383.  
  5384. }
  5385.  
  5386. return result;
  5387. }
  5388. async function checkDuplicatedInfoAug2023(targetDuplicatedInfoPanel) {
  5389. // ytd-text-inline-expander#description-inline-expander:not([hidden])
  5390. const firstElementSelector = "ytd-text-inline-expander#description-inline-expander"; // ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata
  5391. const secondElementSelector = "#tab-info ytd-expander #description";
  5392.  
  5393. const firstElement = targetDuplicatedInfoPanel || document.querySelector(firstElementSelector);
  5394. const secondElement = document.querySelector(secondElementSelector);
  5395.  
  5396. if (!firstElement || !secondElement) return false;
  5397.  
  5398. return await _checkDuplicatedInfoAug2023(firstElement, (secondElement.closest('ytd-expander') || secondElement), false);
  5399. }
  5400. // document.addEventListener('dbx', ()=>{
  5401. // console.log(window.dbx1)
  5402. // console.log(window.dbx2)
  5403. // }, true);
  5404.  
  5405. async function checkDuplicatedInfo() {
  5406.  
  5407. const ytdFlexyElm = es.ytdFlexy;
  5408. if (!ytdFlexyElm) return; //unlikely
  5409.  
  5410. const targetDuplicatedInfoPanels = [...document.querySelectorAll('ytd-text-inline-expander#description-inline-expander:not([hidden])')].filter((e) => {
  5411.  
  5412. // playlist WL page -> video -> buggy
  5413. // 0: ytd-text-inline-expander#description-inline-expander.style-scope.ytd-playlist-header-renderer
  5414. // 1: ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata
  5415.  
  5416. return !closestDOM.call(e, '[hidden]');
  5417. });
  5418. if (targetDuplicatedInfoPanels.length !== 1) return;
  5419. const targetDuplicatedInfoPanel = targetDuplicatedInfoPanels[0];
  5420.  
  5421. let t = Date.now();
  5422. g_check_detail_A = t;
  5423.  
  5424. ytdFlexyElm.classList.toggle('tabview-info-duplicated', true) // hide first;
  5425. let infoDuplicated = false;
  5426.  
  5427. try {
  5428. await getDMPromise(); // mcrcr might be not yet initalized
  5429.  
  5430. if (g_check_detail_A !== t) return;
  5431.  
  5432. let clicked = false;
  5433. let promise = null;
  5434. await Promise.all([..._querySelectorAll.call(targetDuplicatedInfoPanel, '#expand[role="button"]:not([hidden])')].map(button => {
  5435. return Promise.resolve().then(() => {
  5436. promise = promise || new Promise(resolve => {
  5437. let mo = new MutationObserver(() => {
  5438. mo.disconnect();
  5439. mo = null;
  5440. resolve();
  5441. });
  5442. mo.observe(targetDuplicatedInfoPanel, { subtree: true, childList: true })
  5443. });
  5444. button.click();
  5445. clicked = true;
  5446. });
  5447. }));
  5448. if (promise) await promise.then();
  5449. if (typeof IntersectionObserver !== 'undefined') {
  5450. await new Promise(resolve => {
  5451. let io = new IntersectionObserver(() => {
  5452. io.disconnect();
  5453. io = null;
  5454. resolve();
  5455. });
  5456. io.observe(targetDuplicatedInfoPanel);
  5457. });
  5458. }
  5459.  
  5460. await Promise.resolve(0);
  5461.  
  5462. infoDuplicated = await checkDuplicatedInfoAug2023(targetDuplicatedInfoPanel);
  5463.  
  5464. if (infoDuplicated === false && clicked) {
  5465.  
  5466. await Promise.all([..._querySelectorAll.call(targetDuplicatedInfoPanel, '#collapse[role="button"]:not([hidden])')].map(button => {
  5467. return Promise.resolve().then(() => {
  5468. button.click();
  5469. });
  5470. }));
  5471.  
  5472. }
  5473.  
  5474. } catch (e) {
  5475.  
  5476. ytdFlexyElm.classList.toggle('tabview-info-duplicated', false) // error => unhide
  5477. }
  5478.  
  5479. console.debug('[tyt] Have any details with duplicated information been found?', (infoDuplicated ? 'Yes' : 'No'));
  5480.  
  5481. if (g_check_detail_A !== t) return;
  5482.  
  5483. return { ok: 1, infoDuplicated }; // other than 5, duplicated check = false
  5484.  
  5485. };
  5486.  
  5487. const removeDuplicateInfoFn = (b) => {
  5488.  
  5489. async function alCheckFn(ks) {
  5490.  
  5491. let alCheckInterval = 270;
  5492.  
  5493. let descExpandState = null;
  5494. const descToggleBtnSelector = '#collapse[role="button"]:not([hidden]), #expand[role="button"]:not([hidden])';
  5495. const descMetaExpander = closestDOMX(document.querySelector('ytd-watch-metadata ytd-text-inline-expander#description-inline-expander'), 'ytd-watch-metadata');
  5496. const descMetaLines = querySelectorFromAnchorX(descMetaExpander, 'ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata'); // ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata
  5497. const descToggleBtnA = querySelectorFromAnchorX(descMetaLines, descToggleBtnSelector);
  5498.  
  5499. if (descToggleBtnA) {
  5500. if (descMetaExpander.hasAttribute('description-collapsed') && descToggleBtnA.id === 'expand') {
  5501. descExpandState = false;
  5502. } else if (!descMetaExpander.hasAttribute('description-collapsed') && descToggleBtnA.id === 'collapse') {
  5503. descExpandState = true;
  5504. }
  5505. }
  5506. if (descMetaExpander) {
  5507. descMetaExpander.classList.add('tyt-tmp-hide-metainfo');
  5508. }
  5509.  
  5510. try {
  5511.  
  5512. for (let alCheckCount = 2; alCheckCount-- > 0;) {
  5513. // await removeOldExpanders();
  5514. // await removeContentMismatch();
  5515. await getRAFPromise().then();
  5516.  
  5517. if (renderIdentifier.valueOf() !== ks) break;
  5518.  
  5519. const res = await checkDuplicatedInfo(); //async
  5520.  
  5521. if (res && res.ok) {
  5522. const infoDuplicated = res.infoDuplicated;
  5523. const isFinalResult = infoDuplicated || alCheckCount === 0;
  5524. if (isFinalResult) {
  5525. const ytdFlexyElm = es.ytdFlexy;
  5526. if (ytdFlexyElm) {
  5527. ytdFlexyElm.classList.toggle('tabview-info-duplicated', infoDuplicated)
  5528. ytdFlexyElm.classList.toggle('tabview-info-duplicated-checked', true)
  5529. checkDuplicatedInfo_then();
  5530. sendToPageScript(document, 'tabview-fix-info-box-tooltip');
  5531. }
  5532. break;
  5533. }
  5534. }
  5535.  
  5536. await new Promise(r => setTimeout(r, alCheckInterval));
  5537.  
  5538. }
  5539.  
  5540. await Promise.resolve(0);
  5541.  
  5542. const descToggleBtnB = typeof descExpandState === 'boolean' ? querySelectorFromAnchorX(descMetaLines, descToggleBtnSelector) : null;
  5543. if (descToggleBtnB) {
  5544.  
  5545. const isCollapsed = descMetaExpander.hasAttribute('description-collapsed');
  5546. const id = descToggleBtnB.id;
  5547. const b = descExpandState ? (isCollapsed && id === 'expand') : (!isCollapsed && id === 'collapse');
  5548.  
  5549. if (b) {
  5550. descToggleBtnB.click();
  5551. }
  5552.  
  5553. }
  5554.  
  5555. } catch (e) {
  5556.  
  5557. console.warn(e)
  5558.  
  5559. }
  5560.  
  5561. if (descMetaExpander) {
  5562. descMetaExpander.classList.remove('tyt-tmp-hide-metainfo');
  5563.  
  5564. await Promise.resolve(0)
  5565.  
  5566. const detailsIntersectioner = _querySelector.call(descMetaExpander, '#info-container.style-scope.ytd-watch-metadata') || _querySelector.call(descMetaExpander, '#ytd-watch-info-text.style-scope.ytd-watch-metadata') || null;
  5567. if (detailsIntersectioner) {
  5568. Promise.resolve(detailsIntersectioner).then(dom => {
  5569. if (dom) mtoObservationDetails.bindElement(dom);
  5570. })
  5571. }
  5572.  
  5573. }
  5574.  
  5575. }
  5576. let ks = renderIdentifier.valueOf();
  5577. renderDeferred.debounce(() => {
  5578. if (ks !== renderIdentifier.valueOf()) return
  5579. if (document.fullscreenElement) return;
  5580. if (!b) {
  5581. if (mvideoState & 1) return;
  5582. mvideoState |= 1;
  5583. }
  5584. alCheckFn(ks);
  5585.  
  5586. });
  5587. }
  5588.  
  5589. return { removeDuplicateInfoFn, setHiddenStateForDesc };
  5590.  
  5591. })();
  5592.  
  5593. let udm = 0;
  5594. async function deferredDuplicatedMetaCheckerFn(b) {
  5595.  
  5596. udm = 0;
  5597. await scriptletDeferred.d();
  5598. if (REMOVE_DUPLICATE_META_RECOMMENDATION) {
  5599. checkDuplicatedMetaRecommendation();
  5600. }
  5601. waitForContentReady = new PromiseExternal();
  5602. isContentDuplicationCheckAllow() ? waitForContentReady.resolve() : (await waitForContentReady.then());
  5603. await removeContentMismatch(); // play safe
  5604. removeDuplicateInfoFn(b);
  5605. udm = 1;
  5606. }
  5607.  
  5608. function addVisibilityCheckToChatroom(liveChatFrame) {
  5609.  
  5610. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  5611.  
  5612. if (!(liveChatFrame instanceof Element)) return;
  5613.  
  5614. const ytdFlexyElm = es.ytdFlexy;
  5615. if (scriptEnable && ytdFlexyElm && mtoVisibility_Chatroom.bindElement(liveChatFrame)) {
  5616. mtoVisibility_Chatroom.observer.check(9)
  5617. }
  5618.  
  5619. }
  5620.  
  5621. // setupChatFrameDOM (v1) - removed in 2023.07.06 since it is buggy for page changing. subject to further review
  5622. function setupChatFrameDOM(node) {
  5623. // this function calls 3 times per each new video page
  5624.  
  5625. // 'tyt-chat' is initialized in setupChatFrameDisplayState1()
  5626.  
  5627. const liveChatFrame = node || document.querySelector('ytd-live-chat-frame#chat');
  5628. if (!liveChatFrame) return;
  5629. addVisibilityCheckToChatroom(liveChatFrame);
  5630.  
  5631. // there can be liveChatFrame but without chatroomDetails if Chat is disabled.
  5632. // eg https://www.youtube.com/watch?v=f8tIZpZ3hG0
  5633.  
  5634. if (!chatroomDetails) return;
  5635.  
  5636. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  5637.  
  5638. setToggleBtnTxt(); // immediate update when page changed
  5639.  
  5640. if (node !== null) {
  5641. // button might not yet be rendered
  5642. getRAFPromise().then(setToggleBtnTxt); // bool = true must be front page
  5643. } else {
  5644.  
  5645. // this is due to page change
  5646. layoutStatusMutex.lockWith(unlock => {
  5647. if (!document.fullscreenElement) fixTheaterChat1A();
  5648. getDMPromise().then(unlock);
  5649. });
  5650.  
  5651. }
  5652.  
  5653. }
  5654.  
  5655. function whenEngagemenetPanelVisible() {
  5656.  
  5657. const layoutStatus = wls.layoutStatus;
  5658. if ((layoutStatus & (LAYOUT_TWO_COLUMNS | LAYOUT_THEATER)) === LAYOUT_TWO_COLUMNS) {
  5659.  
  5660. if (layoutStatus & LAYOUT_TAB_EXPANDED) {
  5661. switchTabActivity(null);
  5662. return true;
  5663. } else if (layoutStatus & LAYOUT_CHATROOM_EXPANDED) {
  5664. ytBtnCollapseChat();
  5665. return true;
  5666. }
  5667.  
  5668. }
  5669.  
  5670. return false;
  5671.  
  5672. }
  5673.  
  5674.  
  5675. function removeFocusOnLeave(evt) {
  5676. let node = (evt || 0).target || 0
  5677. let activeElement = document.activeElement || 0
  5678. if (node.nodeType === 1 && activeElement.nodeType === 1) {
  5679. Promise.resolve().then(() => {
  5680. if (node.contains(activeElement)) {
  5681. activeElement.blur();
  5682. }
  5683. })
  5684. }
  5685. }
  5686.  
  5687. async function setupMedia(node) {
  5688. // this can be fired even in background without tabs rendered
  5689. const attrKey = 'gM7Cp'
  5690. let media = _querySelector.call(node, `#movie_player video[src]:not([${attrKey}])`);
  5691. media = media || _querySelector.call(node, `#movie_player audio.video-stream.html5-main-video[src]:not([${attrKey}])`);
  5692. if (media) {
  5693. media.setAttribute(attrKey, '')
  5694.  
  5695. media.addEventListener('timeupdate', (evt) => {
  5696. energizedByMediaTimeUpdate();
  5697. }, bubblePassive);
  5698.  
  5699. media.addEventListener('ended', (evt) => {
  5700. // scrollIntoView => auto start next video
  5701. // otherwise it cannot auto paly next
  5702. if (pageType === 'watch') {
  5703. let elm = evt.target;
  5704. Promise.resolve(elm).then((elm) => {
  5705. if (pageType === 'watch') {
  5706. let scrollElm = closestDOM.call(elm, '#player') || closestDOM.call(elm, '#ytd-player') || elm;
  5707. // background applicable
  5708. scrollElm.scrollIntoView(false);
  5709. scrollElm = null
  5710. }
  5711. elm = null
  5712. });
  5713. }
  5714.  
  5715. }, bubblePassive)
  5716.  
  5717. }
  5718. }
  5719.  
  5720.  
  5721. globalHook('yt-player-updated', (evt) => {
  5722.  
  5723. const node = ((evt || 0).target) || 0
  5724.  
  5725. if (node.nodeType !== 1) return;
  5726.  
  5727. const nodeName = node.nodeName.toUpperCase();
  5728.  
  5729. // _console.log(evt.target.nodeName, 904, evt.type);
  5730.  
  5731. if (nodeName !== 'YTD-PLAYER') return
  5732.  
  5733. setupMedia(node)
  5734.  
  5735. discardableFn(() => { // might discarded since it runs earlier than tabs insertion
  5736.  
  5737. if (!scriptEnable) return
  5738.  
  5739. checkAndMakeNewCommentFetch();
  5740. pageCheck();
  5741.  
  5742. domInit_comments();
  5743. setupChatFrameDOM(null);
  5744.  
  5745. });
  5746.  
  5747.  
  5748. });
  5749.  
  5750. const appendExpander = () => {
  5751. console.log('d950')
  5752.  
  5753. const rid = `${renderIdentifier.valueOf()}`; // string type integer id
  5754.  
  5755. const targets = document.querySelectorAll(`[bsptu="${rid}"]`); // ignore all appearance in previous paging
  5756.  
  5757. console.log('d951', targets.length)
  5758. if (!targets.length) return;
  5759. for (const target of targets) {
  5760. // expect only one node; other measures for more than one node
  5761.  
  5762. const nodeName = target.nodeName.toLowerCase();
  5763.  
  5764. let dummy = nodeName === 'yt-dummy-532' ? target : null;
  5765. let expander0 = dummy ? elementMapper.get(dummy) : null;
  5766. if (expander0 && closestDOM.call(expander0, '#tab-info')) continue;
  5767. console.log('d952', target)
  5768. const expander = expander0 || (nodeName === 'ytd-expander' ? target : null);
  5769. if (expander) {
  5770.  
  5771. let qt = 0;
  5772.  
  5773. // once per $$native-info-description$$ {#meta-contents ytd-expander} detection
  5774. // append the detailed meta contents to the tab-info
  5775.  
  5776. const tabInfo = document.querySelector("#tab-info");
  5777. if (tabInfo) {
  5778. qt = 1;
  5779.  
  5780. if (!expander.hasAttribute('tyt-info-expander-content')) {
  5781. qt = 2;
  5782. expander.setAttribute('tyt-info-expander-content', '');
  5783. const dmy = document.createElement('yt-dummy-532'); // to detemine the content change by youtube engine
  5784. dmy.setAttribute('tyt-info-expander-placeholder', '');
  5785. expander.replaceWith(dmy);
  5786. elementMapper.set(expander, dmy); // interlink
  5787. elementMapper.set(dmy, expander); // interlink
  5788.  
  5789. const parentRoot = closestDOM.call(dmy, '[hidden]'); // only detect if the traditional block is hidden
  5790. if (parentRoot) {
  5791. qt = 3;
  5792. moInfoContent.observe(parentRoot.parentNode, { subtree: true, childList: true });
  5793. }
  5794.  
  5795. }
  5796. elementAppend.call(tabInfo, expander);
  5797. }
  5798.  
  5799.  
  5800.  
  5801.  
  5802. removeContentMismatch();
  5803. console.log('d953', qt)
  5804.  
  5805.  
  5806. }
  5807.  
  5808.  
  5809. }
  5810. }
  5811.  
  5812. let infoContentDS = 0;
  5813.  
  5814.  
  5815. const infoContentForTab = () => {
  5816. console.log('d945', infoContentDS)
  5817.  
  5818. if (mvideoState & 2) return;
  5819. mvideoState |= 2;
  5820. console.log('d946', infoContentDS)
  5821. document.documentElement.removeAttribute('pnzgu'); // just in case
  5822. appendExpander();
  5823. if (REMOVE_DUPLICATE_META_RECOMMENDATION) checkDuplicatedMetaRecommendation();
  5824.  
  5825. mtf_fix_details().then(() => {
  5826. setToggleInfo();
  5827. renderDeferred.debounce(() => {
  5828. if (mvideoState & 8) return;
  5829. mvideoState |= 8;
  5830. setTimeout(() => {
  5831. //dispatchWindowResize(); //try to omit
  5832. dispatchWindowResize(); //add once for safe
  5833. manualResizeT();
  5834. }, 420)
  5835. }, renderIdentifier)
  5836.  
  5837.  
  5838. let secondary = document.querySelector('#columns.ytd-watch-flexy #secondary.ytd-watch-flexy');
  5839.  
  5840. let columns = closestDOMX(secondary, '#columns.ytd-watch-flexy');
  5841.  
  5842. setupHoverSlider(secondary, columns)
  5843.  
  5844. let tabInfo = document.querySelector('#tab-info');
  5845. addTabExpander(tabInfo);
  5846.  
  5847. let tabComments = document.querySelector('#tab-comments');
  5848. addTabExpander(tabComments);
  5849.  
  5850.  
  5851. document.documentElement.setAttribute('pnzgu', '');
  5852.  
  5853. }).catch(console.warn);
  5854.  
  5855.  
  5856.  
  5857. }
  5858.  
  5859.  
  5860. function tabviewControllerFn(controllerId, val) {
  5861. val = +val;
  5862. if (val > -1 && val >= 0) {
  5863. if (controllerId === 'tabviewTabsHideController') {
  5864. hiddenTabsByUserCSS = val;
  5865.  
  5866. let btn;
  5867. btn = document.querySelector('[tyt-tab-content="#tab-info"]')
  5868. if (btn) btn.classList.toggle('tab-btn-hidden', ((hiddenTabsByUserCSS & 1) === 1));
  5869.  
  5870. btn = document.querySelector('[tyt-tab-content="#tab-comments"]')
  5871. if (btn) {
  5872. if ((hiddenTabsByUserCSS & 2) === 2) {
  5873. btn.classList.toggle('tab-btn-hidden', true);
  5874. } else {
  5875. btn.classList.toggle('tab-btn-hidden', isCommentsTabBtnHidden);
  5876. }
  5877. }
  5878. btn = document.querySelector('[tyt-tab-content="#tab-videos"]');
  5879. if (btn) btn.classList.toggle('tab-btn-hidden', ((hiddenTabsByUserCSS & 4) === 4));
  5880.  
  5881. let activeHiddenBtn = document.querySelector('[tyt-tab-content^="#"].active.tab-btn-hidden');
  5882. if (activeHiddenBtn) {
  5883. setToActiveTab();
  5884. }
  5885.  
  5886. } else if (controllerId === 'tabviewDefaultTabController') {
  5887. defaultTabByUserCSS = val;
  5888. if (setupDefaultTabBtnSetting) setupDefaultTabBtnSetting();
  5889. }
  5890. }
  5891. }
  5892.  
  5893.  
  5894. /** @type {Map<string, Function>} */
  5895. let handleDOMAppearFN = new Map();
  5896. function handleDOMAppear( /** @type {string} */ fn, /** @type { listener: (this: Document, ev: AnimationEvent ) => any } */ func) {
  5897. if (handleDOMAppearFN.size === 0) {
  5898. document.addEventListener('animationstart', (evt) => {
  5899. const animationName = evt.animationName;
  5900. if (!animationName) return;
  5901. let idx = -1;
  5902. let func = handleDOMAppearFN.get(animationName);
  5903. if (func) func(evt);
  5904. else {
  5905. let idx = animationName.indexOf('Controller-');
  5906. if (idx > 0) {
  5907. let j = idx + 'Controller'.length;
  5908. tabviewControllerFn(animationName.substring(0, j), animationName.substring(j + 1));
  5909. }
  5910. }
  5911. }, capturePassive)
  5912. } else {
  5913. if (handleDOMAppearFN.has(fn)) return;
  5914. }
  5915. handleDOMAppearFN.set(fn, func);
  5916. }
  5917.  
  5918.  
  5919.  
  5920.  
  5921.  
  5922. const moInfoContent = new MutationObserver(() => {
  5923.  
  5924. removeContentMismatch();
  5925. });
  5926.  
  5927. function ytMicroEventsInit() {
  5928.  
  5929. // _console.log(902);
  5930.  
  5931. handleDOMAppear('videosDOMAppended', function (evt) {
  5932. videosDeferred.resolve();
  5933. })
  5934.  
  5935. handleDOMAppear('liveChatFrameDOMAppended', (evt) => {
  5936. // $$ html[tyt-deferred] ytd-live-chat-frame#chat $$
  5937.  
  5938. let node = evt.target;
  5939. if (node) setupChatFrameDOM(node); // front page
  5940.  
  5941. });
  5942.  
  5943. const skIdPageRenderedEventDispatch = ControllerID();
  5944.  
  5945. handleDOMAppear('pageLoaderAnimation', (evt) => {
  5946.  
  5947. pageRendered = 2;
  5948. renderDeferred.resolve();
  5949. console.debug('[tyt] pageRendered');
  5950.  
  5951. scriptletDeferred.debounce(() => {
  5952. // document.dispatchEvent(new CustomEvent('tabview-page-rendered'));
  5953. getDMPromise().then(() => {
  5954. document.dispatchEvent(new CustomEvent("yt-watch-comments-ready")); // immediately render comments when tab is switched from background
  5955. });
  5956. }, skIdPageRenderedEventDispatch);
  5957.  
  5958. });
  5959.  
  5960.  
  5961.  
  5962. const metaContentSetup = () => {
  5963. console.log('d943', infoContentDS)
  5964. setupVideoTitleHover();
  5965. let ks = renderIdentifier.valueOf();
  5966. scriptletDeferred.debounce(() => {
  5967. console.log('d944', infoContentDS)
  5968. if (ks === renderIdentifier.valueOf()) infoContentForTab();
  5969. });
  5970. };
  5971.  
  5972. const wmId1 = ControllerID();
  5973. const wmHandler1 = (evt) => {
  5974. renderDeferred.debounce(() => {
  5975. infoContentDS |= 1;
  5976. setTimeout(() => {
  5977. if (infoContentDS === 3) {
  5978. infoContentDS |= 4;
  5979. Promise.resolve().then(metaContentSetup);
  5980. }
  5981. }, 8)
  5982. }, wmId1);
  5983. }
  5984. handleDOMAppear('watchMetaFrameReady1', wmHandler1);
  5985. handleDOMAppear('watchMetaFrameReady2', wmHandler1);
  5986.  
  5987. const wmId2 = ControllerID();
  5988. const wmHandler2 = (evt) => {
  5989. // sxmq8
  5990. const target = evt.target;
  5991. renderDeferred.debounce(() => {
  5992. infoContentDS |= 2;
  5993. target.isConnected && target.setAttribute('bsptu', renderIdentifier.valueOf());
  5994. setTimeout(() => {
  5995. if (infoContentDS === 3) {
  5996. infoContentDS |= 4;
  5997. Promise.resolve().then(metaContentSetup);
  5998. }
  5999. }, 8)
  6000. }, wmId2);
  6001. }
  6002. handleDOMAppear('watchMetaContentReady1', wmHandler2);
  6003. handleDOMAppear('watchMetaContentReady2', wmHandler2);
  6004.  
  6005. handleDOMAppear('fixInvisibleInfoBug', (evt) => evt.target.removeAttribute('hidden'));
  6006.  
  6007.  
  6008. handleDOMAppear('commentsHeaderAppended1', onCommentsReady);
  6009. handleDOMAppear('commentsHeaderAppended2', onCommentsReady);
  6010.  
  6011. handleDOMAppear('fixPlaylistLocation1', (evt) => { // used as a fallback to play safe. overall strategy to be reviewed
  6012.  
  6013. const n = evt.target;
  6014. if (closestDOM.call(n, '#tab-list')) return;
  6015.  
  6016. mtf_append_playlist(n); // the true playlist is appended to the #tab-list
  6017. checkPlaylistForInitialization();
  6018.  
  6019. });
  6020.  
  6021. const onChatFrameToggleBtnAppended = (evt) => {
  6022. // $$ html[tyt-deferred] ytd-live-chat-frame#chat>.ytd-live-chat-frame#show-hide-button $$
  6023. // $$ except html[tyt-deferred] ytd-live-chat-frame#chat>.ytd-live-chat-frame#show-hide-button:first-child $$
  6024.  
  6025. const button = evt.target;
  6026.  
  6027. if (!button || button.nodeType !== 1) return;
  6028.  
  6029. /** @type {HTMLElement | null} */
  6030. const ytdFlexyElm = es.ytdFlexy;
  6031. if (!scriptEnable || !ytdFlexyElm) return;
  6032.  
  6033. prependTo(button, nodeParent(button));
  6034.  
  6035. };
  6036.  
  6037. handleDOMAppear('chatFrameToggleBtnAppended1', onChatFrameToggleBtnAppended);
  6038. handleDOMAppear('chatFrameToggleBtnAppended2', onChatFrameToggleBtnAppended);
  6039.  
  6040.  
  6041. handleDOMAppear('epDOMAppended', async (evt) => {
  6042. try {
  6043. let node = evt.target;
  6044.  
  6045. let eps = document.querySelectorAll('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])')
  6046.  
  6047. if (eps && eps.length > 0) {
  6048.  
  6049. if (eps.length > 1) {
  6050. let p = 0;
  6051. for (const ep of eps) {
  6052. if (ep !== node) {
  6053. ytBtnCloseEngagementPanel(ep)
  6054. p++
  6055. }
  6056. }
  6057. if (p > 0) {
  6058. await Promise.resolve(0)
  6059. }
  6060. }
  6061.  
  6062. FP.mtf_attrEngagementPanel(9, node);
  6063.  
  6064. Promise.resolve().then(() => {
  6065.  
  6066. mtoVisibility_EngagementPanel.bindElement(node, {
  6067. attributes: true,
  6068. attributeFilter: ['visibility'],
  6069. attributeOldValue: true
  6070. })
  6071.  
  6072. node.removeEventListener('mouseleave', removeFocusOnLeave, false)
  6073. node.addEventListener('mouseleave', removeFocusOnLeave, false)
  6074.  
  6075. })
  6076.  
  6077. }
  6078.  
  6079.  
  6080. } catch (e) { }
  6081.  
  6082. })
  6083.  
  6084. handleDOMAppear('playlistRowDOMSelected', (evt) => {
  6085. if (!evt) return;
  6086. let target = evt.target;
  6087. if (!target) return;
  6088. let items = nodeParent(target);
  6089. if (!items || items.id !== 'items') return;
  6090. let m = /\/watch\?v=[^\&]+\&/.exec(location.href || '')
  6091. if (!m) return;
  6092. let s = m[0] + "";
  6093. if (!s || s.length <= 10) return;
  6094. let correctAnchor = _querySelector.call(items, `ytd-playlist-panel-video-renderer a[href*="${s}"]`);
  6095. if (!correctAnchor || target.contains(correctAnchor)) return;
  6096. let correctRow = closestDOM.call(correctAnchor, 'ytd-playlist-panel-video-renderer');
  6097. if (!correctRow) return;
  6098. target.removeAttribute('selected');
  6099. correctRow.setAttribute('selected', '');
  6100. });
  6101.  
  6102. let _tabviewSiderAnimated = false;
  6103.  
  6104. handleDOMAppear('tabviewSiderAnimation', (evt) => {
  6105. if (!_tabviewSiderAnimated) {
  6106. _tabviewSiderAnimated = true;
  6107. dispatchCommentRowResize();
  6108. }
  6109. })
  6110.  
  6111. handleDOMAppear('tabviewSiderAnimationNone', (evt) => {
  6112. if (_tabviewSiderAnimated) {
  6113. _tabviewSiderAnimated = false;
  6114. dispatchCommentRowResize();
  6115. }
  6116. })
  6117.  
  6118. handleDOMAppear('SearchWhileWatchAutocomplete', (evt) => { // Youtube - Search While Watching Video
  6119. let elm = evt.target;
  6120. if (elm.hasAttribute('tyt-found')) return;
  6121. elm.setAttribute('tyt-found', '');
  6122. let container = document.createElement('div');
  6123. container.id = 'suggestions-search-container';
  6124. elm.replaceWith(container);
  6125. container.appendChild(elm);
  6126. elm.addEventListener('tyt-autocomplete-sc-exist', handlerAutoCompleteExist, false);
  6127. scriptletDeferred.debounce(() => {
  6128. elm.isConnected && sendToPageScript(elm, 'tabview-fix-autocomplete');
  6129. elm = null;
  6130. });
  6131. })
  6132.  
  6133. handleDOMAppear('rydTooltipAppear', (evt) => {
  6134. document.documentElement.classList.add('return-youtube-dislike');
  6135. })
  6136.  
  6137. handleDOMAppear('oldYtIconPinAppeared', (evt) => {
  6138. /* added in May 2023 - 2023.05.19 */
  6139.  
  6140. /*
  6141. from
  6142. <svg style="pointer-events: none; display: block; width: 100%; height: 100%;" focusable="false" width="24" viewBox="0 0 24 24" height="24"><path d="M16 11V3h1V2H7v1h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2zm1 3H7v-.59l1.71-1.71.29-.29V3h6v8.41l.29.29L17 13.41V14z"></path></svg>
  6143.  
  6144. to
  6145. <svg viewBox="0 0 12 12" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope yt-icon"><path d="M8,2V1H3v1h1v3.8L3,7h2v2.5L5.5,10L6,9.5V7h2L7,5.8V2H8z M6,6H5V2h1V6z" class="style-scope yt-icon"></path></g></svg>
  6146.  
  6147. */
  6148.  
  6149. let svg = evt.target;
  6150. let p = document.createElement('template');
  6151. p.innerHTML = createHTML('<svg viewBox="0 0 12 12" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope yt-icon"><path d="M8,2V1H3v1h1v3.8L3,7h2v2.5L5.5,10L6,9.5V7h2L7,5.8V2H8z M6,6H5V2h1V6z" class="style-scope yt-icon"></path></g></svg>');
  6152. svg.replaceWith(p.content.firstChild);
  6153.  
  6154.  
  6155. })
  6156.  
  6157.  
  6158. globalHook('yt-rendererstamper-finished', (evt) => {
  6159.  
  6160. if (!scriptEnable && tabsDeferred.resolved) { return }
  6161. // might occur before initialization
  6162.  
  6163. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6164.  
  6165. let node = evt.target;
  6166. const nodeName = node.nodeName.toUpperCase();
  6167.  
  6168. if (nodeName === 'YTD-PLAYLIST-PANEL-RENDERER' || nodeName === 'YTD-COMMENTS-HEADER-RENDERER') {
  6169.  
  6170. discardableFn(() => {
  6171.  
  6172. const n = node;
  6173. node = null;
  6174.  
  6175. if (nodeName === 'YTD-PLAYLIST-PANEL-RENDERER') {
  6176. mtf_append_playlist(n); // the true playlist is appended to the #tab-list
  6177. checkPlaylistForInitialization();
  6178. } else if (nodeName === 'YTD-COMMENTS-HEADER-RENDERER') {
  6179. if ((comments_loader & 3) === 3) getFinalComments();
  6180. }
  6181.  
  6182. });
  6183.  
  6184. }
  6185.  
  6186.  
  6187.  
  6188. });
  6189.  
  6190. if (REMOVE_DUPLICATE_INFO) {
  6191.  
  6192. handleDOMAppear('deferredDuplicatedMetaChecker', () => {
  6193. deferredDuplicatedMetaCheckerFn();
  6194. });
  6195.  
  6196. }
  6197.  
  6198. globalHook('yt-page-data-updated', (evt) => {
  6199.  
  6200. if (REMOVE_DUPLICATE_INFO && udm) {
  6201.  
  6202. if (location.pathname === '/watch') deferredDuplicatedMetaCheckerFn(1);
  6203. }
  6204.  
  6205. // if (!scriptEnable && tabsDeferred.resolved) { return }
  6206. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6207.  
  6208. discardableFn(() => { // might discard if yt-navigate-finish not yet called
  6209.  
  6210. // if the page is navigated by history back-and-forth, not all engagement panels can be catched in rendering event.
  6211.  
  6212. if (!scriptEnable) return;
  6213.  
  6214. checkAndMakeNewCommentFetch();
  6215. pageCheck();
  6216. setupChatFrameDOM(null);
  6217. checkPlaylistForInitialization();
  6218.  
  6219. });
  6220.  
  6221. });
  6222.  
  6223.  
  6224.  
  6225. async function onWatchCommentsReady(b) {
  6226. await tabsInsertedPromise.then();
  6227. await scriptletDeferred.d();
  6228. checkAndMakeNewCommentFetch();
  6229.  
  6230. if (b) {
  6231. domInit_comments();
  6232. _onCommentsReady();
  6233. }
  6234.  
  6235. }
  6236.  
  6237. globalHook('yt-watch-comments-ready', (evt) => {
  6238.  
  6239. if (!scriptEnable && tabsDeferred.resolved) { return }
  6240. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6241.  
  6242. const nodeName = evt.target.nodeName.toUpperCase()
  6243.  
  6244. comments_loader = comments_loader | 2;
  6245.  
  6246. onWatchCommentsReady(nodeName === 'YTD-WATCH-FLEXY');
  6247.  
  6248. });
  6249.  
  6250. function ytPageTypeChangedAsync(detail) {
  6251. const { newPageType, oldPageType } = detail;
  6252. if (newPageType && oldPageType) {
  6253. let bool = false;
  6254. if (newPageType == 'ytd-watch-flexy') {
  6255. bool = true;
  6256. pageType = 'watch';
  6257. } else if (newPageType == 'ytd-browse') {
  6258. pageType = 'browse';
  6259. }
  6260. document.documentElement.classList.toggle('tabview-normal-player', bool);
  6261. }
  6262. }
  6263. window.addEventListener('message', (evt) => {
  6264. if (!scriptEnable && tabsDeferred.resolved) { return }
  6265. if (evt.origin === location.origin) {
  6266. const data = evt.data.tabviewData || 0;
  6267. if (data.eventType === "yt-page-type-changed") {
  6268. Promise.resolve(data.eventDetail || {}).then(ytPageTypeChangedAsync);
  6269. }
  6270. }
  6271. }, bubblePassive);
  6272.  
  6273. let isTabviewFixPopupRefitCalled = false;
  6274.  
  6275. globalHook('data-changed', (evt) => {
  6276.  
  6277. if (!scriptEnable && tabsDeferred.resolved) { return }
  6278.  
  6279. let nodeName = (((evt || 0).target || 0).nodeName || '').toUpperCase()
  6280.  
  6281. if (nodeName !== 'YTD-THUMBNAIL-OVERLAY-TOGGLE-BUTTON-RENDERER') return;
  6282.  
  6283. if (!isTabviewFixPopupRefitCalled) {
  6284. isTabviewFixPopupRefitCalled = true;
  6285. sendToPageScript(document, "tabview-fix-popup-refit");
  6286. }
  6287.  
  6288.  
  6289. });
  6290.  
  6291.  
  6292. // DEBUG_LOG && globalHook('yt-rendererstamper-finished', (evt) => {
  6293.  
  6294. // if (!scriptEnable && tabsDeferred.resolved) { return }
  6295. // // might occur before initialization
  6296.  
  6297. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6298.  
  6299. // const nodeName = evt.target.nodeName.toUpperCase();
  6300.  
  6301. // // const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER','YTD-MENU-RENDERER']
  6302. // if (S_GENERAL_RENDERERS.includes(nodeName)) {
  6303. // return;
  6304. // }
  6305.  
  6306. // // _console.log(evt.target.nodeName, 904, evt.type, evt.detail);
  6307.  
  6308. // });
  6309.  
  6310. // DEBUG_LOG && globalHook('data-changed', (evt) => {
  6311.  
  6312. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6313.  
  6314. // let nodeName = evt.target.nodeName.toUpperCase()
  6315. // // _console.log(nodeName, evt.type)
  6316.  
  6317. // if (nodeName === 'YTD-ITEM-SECTION-RENDERER' || nodeName === 'YTD-COMMENTS') {
  6318.  
  6319. // _console.log(344)
  6320.  
  6321. // }
  6322.  
  6323. // })
  6324.  
  6325. // DEBUG_LOG && globalHook('yt-navigate', (evt) => {
  6326.  
  6327. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6328. // _console.log(evt.target.nodeName, evt.type)
  6329.  
  6330. // })
  6331.  
  6332. // DEBUG_LOG && globalHook('ytd-playlist-lockup-now-playing-active', (evt) => {
  6333.  
  6334. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6335. // _console.log(evt.target.nodeName, evt.type)
  6336.  
  6337.  
  6338. // })
  6339.  
  6340. // DEBUG_LOG && globalHook('yt-service-request-completed', (evt) => {
  6341.  
  6342. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6343. // _console.log(evt.target.nodeName, evt.type)
  6344.  
  6345.  
  6346. // })
  6347.  
  6348. // DEBUG_LOG && globalHook('yt-commerce-action-done', (evt) => {
  6349.  
  6350. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6351. // _console.log(evt.target.nodeName, evt.type)
  6352.  
  6353.  
  6354. // })
  6355.  
  6356. // DEBUG_LOG && globalHook('yt-execute-service-endpoint', (evt) => {
  6357.  
  6358. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6359. // _console.log(evt.target.nodeName, evt.type)
  6360.  
  6361.  
  6362. // })
  6363.  
  6364.  
  6365. // DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  6366.  
  6367. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6368. // _console.log(evt.target.nodeName, evt.type)
  6369.  
  6370.  
  6371. // })
  6372.  
  6373.  
  6374. // DEBUG_LOG && globalHook('yt-visibility-refresh', (evt) => {
  6375.  
  6376. // if (!evt || !evt.target /*|| evt.target.nodeType !== 1*/) return;
  6377. // _console.log(evt.target.nodeName || '', evt.type)
  6378.  
  6379. // const ytdFlexyElm = es.ytdFlexy;
  6380. // _console.log(2784, evt.type, (ytdFlexyElm ? ytdFlexyElm.hasAttribute('hidden') : null), evt.detail)
  6381.  
  6382. // _console.log(evt.detail)
  6383.  
  6384.  
  6385. // })
  6386.  
  6387. // DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  6388.  
  6389. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6390. // _console.log(evt.target.nodeName, evt.type)
  6391.  
  6392.  
  6393. // })
  6394.  
  6395. // DEBUG_LOG && globalHook('app-reset-layout', (evt) => {
  6396.  
  6397. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6398. // _console.log(evt.target.nodeName, evt.type)
  6399.  
  6400.  
  6401. // })
  6402. // DEBUG_LOG && globalHook('yt-guide-close', (evt) => {
  6403.  
  6404. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6405. // _console.log(evt.target.nodeName, evt.type)
  6406.  
  6407.  
  6408. // })
  6409. // DEBUG_LOG && globalHook('yt-page-data-will-change', (evt) => {
  6410.  
  6411. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6412. // _console.log(evt.target.nodeName, evt.type)
  6413.  
  6414.  
  6415. // })
  6416.  
  6417. // DEBUG_LOG && globalHook('yt-retrieve-location', (evt) => {
  6418.  
  6419. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6420. // _console.log(evt.target.nodeName, evt.type)
  6421.  
  6422.  
  6423. // })
  6424.  
  6425. // DEBUG_LOG && globalHook('yt-refit', (evt) => {
  6426.  
  6427. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6428. // _console.log(evt.target.nodeName, evt.type)
  6429.  
  6430. // })
  6431.  
  6432. // DEBUG_LOG && globalHook('addon-attached', (evt) => {
  6433.  
  6434. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6435. // _console.log(evt.target.nodeName, evt.type)
  6436.  
  6437. // })
  6438.  
  6439. // DEBUG_LOG && globalHook('yt-live-chat-context-menu-opened', (evt) => {
  6440.  
  6441. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6442. // _console.log(evt.target.nodeName, evt.type)
  6443.  
  6444. // })
  6445.  
  6446. // DEBUG_LOG && globalHook('yt-live-chat-context-menu-closed', (evt) => {
  6447.  
  6448. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6449. // _console.log(evt.target.nodeName, evt.type)
  6450.  
  6451. // })
  6452.  
  6453. // DEBUG_LOG && globalHook('yt-commentbox-resize', (evt) => {
  6454.  
  6455. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6456. // _console.log(evt.target.nodeName, evt.type)
  6457. // })
  6458.  
  6459. // DEBUG_LOG && globalHook('yt-rich-grid-layout-refreshed', (evt) => {
  6460.  
  6461. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6462. // _console.log(2327, evt.target.nodeName, evt.type)
  6463. // })
  6464.  
  6465. // DEBUG_LOG && globalHook('animationend', (evt) => {
  6466.  
  6467. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6468. // _console.log(evt.target.nodeName, evt.type)
  6469.  
  6470.  
  6471. // })
  6472.  
  6473. // DEBUG_LOG && globalHook('yt-dismissible-item-dismissed', (evt) => {
  6474.  
  6475. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6476. // _console.log(evt.target.nodeName, evt.type)
  6477.  
  6478.  
  6479. // })
  6480.  
  6481. // DEBUG_LOG && globalHook('yt-dismissible-item-undismissed', function (evt) {
  6482.  
  6483. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6484. // _console.log(evt.target.nodeName, evt.type)
  6485.  
  6486.  
  6487. // })
  6488.  
  6489.  
  6490. // DEBUG_LOG && globalHook('yt-load-next-continuation', function (evt) {
  6491.  
  6492. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6493. // _console.log(evt.target.nodeName, evt.type)
  6494.  
  6495.  
  6496. // })
  6497.  
  6498.  
  6499. // DEBUG_LOG && globalHook('yt-load-reload-continuation', function (evt) {
  6500.  
  6501. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6502. // _console.log(evt.target.nodeName, evt.type)
  6503.  
  6504.  
  6505. // })
  6506.  
  6507. // DEBUG_LOG && globalHook('yt-toggle-button', function (evt) {
  6508.  
  6509. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6510. // _console.log(evt.target.nodeName, evt.type)
  6511.  
  6512.  
  6513. // })
  6514.  
  6515.  
  6516. }
  6517.  
  6518.  
  6519. function chatFrameContentDocument() {
  6520. // non-null if iframe exist && contentDocument && readyState = complete
  6521. /** @type {HTMLIFrameElement | null} */
  6522. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  6523. if (!iframe) return null; //iframe must be there
  6524. /** @type {Document | null} */
  6525. let cDoc = null;
  6526. try {
  6527. cDoc = iframe.contentDocument;
  6528. } catch (e) { }
  6529. if (!cDoc) return null;
  6530. if (cDoc.readyState != 'complete') return null; //we must wait for its completion
  6531.  
  6532. return cDoc;
  6533.  
  6534. }
  6535.  
  6536. function chatFrameElement(/** @type {string} */ cssSelector) {
  6537. let cDoc = chatFrameContentDocument();
  6538. if (!cDoc) return null;
  6539. /** @type {HTMLElement | null} */
  6540. let elm = null;
  6541. try {
  6542. elm = cDoc.querySelector(cssSelector)
  6543. } catch (e) {
  6544. console.log('iframe error', e)
  6545. }
  6546. return elm;
  6547. }
  6548.  
  6549. const iframeLoadControllerId = ControllerID();
  6550. let iframeLoadHookHandlerPromise = new PromiseExternal();
  6551. const iframeLoadHookHandler = function (evt) {
  6552.  
  6553. const target = (evt || 0).target;
  6554. const iframe = target instanceof HTMLIFrameElement ? target : null;
  6555. if (!iframe || iframe.id !== 'chatframe') return;
  6556.  
  6557. iframePipeline(async () => {
  6558. await iframeLoadHookHandlerPromise.then();
  6559. if (iframe.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')) {
  6560. await iframeLoadProcess(iframe);
  6561. }
  6562. })
  6563. };
  6564.  
  6565. document.addEventListener('load', iframeLoadHookHandler, capturePassive);
  6566.  
  6567. // const onIframeSrcReplaced = function (evt) {
  6568. // const isIframe = (((evt || 0).target || 0).nodeName === 'IFRAME');
  6569. // const iframe = isIframe ? evt.target : null;
  6570. // if (!iframe || !iframe.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')) {
  6571. // return;
  6572. // }
  6573. // Promise.resolve(iframe).then(iframeLoadProcess); // in order to set ready
  6574. // };
  6575.  
  6576. let ix93 = 0;
  6577. const iframeLoadStatusWM = new WeakMap;
  6578. // fresh load [determine body empty state]
  6579. const iframeLoadProcess = async function (_iframe) {
  6580.  
  6581. const iframe = _iframe;
  6582.  
  6583. if (ix93 > 1e9) ix93 = 9;
  6584. const t93 = ++ix93;
  6585. iframeLoadStatusWM.set(iframe, t93 * 2);
  6586.  
  6587. await scriptletDeferred.d();
  6588. // iframe.dispatchEvent(new CustomEvent('yt-live-chat-iframe-fix-polymer'));
  6589. await tabsInsertedPromise.then();
  6590. if (t93 !== ix93) return;
  6591. await getRAFPromise().then();
  6592. if (t93 !== ix93) return;
  6593. await renderDeferred.d();
  6594.  
  6595. if (t93 !== ix93) return;
  6596.  
  6597.  
  6598. if (iframe.isConnected !== true) return;
  6599.  
  6600. // console.log("iframe.xx",1235, iframe)
  6601.  
  6602. const chat = closestDOM.call(iframe, 'ytd-live-chat-frame#chat');
  6603.  
  6604. // console.log('chat', chat)
  6605. if (!chat) return;
  6606.  
  6607. // console.log("iframe.xx",1236, chat)
  6608.  
  6609. if (chat.hasAttribute('collapsed')) return;
  6610. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 10');
  6611.  
  6612. const tid = iframeLoadControllerId.inc();
  6613.  
  6614. // console.log("iframe.xx",1237, chat)
  6615.  
  6616.  
  6617. const cDoc = await new Promise((resolve) => {
  6618.  
  6619. let timeoutCount = 250;
  6620. let cid = 0;
  6621. const tf = () => {
  6622. let exitLoop;
  6623. if (tid !== iframeLoadControllerId.valueOf() || --timeoutCount < 0) {
  6624. exitLoop = true;
  6625. } else if (chat.isConnected === true && iframe.isConnected === true && !chat.hasAttribute('collapsed')) {
  6626. exitLoop = false;
  6627. } else {
  6628. exitLoop = true;
  6629. }
  6630.  
  6631. let cDoc = null;
  6632. if (!exitLoop) {
  6633.  
  6634. if (HTMLElement.prototype.closest.call(iframe, '[hidden]')) return;
  6635. if (!HTMLElement.prototype.closest.call(iframe, '#columns.style-scope.ytd-watch-flexy')) return;
  6636. try {
  6637. cDoc = iframe.contentDocument;
  6638. } catch (e) {
  6639. }
  6640. if (!cDoc) return;
  6641. // if (cDoc.readyState === 'loading') return;
  6642. if (!cDoc.body) return;
  6643.  
  6644. }
  6645.  
  6646. resolve && resolve(cDoc);
  6647. resolve = null;
  6648. cid && clearInterval(cid);
  6649. cid = 0;
  6650.  
  6651. }
  6652. tf();
  6653. if (resolve !== null) {
  6654. cid = setInterval(tf, 17);
  6655. }
  6656.  
  6657.  
  6658. }).catch(console.warn);
  6659.  
  6660. if (t93 !== ix93) return;
  6661.  
  6662. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 11');
  6663.  
  6664. if (tid !== iframeLoadControllerId.valueOf() || !cDoc) {
  6665. return;
  6666. }
  6667.  
  6668. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 12');
  6669.  
  6670. if (chat.hasAttribute('collapsed') || iframe.isConnected !== true) {
  6671. return;
  6672. }
  6673.  
  6674. if (cDoc && (cDoc.readyState === 'loading' || !(cDoc.body || 0).firstElementChild)) {
  6675. await new Promise(r => setTimeout(r, 50));
  6676. }
  6677.  
  6678. if (chat.hasAttribute('collapsed') || iframe.isConnected !== true) {
  6679. return;
  6680. }
  6681.  
  6682.  
  6683. if (t93 !== ix93) return;
  6684.  
  6685. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 13');
  6686.  
  6687. // console.log("iframe.xx",1238, chat)
  6688.  
  6689. const contentElement = (cDoc.body || 0).firstElementChild;
  6690.  
  6691. if (contentElement) {
  6692.  
  6693. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 20');
  6694.  
  6695. if (!scriptEnable || !isChatExpand()) return; // v4.13.19 - scriptEnable = true in background
  6696. if (!(iframe instanceof HTMLIFrameElement) || iframe.isConnected !== true) return; //prevent iframe is detached from the page
  6697. let isCorrectDoc = false;
  6698. try {
  6699. isCorrectDoc = iframe.contentDocument === cDoc;
  6700. } catch (e) { }
  6701.  
  6702. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 21');
  6703. if (isCorrectDoc) {
  6704. let chatFrame = closestDOM.call(iframe, 'ytd-live-chat-frame#chat');
  6705. if (chatFrame) {
  6706. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 22');
  6707. // chatFrame.setAttribute('tyt-iframe-loaded', '');
  6708. sendToPageScript(chatFrame, 'tabview-chatroom-ready');
  6709. iframeLoadStatusWM.set(iframe, t93 * 2 + 1);
  6710. }
  6711. }
  6712.  
  6713. } else if (!contentElement) {
  6714.  
  6715. showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 30');
  6716.  
  6717. // e.g. when restore from mini view to watch page
  6718.  
  6719. // tabview-chat-fix-url-onload-with-empty-body
  6720.  
  6721. // iframe.removeEventListener('iframe-src-replaced', onIframeSrcReplaced, false);
  6722. // iframe.addEventListener('iframe-src-replaced', onIframeSrcReplaced, false);
  6723.  
  6724. setTimeout(() => {
  6725. if (t93 !== ix93) return;
  6726. try {
  6727. if (chat.isConnected && !chat.hasAttribute('collapsed') && chat.contains(iframe)) {
  6728. const cDoc = iframe.contentDocument;
  6729. if (cDoc) {
  6730. const cBody = cDoc.body;
  6731. if (cBody && cBody.firstElementChild === null) {
  6732. sendToPageScript(chat, "tabview-chat-fix-url-onload-with-empty-body");
  6733. }
  6734. }
  6735. }
  6736. } catch (e) {
  6737. console.warn(e);
  6738. }
  6739. }, 230);
  6740.  
  6741. }
  6742.  
  6743.  
  6744. };
  6745.  
  6746. // collapsed -> expanded [already loaded with location.replace]
  6747. const iframeToVisible = async function (_iframe) {
  6748. const iframe = _iframe;
  6749. if (iframe.isConnected !== true || !(iframe instanceof HTMLIFrameElement)) return;
  6750. if (((iframeLoadStatusWM.get(iframe) || 0) % 2) !== 1) {
  6751. return;
  6752. }
  6753. const chat = closestDOM.call(iframe, 'ytd-live-chat-frame#chat');
  6754. if (!chat) return;
  6755. if (chat.hasAttribute('collapsed') || iframe.isConnected !== true) {
  6756. return;
  6757. }
  6758. let cDoc = 0;
  6759. try {
  6760. cDoc = iframe.contentDocument;
  6761. } catch (e) { }
  6762.  
  6763. const contentElement = (cDoc.body || 0).firstElementChild;
  6764.  
  6765. if (!contentElement) return;
  6766.  
  6767. if (!scriptEnable || !isChatExpand()) return; // v4.13.19 - scriptEnable = true in background
  6768.  
  6769. sendToPageScript(chat, 'tabview-chatroom-ready');
  6770.  
  6771. };
  6772.  
  6773. async function restorePIPforStickyHead() {
  6774. // after a trusted user action, PIP can be cancelled.
  6775. // this is to ensure enterPIP can be re-excecuted
  6776.  
  6777. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled && userActivation) {
  6778. userActivation = false;
  6779. exitPIP();
  6780. }
  6781. }
  6782.  
  6783. let videosDeferred = new Deferred();
  6784.  
  6785. let _navigateLoadDT = 0;
  6786.  
  6787. function delayedClickHandler() {
  6788.  
  6789. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled) {
  6790. restorePIPforStickyHead();
  6791. } else if (!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && typeof IntersectionObserver == 'function') {
  6792. enablePIPforStickyHead();
  6793. }
  6794.  
  6795. }
  6796.  
  6797. function convertDefaultTabFromTmpToFinal(myDefaultTab_tmp) {
  6798.  
  6799. let myDefaultTab_final = null
  6800. if (myDefaultTab_tmp && typeof myDefaultTab_tmp === 'string' && /^\#[a-zA-Z\_\-\+]+$/.test(myDefaultTab_tmp)) {
  6801. if (document.querySelector(`.tab-btn[tyt-tab-content="${myDefaultTab_tmp}"]`)) myDefaultTab_final = myDefaultTab_tmp;
  6802. }
  6803.  
  6804. return myDefaultTab_final;
  6805. }
  6806.  
  6807. function getConfiguredDefaultTab(store) {
  6808. let myDefaultTab;
  6809. if (defaultTabByUserCSS === 1) {
  6810. myDefaultTab = '#tab-info';
  6811. } else if (defaultTabByUserCSS === 2) {
  6812. myDefaultTab = '#tab-comments';
  6813. } else if (defaultTabByUserCSS === 3) {
  6814. myDefaultTab = '#tab-videos';
  6815. } else {
  6816. store = store || getStore();
  6817. myDefaultTab = store[key_default_tab];
  6818. }
  6819. return myDefaultTab;
  6820. }
  6821.  
  6822. function setupTabBtns() {
  6823.  
  6824. const materialTab = document.querySelector("#material-tabs")
  6825. if (!materialTab) return;
  6826.  
  6827. if (tabsUiScript_setclick) return;
  6828. tabsUiScript_setclick = true;
  6829.  
  6830. let fontSizeBtnClick = null;
  6831.  
  6832. materialTab.addEventListener('click', async function (evt) {
  6833.  
  6834. const dom = evt.target;
  6835. if ((dom || 0).nodeType !== 1) return;
  6836.  
  6837. await getRAFPromise().then(); // prevent execution in background
  6838. await scriptletDeferred.d();
  6839.  
  6840. if (dom.isConnected !== true) return;
  6841.  
  6842. let b = isMiniviewForStickyHeadEnabled || (isStickyHeaderEnabled && typeof IntersectionObserver == 'function');
  6843. if (b) {
  6844. getDMPromise().then(delayedClickHandler);
  6845. }
  6846.  
  6847. const domInteraction = dom.getAttribute('tyt-di');
  6848. if (domInteraction === 'q9Kjc') {
  6849. handlerMaterialTabClick.call(dom, evt);
  6850. } else if (domInteraction === '8rdLQ') {
  6851. fontSizeBtnClick && fontSizeBtnClick.call(dom, evt);
  6852. }
  6853.  
  6854. }, true);
  6855.  
  6856. function updateCSS_fontsize(store) {
  6857.  
  6858. if (!store) return;
  6859.  
  6860. const ytdFlexyElm = es.ytdFlexy;
  6861. if (ytdFlexyElm) {
  6862. if (store['font-size-#tab-info']) ytdFlexyElm.style.setProperty('--ut2257-info', store['font-size-#tab-info'])
  6863. if (store['font-size-#tab-comments']) ytdFlexyElm.style.setProperty('--ut2257-comments', store['font-size-#tab-comments'])
  6864. if (store['font-size-#tab-videos']) ytdFlexyElm.style.setProperty('--ut2257-videos', store['font-size-#tab-videos'])
  6865. if (store['font-size-#tab-list']) ytdFlexyElm.style.setProperty('--ut2257-list', store['font-size-#tab-list'])
  6866. sendToPageScript(document, "tabview-zoom-updated");
  6867. }
  6868.  
  6869. }
  6870.  
  6871. fontSizeBtnClick = function (evt) {
  6872.  
  6873. evt.preventDefault();
  6874. evt.stopPropagation();
  6875. evt.stopImmediatePropagation();
  6876.  
  6877. /** @type {HTMLElement | null} */
  6878. let dom = evt.target;
  6879. if (!dom) return;
  6880.  
  6881. let value = dom.classList.contains('font-size-plus') ? 1 : dom.classList.contains('font-size-minus') ? -1 : 0;
  6882.  
  6883. let active_tab_content = closestDOM.call(dom, '[tyt-tab-content]').getAttribute('tyt-tab-content');
  6884.  
  6885. let store = getStore();
  6886. let settingKey = `font-size-${active_tab_content}`
  6887. if (!store[settingKey]) store[settingKey] = 1.0;
  6888. if (value < 0) store[settingKey] -= 0.05;
  6889. else if (value > 0) store[settingKey] += 0.05;
  6890. if (store[settingKey] < 0.1) store[settingKey] = 0.1;
  6891. else if (store[settingKey] > 10) store[settingKey] = 10.0;
  6892. setStore(store);
  6893.  
  6894. store = getStore();
  6895. updateCSS_fontsize(store);
  6896.  
  6897. }
  6898.  
  6899. function loadDefaultTabBtnSettingToMem(store) {
  6900. let myDefaultTab = getConfiguredDefaultTab(store);
  6901. myDefaultTab = myDefaultTab ? convertDefaultTabFromTmpToFinal(myDefaultTab) : null;
  6902. if (myDefaultTab) {
  6903. settings.defaultTab = myDefaultTab;
  6904. }
  6905. }
  6906.  
  6907. let store = getStore();
  6908. updateCSS_fontsize(store);
  6909. loadDefaultTabBtnSettingToMem(store);
  6910. setupDefaultTabBtnSetting = function () {
  6911. let myDefaultTab = getConfiguredDefaultTab();
  6912. myDefaultTab = myDefaultTab ? convertDefaultTabFromTmpToFinal(myDefaultTab) : null;
  6913. if (myDefaultTab) {
  6914. settings.defaultTab = myDefaultTab;
  6915. } else {
  6916. settings.defaultTab = SETTING_DEFAULT_TAB_0;
  6917. }
  6918. }
  6919.  
  6920. }
  6921.  
  6922. function setMyDefaultTab(myDefaultTab) {
  6923. myDefaultTab = convertDefaultTabFromTmpToFinal(myDefaultTab);
  6924. let store = getStore();
  6925. if (myDefaultTab) {
  6926. store[key_default_tab] = myDefaultTab;
  6927. settings.defaultTab = myDefaultTab;
  6928. } else {
  6929. delete store[key_default_tab];
  6930. settings.defaultTab = SETTING_DEFAULT_TAB_0;
  6931. }
  6932. setStore(store);
  6933. }
  6934.  
  6935. document.addEventListener('tabview-setMyDefaultTab', function (evt) {
  6936.  
  6937. let myDefaultTab_tmp = ((evt || 0).detail || 0).myDefaultTab
  6938.  
  6939. setMyDefaultTab(myDefaultTab_tmp)
  6940.  
  6941. }, false)
  6942.  
  6943. function loadFrameHandler(evt) {
  6944. let target = (evt || 0).target;
  6945. if (target instanceof HTMLIFrameElement && target.id === 'chatframe') {
  6946. fixLiveChatToggleButtonDispatchEvent();
  6947. }
  6948. }
  6949. async function onNavigationEndAsync() {
  6950.  
  6951. await scriptletDeferred.d();
  6952.  
  6953. if (pageType !== 'watch') return;
  6954.  
  6955. Promise.resolve().then(setupVideoTitleHover);
  6956.  
  6957. // infoContentSetup();
  6958.  
  6959. let tdt = Date.now();
  6960. _navigateLoadDT = tdt;
  6961.  
  6962. // avoid blocking the page when youtube is initializing the page
  6963. const promiseDelay = getRAFPromise().then();
  6964. const promiseVideoRendered = videosDeferred.d();
  6965.  
  6966. const verifyPageState = () => {
  6967. if (_navigateLoadDT !== tdt) {
  6968. return -400;
  6969. }
  6970. if (ytEventSequence !== 3) {
  6971. return -200;
  6972. }
  6973.  
  6974. const ytdFlexyElm = document.querySelector('ytd-watch-flexy:not([hidden])') || document.querySelector('ytd-watch-flexy');
  6975.  
  6976. if (!ytdFlexyElm) {
  6977. ytdFlexy = null;
  6978. return -100;
  6979. } else {
  6980. ytdFlexy = mWeakRef(ytdFlexyElm);
  6981. return 0;
  6982. }
  6983.  
  6984.  
  6985. }
  6986.  
  6987. let pgState = verifyPageState();
  6988. if (pgState < 0) return;
  6989.  
  6990. let scriptEnableTemp = scriptEnable;
  6991. scriptEnable = true; // avoid locking the other parts of script (see v4.13.19)
  6992. await Promise.all([promiseVideoRendered, promiseDelay]);
  6993. pgState = verifyPageState();
  6994. if (pgState < 0) {
  6995. scriptEnable = scriptEnableTemp;
  6996. if (!ytdFlexy) scriptEnable = false;
  6997. return;
  6998. }
  6999. pgState = null;
  7000. const ytdFlexyElm = kRef(ytdFlexy);
  7001.  
  7002. if (!ytdFlexyElm) {
  7003. ytdFlexy = null;
  7004. scriptEnable = false;
  7005. return;
  7006. }
  7007.  
  7008. fixLiveChatToggleButtonDispatchEvent();
  7009. document.removeEventListener('load', loadFrameHandler, true);
  7010. document.addEventListener('load', loadFrameHandler, true);
  7011.  
  7012. const related = _querySelector.call(ytdFlexyElm, "#related.ytd-watch-flexy");
  7013. if (!related) return;
  7014.  
  7015.  
  7016. if (!document.querySelector("#right-tabs")) {
  7017. getLangForPage();
  7018. let docTmp = document.createElement('template');
  7019. docTmp.innerHTML = createHTML(getTabsHTML());
  7020. let newElm = docTmp.content.firstElementChild;
  7021. if (newElm !== null) {
  7022. insertBeforeTo(newElm, related);
  7023. _querySelector.call(newElm, '#material-tabs').addEventListener('mousemove', (evt) => {
  7024. evt.preventDefault();
  7025. evt.stopPropagation();
  7026. evt.stopImmediatePropagation();
  7027. }, true);
  7028. setupTabBtns();
  7029. console.debug('[tyt] #right-tabs inserted')
  7030. }
  7031. docTmp.textContent = '';
  7032. docTmp = null;
  7033. }
  7034.  
  7035. if (!ytdFlexyElm.hasAttribute('tyt-tab')) ytdFlexyElm.setAttribute('tyt-tab', '')
  7036.  
  7037. // append the next videos
  7038. // it exists as "related" is already here
  7039. fixTabs();
  7040.  
  7041. let switchToDefaultTabNotAllowed = false;
  7042.  
  7043. if (document.querySelector('ytd-watch-flexy[tyt-chat^="+"]')) {
  7044. switchToDefaultTabNotAllowed = true;
  7045. } 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])')) {
  7046. switchToDefaultTabNotAllowed = true;
  7047. } else if (document.querySelector('ytd-watch-flexy ytd-donation-shelf-renderer.ytd-watch-flexy:not([hidden]):not(:empty)')) {
  7048. switchToDefaultTabNotAllowed = true;
  7049. }
  7050.  
  7051. if (switchToDefaultTabNotAllowed) {
  7052. switchTabActivity(null);
  7053. } else {
  7054. setToActiveTab(); // just switch to the default tab
  7055. }
  7056.  
  7057.  
  7058. mtoFlexyAttr.clear(true)
  7059. mtoBodyAttr.clear(true)
  7060. mtf_checkFlexy()
  7061.  
  7062. document.documentElement.setAttribute('tyt-deferred', '')
  7063. tabsDeferred.resolve();
  7064. tabsInsertedPromise.resolve();
  7065. FP.mtf_attrEngagementPanel(); // check whether no visible panels
  7066.  
  7067.  
  7068. }
  7069.  
  7070.  
  7071. function fetchCommentsFinished() {
  7072. const ytdFlexyElm = es.ytdFlexy;
  7073. if (!scriptEnable || !ytdFlexyElm) return;
  7074. if (mtf_forceCheckLiveVideo_disable === 2) return;
  7075. ytdFlexyElm.setAttribute('tyt-comments', 'L');
  7076. _console.log(2909, 1)
  7077. }
  7078.  
  7079. function setCommentSection( /** @type {number} */ value) {
  7080.  
  7081. Q.comments_section_loaded = value;
  7082. if (value === 0 && fetchCounts) {
  7083. fetchCounts.fetched = false; // unknown bug
  7084. }
  7085.  
  7086. }
  7087.  
  7088.  
  7089. function emptyCommentSection() {
  7090. let tab_btn = document.querySelector('.tab-btn[tyt-tab-content="#tab-comments"]')
  7091. if (tab_btn) {
  7092. let span = _querySelector.call(tab_btn, 'span#tyt-cm-count');
  7093. tab_btn.removeAttribute('loaded-comment')
  7094. if (span) {
  7095. span.textContent = '';
  7096. }
  7097. }
  7098. setCommentSection(0);
  7099. _console.log(7233, 'comments_section_loaded = 0')
  7100. }
  7101.  
  7102.  
  7103. function _disableComments() {
  7104.  
  7105.  
  7106. _console.log(2909, 1)
  7107. if (!scriptEnable) return;
  7108. let cssElm = es.ytdFlexy;
  7109. if (!cssElm) return;
  7110.  
  7111. _console.log(2909, 2)
  7112.  
  7113.  
  7114. let comments = document.querySelector('ytd-comments#comments')
  7115. if (mtf_forceCheckLiveVideo_disable === 2) {
  7116. // earlier than DOM change
  7117. } else {
  7118. if (comments && !comments.hasAttribute('hidden')) return; // visible comments content)
  7119. }
  7120.  
  7121. _console.log(2909, 4)
  7122. if (Q.comments_section_loaded === 2) return; //already disabled
  7123.  
  7124. setCommentSection(2);
  7125.  
  7126. _console.log(2909, 5)
  7127.  
  7128. let tabBtn = document.querySelector('.tab-btn[tyt-tab-content="#tab-comments"]');
  7129. if (tabBtn) {
  7130. let span = _querySelector.call(tabBtn, 'span#tyt-cm-count');
  7131. tabBtn.removeAttribute('loaded-comment')
  7132. if (!tabBtn.classList.contains('tab-btn-hidden')) {
  7133. //console.log('hide', comments, comments && comments.hasAttribute('hidden'))
  7134. hideTabBtn(tabBtn)
  7135. }
  7136. if (span) {
  7137. span.textContent = '';
  7138. }
  7139. }
  7140.  
  7141. cssElm.setAttribute('tyt-comments', 'D');
  7142.  
  7143. _console.log(2909, 10)
  7144.  
  7145.  
  7146. }
  7147.  
  7148. function setToggleInfo() {
  7149. scriptletDeferred.d().then(() => {
  7150. const elem = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #info-container.ytd-watch-metadata:first-child') || document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #ytd-watch-info-text.ytd-watch-metadata:first-child') || null;
  7151. if (elem && !elem.hasAttribute('tyt-info-toggler')) {
  7152. elem.setAttribute('tyt-info-toggler', '');
  7153. sendToPageScript(elem, 'tyt-info-toggler');
  7154. }
  7155. });
  7156. }
  7157.  
  7158. function flexyAttr_toggleFlag(mFlag, b, flag) {
  7159. return b ? (mFlag | flag) : (mFlag & ~flag);
  7160. }
  7161.  
  7162. function flexAttr_toLayoutStatus(nls, attributeName) {
  7163.  
  7164. let attrElm, b, v;
  7165. switch (attributeName) {
  7166. case 'theater':
  7167. b = isTheater();
  7168. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_THEATER);
  7169. break;
  7170. case 'tyt-chat':
  7171. attrElm = es.ytdFlexy;
  7172. v = attrElm.getAttribute('tyt-chat');
  7173.  
  7174. if (v !== null && v.charAt(0) === '-') {
  7175. nls = flexyAttr_toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED);
  7176. } else {
  7177. nls = flexyAttr_toggleFlag(nls, v !== null, LAYOUT_CHATROOM);
  7178. nls = flexyAttr_toggleFlag(nls, false, LAYOUT_CHATROOM_COLLAPSED);
  7179. }
  7180.  
  7181. break;
  7182. case 'is-two-columns_':
  7183. b = isWideScreenWithTwoColumns();
  7184. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TWO_COLUMNS);
  7185. break;
  7186.  
  7187. case 'tyt-tab':
  7188. attrElm = es.ytdFlexy;
  7189. b = isNonEmptyString(attrElm.getAttribute('tyt-tab'));
  7190. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  7191. break;
  7192.  
  7193. case 'fullscreen':
  7194. attrElm = es.ytdFlexy;
  7195. b = attrElm.hasAttribute('fullscreen');
  7196. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  7197. break;
  7198.  
  7199. case 'tyt-ep-visible':
  7200. attrElm = es.ytdFlexy;
  7201. v = attrElm.getAttribute('tyt-ep-visible');
  7202. b = (+v > 0)
  7203. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPANDED);
  7204. break;
  7205.  
  7206. case 'tyt-donation-shelf':
  7207. attrElm = es.ytdFlexy;
  7208. b = attrElm.hasAttribute('tyt-donation-shelf');
  7209. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_DONATION_SHELF_EXPANDED);
  7210. break;
  7211.  
  7212. }
  7213.  
  7214. return nls;
  7215.  
  7216.  
  7217. }
  7218.  
  7219.  
  7220.  
  7221. function ito_details(entries, observer) {
  7222. if (!detailsTriggerReset) return;
  7223. if (!entries || entries.length !== 1) return; // unlikely
  7224. let entry = entries[0];
  7225. //console.log(entries)
  7226. if (entry.isIntersecting === true) {
  7227.  
  7228. if (fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS | LAYOUT_FULLSCREEN, 0) === false) return;
  7229.  
  7230. let dom = entry.target;
  7231. if (!dom) return; //unlikely
  7232.  
  7233. let bool = false;
  7234. let descClickable = null;
  7235.  
  7236. if (fT(wls.layoutStatus, 0, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_TAB_EXPANDED) === false) {
  7237. descClickable = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata')
  7238. if (descClickable) {
  7239. detailsTriggerReset = false;
  7240. bool = true;
  7241. }
  7242. }
  7243.  
  7244. async function runAsync(dom, bool) {
  7245.  
  7246. if (bool) {
  7247. let descClickable = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata')
  7248. if (descClickable) {
  7249. descClickable.click();
  7250. }
  7251. }
  7252.  
  7253. await getDMPromise();
  7254.  
  7255. let pInner, nw = null;
  7256. try {
  7257. let x = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata');
  7258. let h2 = x.offsetHeight;
  7259. pInner = closestDOM.call(x, '#primary-inner')
  7260. let h1 = pInner.offsetHeight;
  7261. x.setAttribute('userscript-scrollbar-render', '')
  7262. if (h1 > h2 && h2 > 0 && h1 > 0) nw = h1 - h2
  7263. } catch (e) { }
  7264. if (pInner) {
  7265. pInner.style.setProperty('--tyt-desc-top-h', `${nw ? nw : 0}px`)
  7266. }
  7267. }
  7268.  
  7269. runAsync(dom, bool);
  7270.  
  7271.  
  7272. }
  7273.  
  7274. }
  7275.  
  7276. function immediateCheck() {
  7277.  
  7278.  
  7279. if (!scriptEnable) return;
  7280.  
  7281. if (fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_THEATER | LAYOUT_FULLSCREEN | LAYOUT_CHATROOM_EXPANDED)) {
  7282. setToActiveTab();
  7283. }
  7284.  
  7285. }
  7286.  
  7287. // let _ctlPromise = null;
  7288.  
  7289. // const getCTLPromise = () => {
  7290.  
  7291. // if(!_ctlPromise) _ctlPromise = new PromiseExternal();
  7292.  
  7293.  
  7294. // const ytdFlexyElm = es.ytdFlexy;
  7295. // if (!scriptEnable || !ytdFlexyElm || !wls.layoutStatus) {
  7296. // _ctlPromise.resolve();
  7297. // }else{
  7298. // ytdFlexyElm.setAttribute('tyt-ctl', ytdFlexyElm.getAttribute('tyt-ctl') === '1' ? '0' : '1')
  7299. // }
  7300.  
  7301. // return _ctlPromise;
  7302.  
  7303. // };
  7304.  
  7305.  
  7306. // const triggerCTL = ()=>{
  7307. // _ctlPromise && _ctlPromise.resolve();
  7308. // _ctlPromise = null
  7309. // }
  7310.  
  7311. let updateFlexyStateRequired = false;
  7312. const updateFlexyStateFn = () => {
  7313. updateFlexyStateRequired = false;
  7314. es.ytdFlexy && layoutStatusMutex.lockWith(unlock => {
  7315. const ytdFlexyElm = es.ytdFlexy;
  7316. if (!ytdFlexyElm) return;
  7317. const isFlexyHidden = ytdFlexyElm.hasAttribute('hidden');
  7318. if (isFlexyHidden) return;
  7319. setChatroomState();
  7320. mtf_checkFlexy_(LAYOUT_VAILD);
  7321. unlock();
  7322. });
  7323. }
  7324. const mtf_attrFlexy = (mutations, observer) => {
  7325.  
  7326. // if(document.documentElement.hasAttribute('p355')) return;
  7327.  
  7328.  
  7329. //attr mutation checker - $$ytdFlexyElm$$ {ytd-watch-flexy} \single
  7330. //::attr
  7331. // ~ 'tyt-chat', 'theater', 'is-two-columns_',
  7332. // ~ 'tyt-tab', 'fullscreen', 'tyt-ep-visible',
  7333. // ~ 'hidden', 'is-extra-wide-video_'
  7334.  
  7335. //console.log(15330, scriptEnable, es.ytdFlexy, mutations)
  7336.  
  7337.  
  7338. if (mutations && mutations.length >= 1) {
  7339. let b = 0;
  7340. for (const mutation of mutations) {
  7341. if (mutation.attributeName === 'hidden') {
  7342. b = 1;
  7343. break;
  7344. }
  7345. }
  7346. if (b) {
  7347. updateFlexyStateRequired = true;
  7348. }
  7349. }
  7350.  
  7351. if (!scriptEnable) return;
  7352.  
  7353. const cssElm = es.ytdFlexy;
  7354. if (!cssElm) return;
  7355.  
  7356. if (!mutations) return;
  7357.  
  7358.  
  7359. const old_layoutStatus = wls.layoutStatus
  7360. if (old_layoutStatus === 0) return;
  7361. let new_layoutStatus = old_layoutStatus;
  7362.  
  7363. let checkedChat = false;
  7364. let mss = 0;
  7365. let dcall = 0;
  7366. // let resolveCTL = false;
  7367.  
  7368. check1885();
  7369. if (!chatController.allowChatControl) {
  7370.  
  7371. // for (const mutation of mutations) {
  7372.  
  7373. // if(mutation.attributeName === 'tyt-ctl') {
  7374. // resolveCTL = true;
  7375. // continue;
  7376. // }
  7377. // }
  7378. // if(resolveCTL) triggerCTL();
  7379. return;
  7380. }
  7381.  
  7382. for (const mutation of mutations) {
  7383.  
  7384. // if(mutation.attributeName === 'tyt-ctl') {
  7385. // resolveCTL = true;
  7386. // continue;
  7387. // }
  7388.  
  7389. // if(1885 && document.body.hasAttribute('data-ytlstm-theater-mode')) continue;
  7390. new_layoutStatus = flexAttr_toLayoutStatus(new_layoutStatus, mutation.attributeName);
  7391. // _console.log(8221, 18, mutation.attributeName)
  7392. if (mutation.attributeName === 'tyt-chat') {
  7393.  
  7394. if (!checkedChat) {
  7395. checkedChat = true; // avoid double call
  7396.  
  7397. if ((cssElm.getAttribute('tyt-chat') || '').indexOf('chat$live') >= 0) {
  7398. // assigned new attribute - "chat$live" => disable comments section
  7399.  
  7400. //console.log(3712,2)
  7401. _disableComments();
  7402. }
  7403.  
  7404. if (!cssElm.hasAttribute('tyt-chat')) {
  7405. // might or might not collapsed before
  7406. dcall |= 1;
  7407. }
  7408. }
  7409.  
  7410. } else if (mutation.attributeName === 'tyt-ep-visible') {
  7411. // assume any other active component such as tab content and chatroom
  7412.  
  7413. if (+(cssElm.getAttribute('tyt-ep-visible') || 0) === 0 && +mutation.oldValue > 0) {
  7414. dcall |= 2;
  7415. }
  7416.  
  7417. if (mss === 0) mss = 1;
  7418. else mss = -1;
  7419.  
  7420. } else if (mutation.attributeName === 'tyt-donation-shelf') {
  7421. // assume any other active component such as tab content and chatroom
  7422.  
  7423. // console.log(4545, cssElm.hasAttribute('tyt-donation-shelf'))
  7424. if (!(cssElm.hasAttribute('tyt-donation-shelf'))) {
  7425. dcall |= 4;
  7426. } else {
  7427. lstTab.lastPanel = '#donation-shelf'
  7428. switchTabActivity(null);
  7429. // console.log(55)
  7430. }
  7431. if (mss === 0) mss = 2;
  7432. else mss = -1;
  7433.  
  7434. } else if (mutation.attributeName === 'is-extra-wide-video_') {
  7435. getDMPromise().then(updateFloatingSlider); //required for hover slider // eg video after ads
  7436. }
  7437.  
  7438. }
  7439.  
  7440. new_layoutStatus = fixLayoutStatus(new_layoutStatus);
  7441.  
  7442. if (new_layoutStatus !== old_layoutStatus) {
  7443.  
  7444. if (fT(new_layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_THEATER | LAYOUT_CHATROOM_EXPANDED)) {
  7445. if (mss === 1) {
  7446. if (fT(new_layoutStatus, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED, 0)) {
  7447. closeDonationShelf();
  7448. }
  7449. } else if (mss === 2) {
  7450. if (fT(new_layoutStatus, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED, 0)) {
  7451. ytBtnCloseEngagementPanels();
  7452. }
  7453. }
  7454. }
  7455. wls.layoutStatus = new_layoutStatus
  7456.  
  7457. }
  7458.  
  7459.  
  7460. (async () => {
  7461. await getDMPromise();
  7462. layoutStatusMutex.lockWith(unlock => {
  7463. immediateCheck();
  7464. unlock();
  7465. });
  7466. })();
  7467.  
  7468.  
  7469. }
  7470.  
  7471. const mtf_attrBody = (mutations, observer) => {
  7472.  
  7473. getDMPromise().then(() => {
  7474.  
  7475. layoutStatusMutex.lockWith(unlock => {
  7476.  
  7477. check1885();
  7478. check1886();
  7479.  
  7480. if (chatController.allowChatControl) {
  7481. mtf_checkFlexy_(wls.layoutStatus);
  7482. fixTabs();
  7483. }
  7484.  
  7485.  
  7486. if (document.fullscreenElement) {
  7487. unlock();
  7488. } else if (wls.layoutStatus & LAYOUT_THEATER) {
  7489. if (chatController.allowChatControl) {
  7490. fixTheaterChat1A();
  7491. } else {
  7492. fixTheaterChat2A();
  7493. }
  7494. getDMPromise().then(unlock);
  7495. } else {
  7496. unlock();
  7497. }
  7498.  
  7499. });
  7500.  
  7501. });
  7502.  
  7503.  
  7504. }
  7505.  
  7506. function setupChatFrameDisplayState1(chatBlockR, initialDisplayState) {
  7507.  
  7508.  
  7509. const ytdFlexyElm = es.ytdFlexy;
  7510. if (!scriptEnable || !ytdFlexyElm) return;
  7511.  
  7512. let chatTypeChanged = mtf_chatBlockQ !== chatBlockR;
  7513.  
  7514. let attr_chatblock = chatBlockR === 1 ? 'chat$live' : chatBlockR === 3 ? 'chat$playback' : false;
  7515. let attr_chatcollapsed = false;
  7516.  
  7517.  
  7518. if (attr_chatblock) {
  7519. let chatFrame = document.querySelector('ytd-live-chat-frame#chat')
  7520. if (chatFrame) {
  7521. attr_chatcollapsed = chatFrame.hasAttribute('collapsed');
  7522. if (!attr_chatcollapsed) {
  7523.  
  7524. //nativeFunc(p,'setupPlayerProgressRelay')
  7525. //if(!p.isFrameReady)
  7526. //nativeFunc(p, "urlChanged")
  7527. //console.log(12399,1)
  7528. // chatFrame.dispatchEvent(new CustomEvent("tabview-chatroom-newpage")); //possible empty iframe is shown
  7529.  
  7530. }
  7531. } else {
  7532. attr_chatcollapsed = initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED' ? true : false;
  7533. }
  7534. }
  7535.  
  7536. if (chatTypeChanged) {
  7537. mtf_chatBlockQ = chatBlockR
  7538.  
  7539. // _console.log(932, 2, attr_chatblock, attr_chatcollapsed)
  7540.  
  7541. //LIVE_CHAT_DISPLAY_STATE_COLLAPSED
  7542. //LIVE_CHAT_DISPLAY_STATE_EXPANDED
  7543. let v = attr_chatblock
  7544. if (typeof attr_chatblock == 'string') {
  7545.  
  7546. if (attr_chatcollapsed === true) v = '-' + attr_chatblock
  7547. if (attr_chatcollapsed === false) v = '+' + attr_chatblock;
  7548. }
  7549. wAttr(ytdFlexyElm, 'tyt-chat', v)
  7550.  
  7551. // _console.log(932, 3, ytdFlexyElm.hasAttribute('tyt-chat'))
  7552.  
  7553.  
  7554. }
  7555.  
  7556. return { attr_chatblock, attr_chatcollapsed, chatTypeChanged }
  7557. }
  7558.  
  7559. function setupChatFrameDisplayState2() {
  7560.  
  7561. const ytdFlexyElm = es.ytdFlexy;
  7562. if (!scriptEnable || !ytdFlexyElm) return null;
  7563.  
  7564. // this is a backup solution only; should be abandoned
  7565.  
  7566. let attr_chatblock = null
  7567. let attr_chatcollapsed = null;
  7568.  
  7569. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  7570. let elmCont = null;
  7571. if (elmChat) {
  7572. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  7573.  
  7574.  
  7575. let s = 0;
  7576. if (elmCont) {
  7577. //not found if it is collapsed.
  7578. s |= _querySelector.call(elmCont, 'yt-timed-continuation') ? 1 : 0;
  7579. s |= _querySelector.call(elmCont, 'yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0;
  7580. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  7581. if (s == 1) {
  7582. attr_chatblock = 'chat$live';
  7583. } else if (s == 2) attr_chatblock = 'chat$playback';
  7584.  
  7585. if (s == 1) {
  7586. let cmCountElm = document.querySelector("span#tyt-cm-count")
  7587. if (cmCountElm) cmCountElm.textContent = '';
  7588. }
  7589.  
  7590. } else if (!ytdFlexyElm.hasAttribute('tyt-chat')) {
  7591. // live chat frame but type not known
  7592.  
  7593. attr_chatblock = '';
  7594.  
  7595. }
  7596. //keep unknown as original
  7597.  
  7598.  
  7599. let isCollapsed = !!elmChat.hasAttribute('collapsed');
  7600. attr_chatcollapsed = isCollapsed;
  7601.  
  7602. } else {
  7603. attr_chatblock = false;
  7604. attr_chatcollapsed = false;
  7605.  
  7606. }
  7607.  
  7608. return { attr_chatblock, attr_chatcollapsed }
  7609.  
  7610. }
  7611.  
  7612.  
  7613. const mtf_checkFlexy_ = (ls) => {
  7614.  
  7615. const ls0 = ls;
  7616.  
  7617. ls = flexAttr_toLayoutStatus(ls, 'theater')
  7618. ls = flexAttr_toLayoutStatus(ls, 'tyt-chat')
  7619. ls = flexAttr_toLayoutStatus(ls, 'is-two-columns_')
  7620. ls = flexAttr_toLayoutStatus(ls, 'tyt-tab')
  7621. ls = flexAttr_toLayoutStatus(ls, 'fullscreen')
  7622. ls = flexAttr_toLayoutStatus(ls, 'tyt-ep-visible')
  7623. ls = flexAttr_toLayoutStatus(ls, 'tyt-donation-shelf')
  7624.  
  7625. fixLayoutStatus(ls);
  7626.  
  7627. if (ls0 !== ls) {
  7628. wls.layoutStatus = ls
  7629. }
  7630.  
  7631. }
  7632.  
  7633. const setChatroomState = () => {
  7634. const ytdFlexyElm = es.ytdFlexy;
  7635. if (!ytdFlexyElm) return;
  7636. const rChatExist = setupChatFrameDisplayState2();
  7637. if (rChatExist) {
  7638. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  7639. if (attr_chatblock === null) {
  7640. //remove attribute if it is unknown
  7641. attr_chatblock = false;
  7642. attr_chatcollapsed = false;
  7643. }
  7644. let v = attr_chatblock;
  7645. if (typeof v === 'string') {
  7646. if (attr_chatcollapsed === true) v = '-' + v;
  7647. if (attr_chatcollapsed === false) v = '+' + v;
  7648. }
  7649. wAttr(ytdFlexyElm, 'tyt-chat', v)
  7650. }
  7651. }
  7652.  
  7653. const mtf_checkFlexy = () => {
  7654. // once per $$native-ytd-watch-flexy$$ {ytd-watch-flexy} detection
  7655.  
  7656. const ytdFlexyElm = es.ytdFlexy;
  7657. if (!scriptEnable || !ytdFlexyElm) return true;
  7658.  
  7659.  
  7660. if (typeof wls.layoutStatus === 'undefined' || !(wls.layoutStatus & LAYOUT_VAILD)) {
  7661. wls.layoutStatus = 0;
  7662. }
  7663.  
  7664. const isFlexyHidden = (ytdFlexyElm.hasAttribute('hidden'));
  7665.  
  7666. if (!isFlexyHidden) {
  7667. setChatroomState();
  7668. }
  7669.  
  7670. let rTabSelection = [..._querySelectorAll.call(ytdFlexyElm, '.tab-btn[tyt-tab-content]')]
  7671. .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') }));
  7672.  
  7673. if (rTabSelection.length === 0) {
  7674. wAttr(ytdFlexyElm, 'tyt-tab', false);
  7675. } else {
  7676. rTabSelection = rTabSelection.filter(entry => entry.hidden !== true); // all available tabs
  7677. if (rTabSelection.length === 0) wAttr(ytdFlexyElm, 'tyt-tab', '');
  7678. }
  7679. rTabSelection = null;
  7680.  
  7681. let rEP = engagement_panels_();
  7682. if (rEP && rEP.list.length > 0) {
  7683. wAttr(ytdFlexyElm, 'tyt-ep-visible', `${rEP.value}`);
  7684. } else {
  7685. wAttr(ytdFlexyElm, 'tyt-ep-visible', false);
  7686. }
  7687.  
  7688. mtf_checkFlexy_(LAYOUT_VAILD);
  7689.  
  7690. mtoFlexyAttr.bindElement(ytdFlexyElm, {
  7691. attributes: true,
  7692. attributeFilter: [/*'tyt-ctl',*/ 'tyt-chat', 'theater', 'is-two-columns_', 'tyt-tab', 'fullscreen', 'tyt-ep-visible', 'tyt-donation-shelf', 'hidden', 'is-extra-wide-video_'],
  7693. attributeOldValue: true
  7694. })
  7695. mtoBodyAttr.bindElement(document.body, {
  7696. attributes: true,
  7697. attributeFilter: ['data-ytlstm-theater-mode'],
  7698. attributeOldValue: true
  7699. })
  7700.  
  7701. let columns = document.querySelector('ytd-page-manager#page-manager #columns.ytd-watch-flexy')
  7702. if (columns) {
  7703. wAttr(columns, 'userscript-scrollbar-render', true);
  7704. }
  7705.  
  7706. immediateCheck()
  7707.  
  7708. return false;
  7709. }
  7710.  
  7711. document.addEventListener('tabview-fix-layout', () => {
  7712.  
  7713. immediateCheck()
  7714.  
  7715. }, false)
  7716.  
  7717.  
  7718. function switchTabActivity(activeLink) {
  7719.  
  7720. //console.log(4545, activeLink)
  7721. if (!scriptEnable) return;
  7722.  
  7723. const ytdFlexyElm = es.ytdFlexy;
  7724.  
  7725. if (!ytdFlexyElm) return;
  7726.  
  7727. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  7728.  
  7729. //if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null;
  7730.  
  7731.  
  7732. function runAtEnd() {
  7733.  
  7734. if (activeLink) {
  7735. lstTab.lastTab = activeLink.getAttribute('tyt-tab-content')
  7736. lstTab.lastPanel = null;
  7737.  
  7738. if (!document.querySelector(`${lstTab.lastTab}.tab-content-cld tabview-view-tab-expander`)) {
  7739.  
  7740. scriptletDeferred.debounce(() => {
  7741.  
  7742. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  7743. if (secondary) {
  7744. secondary.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  7745. //console.log(1995)
  7746. }
  7747.  
  7748. })
  7749.  
  7750. }
  7751. }
  7752.  
  7753. const newTag = activeLink ? lstTab.lastTab : '';
  7754.  
  7755. ytdFlexyElm.setAttribute('tyt-tab', newTag)
  7756.  
  7757. if (newTag === '#tab-videos') {
  7758.  
  7759.  
  7760.  
  7761. let t = document.querySelector('#tab-videos yt-chip-cloud-renderer')
  7762. if (t) {
  7763. scriptletDeferred.debounce(() => {
  7764. t.isConnected && sendToPageScript(t, 'tyt-resize-chip-cloud');
  7765. t = null;
  7766. })
  7767. }
  7768.  
  7769. }
  7770.  
  7771.  
  7772. }
  7773.  
  7774. const links = document.querySelectorAll('#material-tabs a[tyt-tab-content]');
  7775.  
  7776. //console.log(701, activeLink)
  7777.  
  7778. for (const link of links) {
  7779. let content = document.querySelector(link.getAttribute('tyt-tab-content'));
  7780. if (link && content) {
  7781. if (link !== activeLink) {
  7782. link.classList.remove("active");
  7783. content.classList.add("tab-content-hidden");
  7784. if (!content.hasAttribute("tyt-hidden")) {
  7785. content.setAttribute("tyt-hidden", ""); // for https://gf.qytechs.cn/en/scripts/456108
  7786. }
  7787. } else {
  7788. link.classList.add("active");
  7789. if (content.hasAttribute("tyt-hidden")) {
  7790. content.removeAttribute("tyt-hidden"); // for https://gf.qytechs.cn/en/scripts/456108
  7791. }
  7792. content.classList.remove("tab-content-hidden");
  7793. }
  7794. }
  7795. }
  7796.  
  7797. runAtEnd();
  7798.  
  7799.  
  7800. }
  7801.  
  7802.  
  7803. function getStore() {
  7804. let s = localStorage[STORE_key];
  7805. function resetStore() {
  7806. let ret = {
  7807. version: 1,
  7808. };
  7809. localStorage[STORE_key] = JSON.stringify(ret);
  7810. return ret;
  7811. }
  7812. if (!s) return resetStore();
  7813. let obj = null;
  7814. try {
  7815. obj = JSON.parse(s);
  7816. } catch (e) { }
  7817. return obj && obj.version === STORE_VERSION ? obj : resetStore();
  7818. }
  7819.  
  7820. function setStore(obj) {
  7821. if (!obj || typeof obj !== 'object') return false;
  7822. if (obj.version !== STORE_VERSION) return false;
  7823. localStorage[STORE_key] = JSON.stringify(obj);
  7824. return true;
  7825. }
  7826.  
  7827.  
  7828.  
  7829. async function handlerMaterialTabClickInner(tabBtn) {
  7830.  
  7831. await Promise.resolve(0);
  7832.  
  7833. const layoutStatusMutexUnlock = await new Promise(resolve => {
  7834. layoutStatusMutex.lockWith(unlock => {
  7835. resolve(unlock)
  7836. })
  7837. });
  7838.  
  7839. // locked
  7840. let unlock = layoutStatusMutexUnlock; // function of unlock inside layoutStatusMutex
  7841.  
  7842. //console.log(8515)
  7843. switchTabActivity_lastTab = tabBtn.getAttribute('tyt-tab-content');
  7844.  
  7845. let isActiveAndVisible = tabBtn.classList.contains('tab-btn') && tabBtn.classList.contains('active') && !tabBtn.classList.contains('tab-btn-hidden')
  7846.  
  7847. _console.log(8221, 15, isActiveAndVisible)
  7848.  
  7849. if (isFullScreen()) {
  7850.  
  7851. _console.log(8221, 16, 1)
  7852.  
  7853. if (isActiveAndVisible) {
  7854. getDMPromise().then(unlock);
  7855. switchTabActivity(null);
  7856. } else {
  7857.  
  7858. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  7859. ytBtnCollapseChat();
  7860. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  7861. ytBtnCloseEngagementPanels();
  7862. } else if (isDonationShelfExpanded() && isWideScreenWithTwoColumns()) {
  7863. closeDonationShelf();
  7864. }
  7865.  
  7866. !!(async () => {
  7867. if (canScrollIntoViewWithOptions) {
  7868. await getDMPromise();
  7869. fullScreenTabScrollIntoView();
  7870. }
  7871. await getDMPromise();
  7872. unlock();
  7873. })();
  7874. switchTabActivity(tabBtn)
  7875. }
  7876.  
  7877. } else if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) {
  7878. switchTabActivity(null);
  7879. ytBtnSetTheater();
  7880. getDMPromise().then(unlock);
  7881. } else if (isActiveAndVisible) {
  7882. switchTabActivity(null);
  7883. getDMPromise().then(unlock);
  7884. } else {
  7885.  
  7886. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  7887. ytBtnCollapseChat();
  7888. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  7889. ytBtnCloseEngagementPanels();
  7890. } else if (isDonationShelfExpanded() && isWideScreenWithTwoColumns()) {
  7891. closeDonationShelf();
  7892. } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) {
  7893. ytBtnCancelTheater(); //ytd-watch-flexy attr [theater]
  7894. }
  7895.  
  7896. switchTabActivity(tabBtn)
  7897.  
  7898.  
  7899. !!(async () => {
  7900. await getDMPromise();
  7901. // single column view; click button; scroll to tab content area 100%
  7902. let rightTabs = document.querySelector('#right-tabs');
  7903. if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && tabBtn.classList.contains('active') && rightTabs.hasAttribute('tyt-stickybar')) {
  7904. let tabButtonBar = document.querySelector('#material-tabs');
  7905. let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0;
  7906. window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight); // scrollIntoView
  7907. }
  7908. await getDMPromise();
  7909. unlock();
  7910. })();
  7911.  
  7912. }
  7913.  
  7914.  
  7915. }
  7916.  
  7917. function handlerMaterialTabClick(/** @type {MouseEvent} */ evt) {
  7918.  
  7919. //console.log(8510)
  7920. const ytdFlexyElm = es.ytdFlexy;
  7921. if (!scriptEnable || !ytdFlexyElm) return null;
  7922.  
  7923. let tabBtn = this;
  7924.  
  7925. if (!tabBtn.hasAttribute('tyt-tab-content')) return;
  7926.  
  7927. /** @type {HTMLElement | null} */
  7928. let dom = evt.target;
  7929. if (!dom) return;
  7930.  
  7931. if (dom.classList.contains('font-size-btn')) return;
  7932.  
  7933.  
  7934. evt.preventDefault();
  7935. evt.stopPropagation();
  7936. evt.stopImmediatePropagation();
  7937.  
  7938. handlerMaterialTabClickInner(tabBtn);
  7939.  
  7940.  
  7941. }
  7942.  
  7943. function onVideoLeavePictureInPicuture() {
  7944. isMiniviewForStickyHeadEnabled = false;
  7945. }
  7946.  
  7947.  
  7948. let videoPlayerInsectObserver = null;
  7949. let videoInsected = false;
  7950.  
  7951.  
  7952. async function enablePIPforStickyHead() {
  7953. // use async & await to avoid handler took 60ms
  7954.  
  7955. if (!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && userActivation && typeof IntersectionObserver == 'function') {
  7956. let video = document.querySelector('#player video'); // ignore audio mode
  7957. if (!video) return;
  7958.  
  7959. const vRect = video.getBoundingClientRect();
  7960. if (!(vRect.top < 0 && vRect.height > 100 && vRect.bottom < vRect.height * 0.25)) return; // avoid conflic with YouTube Mobile Mode in YouTube Live Borderless
  7961.  
  7962. await Promise.resolve(0)
  7963. const pageClientWidth = document.documentElement.clientWidth;
  7964. if (pageClientWidth + 320 < screen.width && pageClientWidth > 320 && !document.querySelector('#rCbM3')) {
  7965.  
  7966.  
  7967. await Promise.resolve(0)
  7968. // desktop or notebook can use this feature
  7969.  
  7970. // --------------------------------------------------------
  7971. // ignore user activation error
  7972. enterPIP(video, null).then(r => {
  7973. if (r === true) {
  7974.  
  7975. userActivation = false;
  7976. isMiniviewForStickyHeadEnabled = true;
  7977. }
  7978. });
  7979. // --------------------------------------------------------
  7980. video.removeEventListener('leavepictureinpicture', onVideoLeavePictureInPicuture, false);
  7981. video.addEventListener('leavepictureinpicture', onVideoLeavePictureInPicuture, false);
  7982.  
  7983. if (!video.hasAttribute('NOL4j')) {
  7984. video.setAttribute('NOL4j', "");
  7985.  
  7986.  
  7987. await Promise.resolve(0)
  7988.  
  7989. let callback = (entries) => {
  7990.  
  7991.  
  7992. let lastEntry = entries[entries.length - 1];
  7993. if (lastEntry && lastEntry.isIntersecting === true) {
  7994.  
  7995. videoInsected = true;
  7996.  
  7997. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled && userActivation && videoInsected) {
  7998. restorePIPforStickyHead();
  7999. }
  8000.  
  8001. } else {
  8002. videoInsected = false;
  8003. }
  8004.  
  8005. };
  8006.  
  8007. if (!videoPlayerInsectObserver) {
  8008. videoPlayerInsectObserver = new IntersectionObserver(callback, {
  8009. root: null,
  8010. rootMargin: "0px",
  8011. threshold: 0.25
  8012. });
  8013. }
  8014.  
  8015. videoPlayerInsectObserver.takeRecords();
  8016. videoPlayerInsectObserver.disconnect();
  8017.  
  8018. videoPlayerInsectObserver.observe(video);
  8019.  
  8020.  
  8021. }
  8022.  
  8023. }
  8024. }
  8025. }
  8026.  
  8027. function setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight) {
  8028.  
  8029. //if(isStickyHeaderEnabled===bool) return; // no update
  8030.  
  8031. if (bool === true) {
  8032. const { width, height } = getWidthHeight();
  8033. targetElm.style.setProperty("--tyt-stickybar-w", width + 'px')
  8034. targetElm.style.setProperty("--tyt-stickybar-h", height + 'px')
  8035. const res = getLeftRight();
  8036. if (res) {
  8037.  
  8038. targetElm.style.setProperty("--tyt-stickybar-l", (res.left) + 'px')
  8039. targetElm.style.setProperty("--tyt-stickybar-r", (res.right) + 'px')
  8040.  
  8041. }
  8042. wAttr(targetElm, 'tyt-stickybar', true);
  8043. isStickyHeaderEnabled = true;
  8044.  
  8045. if (!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && userActivation && typeof IntersectionObserver == 'function') {
  8046. getDMPromise().then(enablePIPforStickyHead);
  8047. }
  8048.  
  8049. } else if (bool === false) {
  8050.  
  8051. wAttr(targetElm, 'tyt-stickybar', false);
  8052. isStickyHeaderEnabled = false;
  8053.  
  8054.  
  8055. }
  8056.  
  8057.  
  8058. }
  8059.  
  8060. const singleColumnScrolling = async function () {
  8061. //makeHeaderFloat
  8062. // required for 1) init 2) layout change 3) resizing
  8063.  
  8064. if (!scriptEnable || pageType !== 'watch') return;
  8065.  
  8066.  
  8067. let isTwoCol = (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS;
  8068. if (isTwoCol) {
  8069.  
  8070. if (isStickyHeaderEnabled) {
  8071.  
  8072. let targetElm = document.querySelector("#right-tabs");
  8073. setStickyHeader(targetElm, false, null, null);
  8074. }
  8075. return;
  8076. }
  8077.  
  8078. let pageY = scrollY;
  8079.  
  8080.  
  8081. let tdt = Date.now();
  8082. singleColumnScrolling_dt = tdt;
  8083.  
  8084.  
  8085. _console.log(7891, 'scrolling')
  8086.  
  8087. function getXYStatus(res) {
  8088.  
  8089. const [navHeight, elmY] = res;
  8090.  
  8091. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  8092.  
  8093. let xyStatus = 0
  8094. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  8095. // 1
  8096. xyStatus = 1
  8097. }
  8098.  
  8099. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  8100.  
  8101. //2
  8102. xyStatus = 2
  8103.  
  8104. }
  8105.  
  8106. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  8107. // 3
  8108.  
  8109. xyStatus = 3
  8110.  
  8111.  
  8112. }
  8113.  
  8114. return xyStatus;
  8115. }
  8116.  
  8117. let [targetElm, header, navElm] = await Promise.all([
  8118. Promise.resolve().then(() => document.querySelector("#right-tabs")),
  8119.  
  8120. Promise.resolve().then(() => document.querySelector("#right-tabs header")),
  8121.  
  8122. Promise.resolve().then(() => document.querySelector('#masthead-container, #masthead')),
  8123.  
  8124. ]);
  8125.  
  8126. function emptyForGC() {
  8127. targetElm = null;
  8128. header = null;
  8129. navElm = null;
  8130. }
  8131.  
  8132. if (!targetElm || !header) {
  8133. return emptyForGC();
  8134. }
  8135. if (singleColumnScrolling_dt !== tdt) return emptyForGC();
  8136.  
  8137. let res2 = await Promise.all([
  8138. Promise.resolve().then(() => navElm ? navElm.offsetHeight : 0),
  8139. Promise.resolve().then(() => targetElm.offsetTop)
  8140. ])
  8141.  
  8142. if (res2 === null) return emptyForGC();
  8143.  
  8144. if (singleColumnScrolling_dt !== tdt) return emptyForGC();
  8145.  
  8146.  
  8147. const xyStatus = getXYStatus(res2);
  8148.  
  8149.  
  8150. function getLeftRight() {
  8151.  
  8152. let thp = document.querySelector('tabview-view-pos-thead');
  8153. if (thp) {
  8154.  
  8155. let rect = thp.getBoundingClientRect()
  8156. if (rect) {
  8157. return {
  8158. left: rect.left,
  8159. right: document.documentElement.clientWidth - rect.right
  8160. };
  8161. }
  8162. }
  8163. return null;
  8164. }
  8165.  
  8166. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  8167.  
  8168. function getWidthHeight() {
  8169. return { width: targetElm.offsetWidth, height: header.offsetHeight };
  8170. }
  8171.  
  8172. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  8173.  
  8174.  
  8175. emptyForGC();
  8176.  
  8177. };
  8178.  
  8179.  
  8180. const singleColumnScrolling2 = async function (xyStatus, width, xRect) {
  8181. //makeHeaderFloat
  8182.  
  8183. if (!scriptEnable || pageType !== 'watch') return;
  8184.  
  8185.  
  8186. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  8187. return;
  8188. }
  8189.  
  8190. let [targetElm, header] = await Promise.all([
  8191. Promise.resolve().then(() => document.querySelector("#right-tabs")),
  8192. Promise.resolve().then(() => document.querySelector("#right-tabs header"))
  8193. ]);
  8194.  
  8195. function emptyForGC() {
  8196. targetElm = null;
  8197. header = null;
  8198. }
  8199.  
  8200.  
  8201. if (!targetElm || !header) {
  8202. return emptyForGC();
  8203. }
  8204.  
  8205. function getLeftRight() {
  8206. return xRect;
  8207. }
  8208.  
  8209. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  8210.  
  8211. function getWidthHeight() {
  8212. return { width: (width || targetElm.offsetWidth), height: header.offsetHeight };
  8213. }
  8214.  
  8215. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  8216.  
  8217. emptyForGC();
  8218.  
  8219. };
  8220.  
  8221.  
  8222. function resetBuggyLayoutForNewVideoPage() {
  8223.  
  8224. const ytdFlexyElm = es.ytdFlexy;
  8225. if (!ytdFlexyElm) return;
  8226.  
  8227. //(flexy is visible and watch video page)
  8228.  
  8229. scriptEnable = true;
  8230.  
  8231. _console.log(27056)
  8232.  
  8233. let new_layoutStatus = wls.layoutStatus
  8234.  
  8235. new_layoutStatus & (LAYOUT_CHATROOM_COLLAPSED | LAYOUT_CHATROOM)
  8236.  
  8237. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  8238. const new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  8239.  
  8240. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  8241. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  8242. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  8243. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  8244. const new_isExpandedEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  8245. const new_isExpandedDonationShelf = new_layoutStatus & LAYOUT_DONATION_SHELF_EXPANDED;
  8246.  
  8247. const nothingShown = !new_isTabExpanded && !new_isExpandedChat && !new_isExpandedEPanel && !new_isExpandedDonationShelf
  8248.  
  8249. if (ytdFlexyElm.getAttribute('tyt-tab') === '' && new_isTwoColumns && !new_isTheater && nothingShown && !new_isFullScreen) {
  8250. // e.g. engage panel removed after miniview and change video
  8251. setToActiveTab();
  8252. } else if (new_isExpandedEPanel && _querySelectorAll.call(ytdFlexyElm, '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])').length === 0) {
  8253. wls.layoutStatus = new_layoutStatus & (~LAYOUT_ENGAGEMENT_PANEL_EXPANDED)
  8254. } else if (new_isExpandedDonationShelf && _querySelectorAll.call(ytdFlexyElm, 'ytd-donation-shelf-renderer.ytd-watch-flexy:not([hidden])').length === 0) {
  8255. wls.layoutStatus = new_layoutStatus & (~LAYOUT_DONATION_SHELF_EXPANDED)
  8256. }
  8257.  
  8258. }
  8259.  
  8260.  
  8261. function extractInfoFromLiveChatRenderer(liveChatRenderer) {
  8262.  
  8263. const lcr = liveChatRenderer
  8264.  
  8265. const data_shb = ((lcr || 0).showHideButton || 0).toggleButtonRenderer
  8266.  
  8267. const data_sb = !data_shb ? ((lcr || 0).showButton || 0).buttonRenderer : null // only show button
  8268.  
  8269. let default_display_state = null, txt_collapse = null, txt_expand = null;
  8270.  
  8271. if (!data_shb && data_sb && data_sb.text && typeof data_sb.text.simpleText === 'string') {
  8272.  
  8273. if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_EXPANDED") {
  8274. default_display_state = lcr.initialDisplayState
  8275. } else if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_COLLAPSED") {
  8276. default_display_state = lcr.initialDisplayState
  8277. }
  8278. txt_expand = data_sb.text.simpleText;
  8279.  
  8280. } else if (data_shb && data_shb.defaultText && data_shb.toggledText && data_shb.defaultText.runs && data_shb.toggledText.runs) {
  8281.  
  8282. if (data_shb.defaultText.runs.length === 1 && data_shb.toggledText.runs.length === 1) {
  8283.  
  8284. if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_EXPANDED") {
  8285.  
  8286. default_display_state = lcr.initialDisplayState
  8287.  
  8288. txt_collapse = (data_shb.defaultText.runs[0] || 0).text // COLLAPSE the area
  8289.  
  8290. txt_expand = (data_shb.toggledText.runs[0] || 0).text // expand the area
  8291.  
  8292. } else if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_COLLAPSED") {
  8293. default_display_state = lcr.initialDisplayState
  8294.  
  8295. txt_expand = (data_shb.defaultText.runs[0] || 0).text // expand the area
  8296.  
  8297. txt_collapse = (data_shb.toggledText.runs[0] || 0).text // COLLAPSE the area
  8298. }
  8299.  
  8300.  
  8301. if (typeof txt_expand == 'string' && typeof txt_collapse == 'string' && txt_expand.length > 0 && txt_collapse.length > 0) {
  8302.  
  8303. } else {
  8304. txt_expand = null;
  8305. txt_collapse = null;
  8306. }
  8307. }
  8308.  
  8309. }
  8310.  
  8311. if (lcr && (!default_display_state)) {
  8312. console.log('[tyt] Unable to obtain showHideButton data');
  8313. }
  8314.  
  8315. return { default_display_state, txt_collapse, txt_expand }
  8316.  
  8317. }
  8318.  
  8319. // added in 2024.07.05
  8320. // see https://gf.qytechs.cn/scripts/428651/discussions/250256
  8321. const notWaitingRoom = () => {
  8322.  
  8323. // https://www.youtube.com/watch?v=fDL2UhbysVk
  8324. // live waiting room: dont hide for chat$live
  8325.  
  8326. if (!formatDates) {
  8327. return true; // assumption based
  8328. }
  8329.  
  8330. if (formatDates && formatDates.broadcastBeginAt && !formatDates.broadcastEndAt && formatDates.isLiveNow === false) return false;
  8331.  
  8332. return true;
  8333. }
  8334.  
  8335. const dpeChatRefreshCounter = ControllerID();
  8336. // let proceedingChatFrameVideoID = '';
  8337. // let newVideoPageCACC = -1;
  8338. let mvideoState = 0;
  8339.  
  8340. function newVideoPage(evt_detail) {
  8341.  
  8342. //toggleBtnDC = 1;
  8343.  
  8344. console.debug('[tyt] newVideoPage');
  8345. // Promise.resolve().then(setupVideoTitleHover);
  8346. mvideoState = 0;
  8347. // console.debug('[tyt] debug ym-01-0')
  8348.  
  8349. const ytdFlexyElm = es.ytdFlexy;
  8350. if (!ytdFlexyElm) return;
  8351.  
  8352. layoutStatusMutex = new Mutex();
  8353.  
  8354. let liveChatRenderer = null;
  8355. let isReplay = null;
  8356.  
  8357. if (pageType !== 'watch') return; // scriptEnable -> pageType shall be always 'watch'
  8358. resetBuggyLayoutForNewVideoPage();
  8359.  
  8360. try {
  8361. liveChatRenderer = evt_detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  8362. } catch (e) { }
  8363. if (liveChatRenderer) {
  8364. if (liveChatRenderer.isReplay === true) isReplay = true;
  8365. }
  8366.  
  8367. pageFetchedDataVideoId = ((((evt_detail || 0).pageData || 0).playerResponse || 0).videoDetails || 0).videoId || 0;
  8368.  
  8369.  
  8370. const fvid = pageFetchedDataVideoId;
  8371. const tyid = dpeChatRefreshCounter.inc();
  8372. // proceedingChatFrameVideoID = '';
  8373. // newVideoPageCACC = chatroomAttrCollapseCount;
  8374. // console.debug('[tyt] debug ym-01-1')
  8375.  
  8376. const getPromise = () => {
  8377. return new Promise(resolve => {
  8378. let mo = new MutationObserver(() => {
  8379. if (resolve && mo) {
  8380. mo.disconnect();
  8381. mo.takeRecords();
  8382. mo = null;
  8383. resolve();
  8384. resolve = null;
  8385. }
  8386. });
  8387. mo && mo.observe(document.documentElement, { attributes: true, attributeFilter: ['sxmq8'] });
  8388. });
  8389. }
  8390.  
  8391. const promise = getPromise();
  8392.  
  8393. const rootDom = document.documentElement;
  8394. rootDom.setAttribute('sxmq8', rootDom.getAttribute('sxmq8') === '1' ? '0' : '1');
  8395. console.log('sxmq8 r3', document.documentElement.getAttribute('sxmq8') );
  8396.  
  8397. promise.then(() => {
  8398.  
  8399. const promise = getPromise();
  8400. const rootDom = document.documentElement;
  8401. rootDom.setAttribute('sxmq8', rootDom.getAttribute('sxmq8') === '1' ? '0' : '1');
  8402. console.log('sxmq8 r4', document.documentElement.getAttribute('sxmq8') );
  8403.  
  8404. promise.then(() => {
  8405.  
  8406. // console.debug('[tyt] debug ym-01-2')
  8407. if (fvid !== pageFetchedDataVideoId) return;
  8408.  
  8409. // console.debug('[tyt] debug ym-01-3')
  8410. if (tyid !== dpeChatRefreshCounter.valueOf()) return;
  8411.  
  8412. // console.debug('[tyt] debug ym-01-4')
  8413. dpeChatRefreshCounter.inc();
  8414. const chat = document.querySelector('ytd-live-chat-frame#chat');
  8415. if (chat && !chat.hasAttribute('collapsed')) {
  8416. // proceedingChatFrameVideoID = fvid;
  8417.  
  8418. // console.debug('[tyt] debug ym-01-5')
  8419.  
  8420.  
  8421. // dpeNewUrlChat(chat); // force replace url
  8422.  
  8423. const iframe = _querySelector.call(chat, 'body iframe.style-scope.ytd-live-chat-frame#chatframe');
  8424. // console.log("iframe.xx",501,iframe)
  8425. // showMessages_IframeLoaded && console.debug('[tyt.iframe] loaded 0D');
  8426. if (iframe) Promise.resolve(iframe).then(iframeToVisible); // fix empty
  8427.  
  8428. }
  8429.  
  8430. });
  8431.  
  8432. });
  8433.  
  8434.  
  8435. const chatBlockR = liveChatRenderer ? (isReplay ? 3 : 1) : 0
  8436. const initialDisplayState = liveChatRenderer ? liveChatRenderer.initialDisplayState : null;
  8437.  
  8438.  
  8439. liveChatRenderer = null; // release memory for GC, just in case
  8440.  
  8441. let f = () => {
  8442.  
  8443. _console.log(932, 1, 1)
  8444. const ytdFlexyElm = es.ytdFlexy;
  8445. if (!scriptEnable || !ytdFlexyElm) return;
  8446.  
  8447. _console.log(932, 1, 2)
  8448. if (pageType !== 'watch') return;
  8449.  
  8450. _console.log(932, 1, 3)
  8451.  
  8452.  
  8453. let displayState = setupChatFrameDisplayState1(chatBlockR, initialDisplayState);
  8454.  
  8455. let { attr_chatblock, attr_chatcollapsed, chatTypeChanged } = displayState;
  8456.  
  8457.  
  8458. if (pageType === 'watch') { // reset info when hidden
  8459.  
  8460. let elm_storeLastPanel = es.storeLastPanel;
  8461.  
  8462. if (!elm_storeLastPanel) storeLastPanel = null;
  8463. else if (!isDOMVisible(elm_storeLastPanel)) {
  8464. storeLastPanel = null;
  8465. ytBtnCloseEngagementPanels();
  8466. }
  8467.  
  8468. }
  8469.  
  8470. if (chatTypeChanged) {
  8471.  
  8472. if (attr_chatblock == 'chat$live' && notWaitingRoom()) {
  8473.  
  8474. _console.log(932, 4)
  8475.  
  8476. mtf_forceCheckLiveVideo_disable = 2;
  8477.  
  8478. //console.log(3712,1)
  8479.  
  8480. _disableComments();
  8481.  
  8482.  
  8483. } else {
  8484.  
  8485. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"].tab-btn-hidden')
  8486. if (tabBtn) {
  8487. emptyCommentSection();
  8488. _console.log(9360, 74);
  8489. setTabBtnVisible(tabBtn, true);
  8490. } else {
  8491. setCommentSection(0);
  8492. }
  8493.  
  8494. mtf_forceCheckLiveVideo_disable = 0;
  8495.  
  8496. _console.log(7234, 'comments_section_loaded = 0')
  8497. restoreFetching();
  8498.  
  8499.  
  8500. }
  8501.  
  8502.  
  8503. } else {
  8504.  
  8505. // restore Fetching only
  8506.  
  8507. if (mtf_forceCheckLiveVideo_disable !== 2 && (attr_chatblock === false || attr_chatblock === 'chat$playback')) {
  8508.  
  8509. emptyCommentSection();
  8510. _console.log(9360, 77);
  8511. mtf_forceCheckLiveVideo_disable = 0;
  8512. _console.log(7235, 'comments_section_loaded = 0')
  8513. restoreFetching();
  8514.  
  8515. }
  8516.  
  8517. }
  8518.  
  8519.  
  8520. checkAndMakeNewCommentFetch();
  8521.  
  8522. }
  8523.  
  8524. f();
  8525.  
  8526. }
  8527.  
  8528. function setVideosTwoColumns(/** @type {number} */ flag, /** @type {boolean} */ bool) {
  8529.  
  8530. //two columns to one column
  8531.  
  8532. /*
  8533. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  8534.  
  8535. is-two-columns ="" => no is-two-columns
  8536. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  8537. no hidden => hidden =""
  8538. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  8539. hidden ="" => no hidden
  8540.  
  8541. */
  8542.  
  8543. let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  8544.  
  8545. let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  8546.  
  8547. let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  8548.  
  8549. let res = {}
  8550. if (flag & 1) {
  8551. res.m1 = document.querySelector(cssSelector1)
  8552. if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false);
  8553. }
  8554.  
  8555. if (flag & 2) {
  8556. res.m2 = document.querySelector(cssSelector2)
  8557. if (res.m2) wAttr(res.m2, 'hidden', bool ? false : '');
  8558. }
  8559.  
  8560. if (flag & 4) {
  8561. res.m3 = document.querySelector(cssSelector3)
  8562. if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false);
  8563. }
  8564.  
  8565. return res
  8566.  
  8567.  
  8568. }
  8569.  
  8570.  
  8571. // ---------------------------------------------------------------------------------------------
  8572.  
  8573. // ---- EVENTS ----
  8574.  
  8575. let ytEventSequence = 0
  8576. let formatDates = null
  8577.  
  8578. function setupFormatDates(pageFetchedDataLocal) {
  8579.  
  8580. formatDates = {}
  8581. try {
  8582. formatDates.publishDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.publishDate
  8583. } catch (e) { }
  8584. // 2022-12-30
  8585.  
  8586. try {
  8587. formatDates.uploadDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.uploadDate
  8588. } catch (e) { }
  8589. // 2022-12-30
  8590.  
  8591. try {
  8592. formatDates.publishDate2 = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.dateText.simpleText
  8593. } catch (e) { }
  8594. // 2022/12/31
  8595.  
  8596. if (typeof formatDates.publishDate2 === 'string' && formatDates.publishDate2 !== formatDates.publishDate) {
  8597. formatDates.publishDate = formatDates.publishDate2
  8598. formatDates.uploadDate = null
  8599. }
  8600.  
  8601. try {
  8602. formatDates.broadcastBeginAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.startTimestamp
  8603. } catch (e) { }
  8604. try {
  8605. formatDates.broadcastEndAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.endTimestamp
  8606. } catch (e) { }
  8607. try {
  8608. formatDates.isLiveNow = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.isLiveNow
  8609. } catch (e) { }
  8610.  
  8611. }
  8612.  
  8613. function pageBeingFetched(evt) {
  8614.  
  8615. let nodeName = (((evt || 0).target || 0).nodeName || '').toUpperCase()
  8616. if (nodeName !== 'YTD-APP') return;
  8617.  
  8618. const pageFetchedDataLocal = evt.detail;
  8619.  
  8620. let d_page = ((pageFetchedDataLocal || 0).pageData || 0).page;
  8621. if (!d_page) return;
  8622.  
  8623. pageType = d_page;
  8624.  
  8625. if (pageType !== 'watch') return
  8626.  
  8627. let isFirstLoad = firstLoadStatus & 8;
  8628.  
  8629. if (isFirstLoad) {
  8630. firstLoadStatus -= 8;
  8631. iframeLoadHookHandlerPromise.resolve();
  8632. document.addEventListener('load', iframeLoadHookHandler, capturePassive);
  8633. ytMicroEventsInit();
  8634. }
  8635. // ytMicroEventsInit set
  8636. variableResets();
  8637.  
  8638. if (isFirstLoad && ytEventSequence >= 2) {
  8639.  
  8640. scriptletDeferred.debounce(() => {
  8641.  
  8642. let docElement = document.documentElement
  8643. if (docElement.hasAttribute('tabview-loaded')) {
  8644. throw 'Tabview Youtube Duplicated';
  8645. }
  8646. docElement.setAttribute('tabview-loaded', '')
  8647.  
  8648. Promise.resolve(docElement).then(docElement => {
  8649. if (ytEventSequence >= 2) {
  8650. sendToPageScript(docElement, 'tabview-ce-hack')
  8651. docElement = null
  8652. }
  8653. });
  8654.  
  8655. });
  8656.  
  8657. }
  8658. // tabview-loaded delay set
  8659.  
  8660. setupFormatDates(pageFetchedDataLocal);
  8661.  
  8662. const promiseChatDetails = Promise.resolve(pageFetchedDataLocal).then((pageFetchedDataLocal) => {
  8663. if (ytEventSequence >= 2) {
  8664. let liveChatRenderer = null;
  8665. try {
  8666. liveChatRenderer = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  8667. } catch (e) { }
  8668. chatroomDetails = liveChatRenderer ? extractInfoFromLiveChatRenderer(liveChatRenderer) : null;
  8669. }
  8670. }).then();
  8671.  
  8672.  
  8673. let tmpElm = es.ytdFlexy;
  8674. if (tmpElm && tmpElm.isConnected === true && tmpElm.nodeName.toLowerCase() === 'ytd-watch-flexy') {
  8675. // currently, tmpElm is null; to be reviewed
  8676. } else {
  8677. // update global ref
  8678. ytdFlexy = mWeakRef(document.querySelector('ytd-watch-flexy:not([hidden])') || document.querySelector('ytd-watch-flexy'));
  8679. }
  8680. tmpElm = null;
  8681.  
  8682. if (!es.ytdFlexy) return;
  8683.  
  8684. Promise.resolve(0).then(() => {
  8685.  
  8686. const ytdFlexyElm = es.ytdFlexy; // shall be always non-null
  8687.  
  8688. if (ytEventSequence >= 2 && pageRendered === 0 && ytdFlexyElm && ytdFlexyElm.isConnected === true) {
  8689.  
  8690. // trigger renderDeferred.resolve();
  8691. let elmPL = document.createElement('tabview-view-ploader');
  8692. pageRendered = 1;
  8693. // ytdFlexyElm.appendChild(elmPL);
  8694. elementAppend.call(ytdFlexyElm, elmPL);
  8695. // pageRendered keeps at 1 if the video is continuously playing at the background
  8696. // pageRendered would not be resolve but will reset for each change of video
  8697.  
  8698. } else if (ytEventSequence >= 2 && pageRendered === 1 && ytdFlexyElm && ytdFlexyElm.isConnected === true) {
  8699. // 511, [3, 1, true]
  8700. } else {
  8701. console.warn(511, [ytEventSequence, pageRendered, !!ytdFlexyElm])
  8702. }
  8703.  
  8704. });
  8705.  
  8706. let _pageFetchedDataLocal = pageFetchedDataLocal;
  8707. promiseChatDetails.then(() => {
  8708.  
  8709. const ytdFlexyElm = es.ytdFlexy;
  8710. if (ytEventSequence >= 2 && ytdFlexyElm) {
  8711. ytdFlexyElm.classList.toggle('tyt-chat-toggleable', !!chatroomDetails);
  8712. }
  8713.  
  8714. if (ytEventSequence >= 2) {
  8715.  
  8716. discardableFn(() => { // assume tyt-deferred
  8717.  
  8718. const pageFetchedDataLocal = _pageFetchedDataLocal;
  8719. _pageFetchedDataLocal = null;
  8720. if (ytEventSequence >= 2 && pageFetchedDataLocal !== null) {
  8721. domInit_comments();
  8722. newVideoPage(pageFetchedDataLocal);
  8723. }
  8724.  
  8725. });
  8726.  
  8727. }
  8728.  
  8729. });
  8730.  
  8731. if (updateFlexyStateRequired) updateFlexyStateFn();
  8732.  
  8733. }
  8734.  
  8735. let pageSeqMutex = new Mutex()
  8736.  
  8737. function pageSeq1(evt) {
  8738. _navigateLoadDT = 0
  8739. if (ytEventSequence !== 1) {
  8740. ytEventSequence = 1
  8741. pageSeqMutex.lockWith(unlock => {
  8742. // regardless pageType
  8743. pageBeingInit();
  8744. unlock();
  8745. })
  8746. }
  8747. }
  8748.  
  8749. function pageSeq2(evt) {
  8750. _navigateLoadDT = 0;
  8751.  
  8752. if (ytEventSequence !== 1) {
  8753. ytEventSequence = 1;
  8754. pageSeqMutex.lockWith(unlock => {
  8755. // regardless pageType
  8756. pageBeingInit();
  8757. unlock();
  8758. })
  8759. }
  8760. if (ytEventSequence === 1) {
  8761. ytEventSequence = 2;
  8762.  
  8763. pageType = null;
  8764.  
  8765. pageSeqMutex.lockWith(unlock => {
  8766. // regardless pageType
  8767.  
  8768. pageType = null;
  8769. pageBeingFetched(evt);
  8770. // ytMicroEventsInit set + tabview-loaded delay set
  8771.  
  8772. if (pageType !== 'watch') {
  8773. ytdFlexy = null;
  8774. chatroomDetails = null;
  8775. }
  8776.  
  8777. Promise.resolve().then(() => {
  8778. const isWatch = pageType === 'watch';
  8779. const isEventSeqOK = ytEventSequence >= 2;
  8780. if (!isWatch) {
  8781. ytdFlexy = null;
  8782. chatroomDetails = null;
  8783. }
  8784. if (!isWatch && isEventSeqOK) {
  8785. variableResets();
  8786. emptyCommentSection();
  8787. tabsDeferred.reset();
  8788. _pageBeingInit();
  8789. tabsDeferred.resolve(); // for page initialization
  8790. }
  8791. if (isEventSeqOK) {
  8792. document.documentElement.classList.toggle('tabview-normal-player', isWatch);
  8793. }
  8794. });
  8795.  
  8796. if (_updateTimeAccum > 0) {
  8797. const currentVideo = document.querySelector('#movie_player video[src]')
  8798. let keep_viTime = false
  8799. if (currentVideo && currentVideo.readyState > currentVideo.HAVE_CURRENT_DATA && currentVideo.currentTime > 2.2) {
  8800. // allow miniview browsing
  8801. keep_viTime = true
  8802. }
  8803. if (!keep_viTime) {
  8804. _viTimeNum = 203;
  8805. _updateTimeAccum = 0;
  8806. delete document.head.dataset.viTime;
  8807. }
  8808. }
  8809.  
  8810. unlock();
  8811. })
  8812. }
  8813.  
  8814. }
  8815.  
  8816. function pageSeq3(evt) {
  8817. _navigateLoadDT = 0
  8818.  
  8819. if (ytEventSequence === 2) {
  8820. ytEventSequence = 3
  8821.  
  8822. pageSeqMutex.lockWith(unlock => {
  8823. if (pageType === 'watch') {
  8824. // ytMicroEventsInit set + tabview-loaded delay set
  8825. onNavigationEndAsync();
  8826. }
  8827.  
  8828.  
  8829. unlock();
  8830. })
  8831. }
  8832. }
  8833.  
  8834.  
  8835. document.addEventListener('yt-navigate-start', pageSeq1, bubblePassive)
  8836. document.addEventListener('yt-navigate-cache', pageSeq1, bubblePassive)
  8837. document.addEventListener('yt-navigate-redirect', pageSeq1, bubblePassive)
  8838. document.addEventListener('yt-page-data-fetched', pageSeq2, bubblePassive)
  8839. document.addEventListener("yt-navigate-finish", pageSeq3, bubblePassive)
  8840. //yt-navigate-redirect
  8841. //yt-page-data-fetched
  8842. //yt-navigate-error
  8843. //yt-navigate-start
  8844. //yt-page-manager-navigate-start
  8845. //yt-navigate
  8846. //yt-navigate-cache
  8847.  
  8848. globalHook('yt-page-data-fetched', generalLog901)
  8849. //globalHook('yt-rendererstamper-finished',generalLog901)
  8850. globalHook('yt-page-data-updated', generalLog901)
  8851. globalHook('yt-player-updated', generalLog901)
  8852. globalHook('yt-watch-comments-ready', generalLog901)
  8853. globalHook('yt-page-type-changed', generalLog901)
  8854. globalHook('data-changed', generalLog901)
  8855. globalHook('yt-navigate-finish', generalLog901)
  8856. globalHook('yt-navigate-redirect', generalLog901)
  8857. globalHook('yt-navigate-error', generalLog901)
  8858. globalHook('yt-navigate-start', generalLog901)
  8859. globalHook('yt-page-manager-navigate-start', generalLog901)
  8860. globalHook('yt-navigate', generalLog901)
  8861. globalHook('yt-navigate-cache', generalLog901)
  8862.  
  8863.  
  8864. // ---------------------------------------------------------------------------------------------
  8865.  
  8866.  
  8867. function manualResizeT() {
  8868.  
  8869. if (!scriptEnable) return;
  8870. if (pageType !== 'watch') return;
  8871. //lastResizeAt = Date.now();
  8872.  
  8873. if ((wls.layoutStatus & LAYOUT_FULLSCREEN) === LAYOUT_FULLSCREEN) {
  8874.  
  8875. } else if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === 0) {
  8876. // single col
  8877.  
  8878. getDMPromise().then(() => {
  8879. singleColumnScrolling(true)
  8880. });
  8881.  
  8882. } else {
  8883. // two cols
  8884.  
  8885. updateFloatingSlider();
  8886.  
  8887. }
  8888.  
  8889.  
  8890. }
  8891. //let lastResizeAt = 0;
  8892. const resizeCount = ControllerID();
  8893. window.addEventListener('resize', function (evt) {
  8894.  
  8895. if (evt.isTrusted === true) {
  8896. //console.log(evt)
  8897. let tcw = resizeCount.inc();
  8898. Promise.resolve(0).then(() => {
  8899. if (tcw !== resizeCount.valueOf()) return;
  8900. setTimeout(() => {
  8901. // avoid duplicate calling during resizing
  8902. if (tcw !== resizeCount.valueOf()) return;
  8903.  
  8904. resizeCount.set(0);
  8905. manualResizeT();
  8906. dispatchCommentRowResize();
  8907. }, 160);
  8908. });
  8909. }
  8910.  
  8911. }, bubblePassive)
  8912.  
  8913.  
  8914. document.addEventListener("tyt-chat-popup", (evt) => {
  8915.  
  8916. let detail = (evt || 0).detail
  8917. if (!detail) return
  8918. const { popuped } = detail
  8919. if (typeof popuped !== 'boolean') return;
  8920.  
  8921. let ytdFlexyElm = es.ytdFlexy
  8922. if (!ytdFlexyElm) return
  8923.  
  8924. ytdFlexyElm.classList.toggle('tyt-chat-popup', popuped)
  8925. if (popuped === true) {
  8926. enableLivePopupCheck = true;
  8927. ytBtnSetTheater()
  8928. } else {
  8929. enableLivePopupCheck = false;
  8930. ytBtnCancelTheater()
  8931. }
  8932.  
  8933. })
  8934.  
  8935.  
  8936.  
  8937. let doingSelectionChange = false;
  8938. document.addEventListener("keyup", (evt) => {
  8939. if (!evt || !evt.target || !evt.key) return;
  8940. if (doingSelectionChange) {
  8941. if (!evt.shiftKey || evt.key.indexOf("Shift") == 0) {
  8942. doingSelectionChange = false;
  8943. }
  8944. }
  8945. })
  8946.  
  8947. document.addEventListener("keydown", (evt) => {
  8948. if (!evt || !evt.target || !evt.key) return;
  8949. if (evt.shiftKey && evt.key.indexOf("Arrow") == 0) {
  8950. try {
  8951. if (doingSelectionChange || !window.getSelection().isCollapsed) {
  8952. evt.stopImmediatePropagation();
  8953. evt.stopPropagation();
  8954. doingSelectionChange = true;
  8955. }
  8956.  
  8957. } catch (e) {
  8958.  
  8959. }
  8960. }
  8961. }, true);
  8962.  
  8963. let userActivation = false;
  8964.  
  8965. document.addEventListener('click', function () {
  8966. userActivation = true;
  8967.  
  8968.  
  8969. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled && userActivation && videoInsected) {
  8970. getDMPromise().then(delayedClickHandler);
  8971. }
  8972.  
  8973. });
  8974.  
  8975. // new comment count fetch way
  8976. document.addEventListener('ytd-comments-data-changed', function (evt) {
  8977. const hasData = (evt.detail || 0).hasData;
  8978. if (hasData === false) {
  8979. // this is much effective to clear the counting text
  8980. emptyCommentSection();
  8981. }
  8982. innerDOMCommentsCountLoader(true);
  8983. checkAndMakeNewCommentFetch();
  8984. }, true);
  8985.  
  8986. document.addEventListener('ytd-comments-header-changed', function () {
  8987. const res = innerDOMCommentsCountLoader(true);
  8988. if (res && res.newFound === true && res.length === 1 && res[0].isLatest && res[0].isNew) {
  8989. if (renderDeferred.resolved && fetchCounts.new) {
  8990. // force refresh count dom
  8991. fetchCounts.fetched = false;
  8992. Q.comments_section_loaded = 0;
  8993. }
  8994. }
  8995. checkAndMakeNewCommentFetch();
  8996. }, true);
  8997.  
  8998. document.addEventListener("tabview-plugin-loaded", () => {
  8999.  
  9000. scriptletDeferred.resolve();
  9001.  
  9002. if (MINIVIEW_BROWSER_ENABLE) {
  9003. sendToPageScript(document, "tabview-miniview-browser-enable");
  9004. }
  9005.  
  9006.  
  9007. }, false)
  9008.  
  9009. if (isGMAvailable() && typeof GM_registerMenuCommand === 'function') {
  9010.  
  9011. let dialog = null;
  9012. function createDialog() {
  9013.  
  9014. const _themeProps_ = {
  9015. dialogBackgroundColor: '#f6f6f6',
  9016. dialogBackgroundColorDark: '#23252a',
  9017. backdropColor: '#b5b5b568',
  9018. textColor: '#111111',
  9019. textColorDark: '#f0f3f4',
  9020. zIndex: 60000,
  9021. fontSize: '10pt',
  9022. dialogMinWidth: '32px',
  9023. dialogMinHeight: '24px',
  9024. };
  9025.  
  9026. class VJSD extends VanillaJSDialog {
  9027.  
  9028. get themeProps() {
  9029. return _themeProps_
  9030. }
  9031.  
  9032. isDarkTheme() {
  9033. return document.documentElement.hasAttribute('dark');
  9034. }
  9035.  
  9036. onBeforeShow() {
  9037. const es = this.es;
  9038. if ('checkboxSelectionDisplay' in es) {
  9039. es.checkboxSelectionDisplay.textContent = '';
  9040. }
  9041. const setDefaultTabTick = (myDefaultTab) => {
  9042. for (const checkbox of document.getElementsByName('tabview-tab-default')) {
  9043. checkbox.checked = checkbox.value === myDefaultTab;
  9044. }
  9045. }
  9046. function getDefaultTabBtnSetting(store) {
  9047. if (!store) { } else {
  9048. let myDefaultTab = store[key_default_tab];
  9049. if (!myDefaultTab || typeof myDefaultTab !== 'string' || !/^\#[a-zA-Z\_\-\+]+$/.test(myDefaultTab)) {
  9050. } else {
  9051. if (document.querySelector(`.tab-btn[tyt-tab-content="${myDefaultTab}"]`)) return setDefaultTabTick(myDefaultTab);
  9052. }
  9053. }
  9054. setDefaultTabTick(null);
  9055. }
  9056. let store = getStore();
  9057. getDefaultTabBtnSetting(store);
  9058. }
  9059.  
  9060. onFirstCreation() {
  9061.  
  9062. const S = this.S; /* this is the global method */
  9063.  
  9064. /* on top of the setup function, override the icon widget on global method */
  9065. S.widgets.icon = (iconTag) => {
  9066. return S.ce('i', { className: 'vjsd-icon fa-solid fa-' + iconTag });
  9067. }
  9068.  
  9069. /* you might also overide `S.importCSS` by the use of Userscript Manager's import */
  9070. S.importCSS(
  9071. 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/fontawesome.min.css#sha512=SgaqKKxJDQ/tAUAAXzvxZz33rmn7leYDYfBP+YoMRSENhf3zJyx3SBASt/OfeQwBHA1nxMis7mM3EV/oYT6Fdw==',
  9072. // 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/brands.min.css',
  9073. 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/solid.min.css#sha512=yDUXOUWwbHH4ggxueDnC5vJv4tmfySpVdIcN1LksGZi8W8EVZv4uKGrQc0pVf66zS7LDhFJM7Zdeow1sw1/8Jw=='
  9074. );
  9075.  
  9076. /* load CSS files, etc - You might overide the `getTheme()` in VanillaJSDialog */
  9077. this.themeSetup();
  9078. }
  9079.  
  9080. /* init is called after setup function is called */
  9081. init() {
  9082. const S = this.S; /* this is the global method */
  9083.  
  9084. const es = this.es; /* this is a store for HTMLElements binded to this dialog */
  9085.  
  9086. es.dialog = S.ce('div', {
  9087. className: 'vjsd-dialog'
  9088. }, {
  9089. '__vjsd__': ''
  9090. });
  9091.  
  9092. es.dialog.append(
  9093. es.header = S.ce('div', {
  9094. className: 'vjsd-dialog-header vjsd-hflex'
  9095. }),
  9096. es.body = S.ce('div', {
  9097. className: 'vjsd-dialog-body vjsd-gap-2 vjsd-overscroll-none vjsd-vflex'
  9098. }),
  9099. es.footer = S.ce('div', {
  9100. className: 'vjsd-dialog-footer vjsd-hflex'
  9101. }),
  9102.  
  9103. );
  9104.  
  9105. es.header.append(
  9106. S.widgets.icon('circle-info', (a) => {
  9107.  
  9108. }),
  9109. S.widgets.title('Tabview Youtube - Change Default Tab', {
  9110. className: 'vjsd-flex-fill'
  9111. }),
  9112. S.widgets.buttonIcon('square-xmark', {
  9113. 'vjsd-clickable': '#dialogXmark'
  9114. })
  9115. );
  9116.  
  9117. const checkBoxChanged = () => {
  9118. let elmChoice1 = [...document.getElementsByName('tabview-tab-default')].filter(e => e.checked).map(e => e.value);
  9119. console.assert(elmChoice1.length <= 1);
  9120. es.checkboxSelectionDisplay.textContent = elmChoice1.length === 1 ? `The default tab will be set to ${elmChoice1[0]}` : `The default tab will be reset.`;
  9121. }
  9122.  
  9123. es.body.append(
  9124. S.widgets.labeledRadio('vjsd-checkbox1 vjsd-checkbox-tick', 'Info', (elmLabel, elmInput) => {
  9125. elmInput.name = 'tabview-tab-default';
  9126. elmInput.value = '#tab-info';
  9127. es.checkbox1 = elmInput;
  9128. elmInput.addEventListener('change', checkBoxChanged)
  9129. elmLabel.style.fontSize = '200%';
  9130. }),
  9131. S.widgets.labeledRadio('vjsd-checkbox1 vjsd-checkbox-tick', 'Comment', (elmLabel, elmInput) => {
  9132. elmInput.name = 'tabview-tab-default';
  9133. elmInput.value = '#tab-comments';
  9134. es.checkbox2 = elmInput;
  9135. elmInput.addEventListener('change', checkBoxChanged)
  9136. elmLabel.style.fontSize = '200%';
  9137. }),
  9138. S.widgets.labeledRadio('vjsd-checkbox1 vjsd-checkbox-tick', 'Video', (elmLabel, elmInput) => {
  9139. elmInput.name = 'tabview-tab-default';
  9140. elmInput.value = '#tab-videos';
  9141. es.checkbox3 = elmInput;
  9142. elmInput.addEventListener('change', checkBoxChanged)
  9143. elmLabel.style.fontSize = '200%';
  9144. }),
  9145. es.checkboxSelectionDisplay = S.ce('div', { className: 'vjsd-custom-widget' })
  9146. );
  9147.  
  9148. const onXMarkClicked = () => {
  9149. this.dismiss();
  9150. }
  9151.  
  9152. const onClearClicked = () => {
  9153. es.checkbox1.checked = false;
  9154. es.checkbox2.checked = false;
  9155. es.checkbox3.checked = false;
  9156. checkBoxChanged();
  9157. }
  9158.  
  9159. const onConfirmClicked = () => {
  9160. let myDefaultTab = null;
  9161. for (const checkbox of document.getElementsByName('tabview-tab-default')) {
  9162. if (checkbox.checked) myDefaultTab = checkbox.value;
  9163. }
  9164. myDefaultTab = myDefaultTab || null;
  9165. console.log(myDefaultTab)
  9166. setMyDefaultTab(myDefaultTab);
  9167. this.dismiss();
  9168. }
  9169.  
  9170. const onCancelClicked = () => {
  9171. this.dismiss();
  9172. }
  9173.  
  9174. es.footer.append(
  9175. es.clearButton = S.widgets.button('Clear', {
  9176. 'vjsd-clickable': '#clear'
  9177. }),
  9178. S.widgets.space(),
  9179. S.widgets.button('Cancel', {
  9180. 'vjsd-clickable': '#cancel'
  9181. }),
  9182. S.widgets.button('Confirm', {
  9183. 'vjsd-clickable': '#confirm'
  9184. }),
  9185. )
  9186.  
  9187. this.clickable('#cancel', onCancelClicked)
  9188. this.clickable('#clear', onClearClicked)
  9189. this.clickable('#confirm', onConfirmClicked)
  9190. this.clickable('#dialogXmark', onXMarkClicked);
  9191.  
  9192. this.backdrop = 'dismiss';
  9193. document.body.appendChild(es.dialog)
  9194. }
  9195. }
  9196.  
  9197. VJSD.setup1();
  9198. return new VJSD();
  9199. }
  9200.  
  9201. GM_registerMenuCommand("Change Default Tab", function () {
  9202. dialog = dialog || createDialog();
  9203. dialog.show();
  9204. });
  9205.  
  9206. /*
  9207. GM_registerMenuCommand("Default Tab: NULL", function () {
  9208. setMyDefaultTab(null);
  9209. });
  9210. GM_registerMenuCommand("Default Tab: Info", function () {
  9211. setMyDefaultTab("#tab-info");
  9212. });
  9213. GM_registerMenuCommand("Default Tab: Comments", function () {
  9214. setMyDefaultTab("#tab-comments");
  9215. });
  9216. GM_registerMenuCommand("Default Tab: Video", function () {
  9217. setMyDefaultTab("#tab-videos");
  9218. });
  9219. */
  9220. }
  9221.  
  9222.  
  9223. handleDOMAppear('#tabview-controller', () => { }); // dummy
  9224. document.documentElement.appendChild(document.createElement('tabview-controller')).id = 'tabview-tabs-hide-controller';
  9225. document.documentElement.appendChild(document.createElement('tabview-controller')).id = 'tabview-default-tab-controller';
  9226.  
  9227. document.documentElement.setAttribute('plugin-tabview-youtube', `${scriptVersionForExternal}`)
  9228. if (document.documentElement.getAttribute('tabview-unwrapjs')) {
  9229. document.dispatchEvent(new CustomEvent("tabview-plugin-loaded"))
  9230. }
  9231.  
  9232.  
  9233. // function nestedObjectFlatten(prefix, obj) {
  9234. // let ret = {};
  9235. // let _prefix = prefix ? `${prefix}.` : '';
  9236. // let isObject = (obj && typeof obj == 'object' && obj.constructor.name == 'Object');
  9237. // let isArray = (obj && typeof obj == 'object' && obj.constructor.name == 'Array');
  9238. // const f = (k, v) => {
  9239. // let isObject = (v && typeof v == 'object' && v.constructor.name == 'Object');
  9240. // let isArray = (v && typeof v == 'object' && v.constructor.name == 'Array');
  9241. // if (isObject || isArray) {
  9242. // let r = nestedObjectFlatten(k, v)
  9243. // for (const w in r) {
  9244. // ret[`${_prefix}${w}`] = r[w];
  9245. // }
  9246. // } else {
  9247. // ret[`${_prefix}${k}`] = v;
  9248. // }
  9249. // }
  9250. // if (isObject) {
  9251. // for (const k in obj) {
  9252. // let v = obj[k];
  9253. // f(k, v);
  9254. // }
  9255. // } else if (isArray) {
  9256. // let idx = 0;
  9257. // for (const v of obj) {
  9258. // let k = `[${idx}]`;
  9259. // f(k, v);
  9260. // idx++;
  9261. // }
  9262. // }
  9263. // return ret;
  9264. // }
  9265.  
  9266. /*
  9267.  
  9268. for(const p of document.querySelectorAll('ytd-watch-flexy *')){ let m = p.data; if(!m)continue; console.log(m)}
  9269.  
  9270. function objec
  9271.  
  9272. */
  9273.  
  9274.  
  9275. //Object.keys($0).filter(key=>!(key in $0.constructor.prototype))
  9276.  
  9277. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML'))
  9278. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML')).filter(k=>$0 instanceof window[k])
  9279.  
  9280.  
  9281. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  9282.  
  9283. /*
  9284. fix bug for comment section - version 1.8.7
  9285. This issue is the bug in browser's rendering
  9286. I guess, this is due to the lines clamp with display:-webkit-box
  9287. use stupid coding to let it re-render when its content become visible
  9288. /*
  9289.  
  9290. ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander {
  9291. color: var(--yt-spec-text-primary);
  9292. display: -webkit-box;
  9293. overflow: hidden;
  9294. max-height: none;
  9295. -webkit-box-orient: vertical;
  9296. -webkit-line-clamp: var(--ytd-expander-max-lines, 4);
  9297. }
  9298.  
  9299. // v1.8.36 imposed a effective solution for fixing this bug
  9300.  
  9301. */
  9302.  
  9303. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  9304.  
  9305.  
  9306. /**
  9307. *
  9308.  
  9309.  
  9310. f.initChildrenObserver=function(){var a=this;this.observer=new MutationObserver(function(){return a.childrenChanged()});
  9311. this.observer.observe(this.$.content,{subtree:!0,childList:!0,attributes:!0});this.childrenChanged()};
  9312. f.childrenChanged=function(){var a=this;this.alwaysToggleable?this.canToggle=this.alwaysToggleable:this.canToggleJobId||(this.canToggleJobId=window.requestAnimationFrame(function(){$h(function(){a.canToggleJobId=0;a.calculateCanCollapse()})}))};
  9313.  
  9314.  
  9315. f.onIronResize=function(){this.recomputeOnResize&&this.childrenChanged()};
  9316.  
  9317.  
  9318. onButtonClick_:function(){this.fire("yt-close-upsell-dialog")},
  9319. computeHasHeader_:function(a){return!!a.headerBackgroundImage}});var geb;var heb;var ieb;var jeb;var xI=function(){var a=L.apply(this,arguments)||this;a.alignAuto=!1;a.collapsed=!0;a.isToggled=!1;a.alwaysCollapsed=!1;a.canToggle=!0;a.collapsedHeight=80;a.disableToggle=!1;a.alwaysToggleable=!1;a.reversed=!1;a.shouldUseNumberOfLines=!1;a.recomputeOnResize=!1;a.canToggleJobId=0;return a};
  9320. n(xI,L);f=xI.prototype;f.alwaysToggleableChanged=function(){this.alwaysToggleable&&(this.canToggle=!0)};
  9321.  
  9322.  
  9323. f.calculateCanCollapse=function(){this.canToggle=this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  9324. f.detachObserver=function(){this.observer&&this.observer.disconnect()};
  9325.  
  9326. *
  9327. *
  9328. *
  9329. */
  9330.  
  9331.  
  9332. })({
  9333. requestAnimationFrame: (typeof webkitRequestAnimationFrame === 'function' ? webkitRequestAnimationFrame : requestAnimationFrame),
  9334. cancelAnimationFrame: (typeof webkitRequestAnimationFrame === 'function' ? webkitRequestAnimationFrame : requestAnimationFrame)
  9335. });
  9336. // console.timeEnd("Tabview Youtube Init Script")
  9337.  
  9338. //# sourceURL=debug://tabview-youtube/tabview.content.js
  9339.  
  9340. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  9341. }
  9342.  
  9343.  
  9344. ; !(async () => {
  9345. 'use strict';
  9346.  
  9347. const o = [{
  9348. id: 'tabview-css-content',
  9349. resTag: 'contentCSS',
  9350. }, {
  9351. id: 'tabview-css-chat',
  9352. resTag: 'chatCSS'
  9353. }, {
  9354. id: 'tabview-css-control',
  9355. resTag: 'controlCSS'
  9356. }];
  9357.  
  9358. const Promise = (async () => { })().constructor;
  9359.  
  9360. const contents = o.map(e => {
  9361. e = e.resTag;
  9362. return GM_getResourceText(e) || GM.getResourceText(e) || '';
  9363. });
  9364.  
  9365. const texts = await Promise.all(contents);
  9366.  
  9367. if (document.documentElement === null) {
  9368. await new Promise(resolve => {
  9369. let mo = new MutationObserver(() => {
  9370. if (!mo || document.documentElement === null) return;
  9371. mo.disconnect();
  9372. mo.takeRecords();
  9373. mo = null;
  9374. resolve();
  9375. });
  9376. mo.observe(document, { childList: true, subtree: true });
  9377. });
  9378. }
  9379.  
  9380. const target = document.head || document.documentElement;
  9381. let i = 0;
  9382. for (const e of o) {
  9383. const text = texts[i++];
  9384. if (text) {
  9385. const styleNode = document.createElement('style');
  9386. styleNode.id = e.id;
  9387. styleNode.textContent = text;
  9388. target.appendChild(styleNode);
  9389. }
  9390. }
  9391.  
  9392. main();
  9393.  
  9394. })();
  9395.  
  9396. //# sourceURL=debug://tabview-youtube/tabview.content.js

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址