Tabview Youtube

把Youtube Videos中的评论及视频列表制作成Tabs

当前为 2023-01-31 提交的版本,查看 最新版本

  1. /*
  2.  
  3. MIT License
  4.  
  5. Copyright (c) 2021 cyfung1031
  6.  
  7. Permission is hereby granted, free of charge, to any person obtaining a copy
  8. of this software and associated documentation files (the "Software"), to deal
  9. in the Software without restriction, including without limitation the rights
  10. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. copies of the Software, and to permit persons to whom the Software is
  12. furnished to do so, subject to the following conditions:
  13.  
  14. The above copyright notice and this permission notice shall be included in all
  15. copies or substantial portions of the Software.
  16.  
  17. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. SOFTWARE.
  24.  
  25. */
  26. // ==UserScript==
  27. // @name Tabview Youtube
  28. // @name:en Tabview Youtube
  29. // @description Make comments and lists into tabs for YouTube Videos
  30. // @description:en Make comments and lists into tabs for YouTube Videos
  31. // @name:ja Tabview Youtube
  32. // @description:ja YouTube動画のコメントやリストなどをタブに作成します
  33. // @name:zh-TW Tabview Youtube
  34. // @name:zh-CN Tabview Youtube
  35. // @description:zh-TW 把Youtube Videos中的評論及影片清單製作成Tabs
  36. // @description:zh-CN 把Youtube Videos中的评论及视频列表制作成Tabs
  37. // @name:ru Tabview Youtube
  38. // @description:ru Сделайте описание, комментарии и список видео в виде вкладок для видео на YouTube
  39.  
  40. // @version 4.3.0
  41. // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/bd0ce711b4272eebc226c6cf6d02c7dc0a9c5205/css/style_content.css
  42. // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/bd0ce711b4272eebc226c6cf6d02c7dc0a9c5205/js/injection_script_1.js
  43.  
  44. // @namespace http://tampermonkey.net/
  45. // @author CY Fung
  46. // @license MIT
  47. // @supportURL https://github.com/cyfung1031/Tabview-Youtube
  48. // @run-at document-start
  49. // @match https://www.youtube.com/*
  50. // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
  51. // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
  52.  
  53. // @compatible edge Edge [Blink] >= 79; Tampermonkey (Beta) / Violetmonkey
  54. // @compatible chrome Chrome >= 54; Tampermonkey (Beta) / Violetmonkey
  55. // @compatible firefox FireFox / Waterfox (Classic) >= 55; Tampermonkey / Violetmonkey
  56. // @compatible opera Opera >= 41; Tampermonkey (Beta) / Violetmonkey
  57. // @compatible safari Safari >= 12.1
  58.  
  59. // @grant GM_getResourceText
  60. // @noframes
  61. // ==/UserScript==
  62.  
  63. /* jshint esversion:8 */
  64.  
  65. function main(){
  66. // MIT License
  67. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81.  
  82.  
  83.  
  84.  
  85. "use strict";
  86. // console.time("Tabview Youtube Init Script")
  87. -(function mainBody() {
  88. 'use strict';
  89.  
  90. function inIframe() {
  91. try {
  92. return window.self !== window.top;
  93. } catch (e) {
  94. return true;
  95. }
  96. }
  97.  
  98. if (inIframe()) return;
  99.  
  100. //if (!$) return;
  101.  
  102. /**
  103. * SVG resources:
  104. * <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>
  105. */
  106.  
  107. const scriptVersionForExternal = '2022/12/04';
  108.  
  109. const isMyScriptInChromeRuntime = () => typeof GM === 'undefined' && typeof ((((window || 0).chrome || 0).runtime || 0).getURL) === 'function'
  110.  
  111. // https://yqnn.github.io/svg-path-editor/
  112. // https://vecta.io/nano
  113.  
  114. 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
  115. 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
  116. 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
  117. 0 0 3-1V71h2a12 12 90 0 0 12-12V17a12 12 90 0 0-12-12z"/>`.trim();
  118.  
  119. 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
  120. 66H9V68h9v10zm0-56H9V12h9v10zm22 56H27V68h13v10zm-3-25V36c0-2 2-3 4-2l12 8c2 1 2 4 0 5l-12 8c-2 1-4 0-4-2zm25
  121. 25H49V68h13v10zm0-56H49V12h13v10zm18 56h-9V68h9v10zm0-56h-9V12h9v10z"/>`.trim();
  122.  
  123. 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
  124. 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
  125. .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
  126. .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
  127. 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
  128. 0 0 1.4-.4l-.6 2.2zm-3.8-35.2a1 1 0 010 8.6 1 1 0 010-8.6z"/>`.trim();
  129.  
  130. const svgPlayList = `<path d="M0 3h12v2H0zm0 4h12v2H0zm0 4h8v2H0zm16 0V7h-2v4h-4v2h4v4h2v-4h4v-2z"/>`.trim();
  131.  
  132. const svgDiag1 = `<svg stroke="currentColor" fill="none"><path d="M8 2h2v2M7 5l3-3m-6 8H2V8m0 2l3-3"/></svg>`;
  133. const svgDiag2 = `<svg stroke="currentColor" fill="none"><path d="M7 3v2h2M7 5l3-3M5 9V7H3m-1 3l3-3"/></svg>`;
  134.  
  135. const REMOVE_DUPLICATE_INFO = true;
  136. const MINIVIEW_BROWSER_ENABLE = true;
  137. const DEBUG_LOG = false;
  138.  
  139. let _isPageFirstLoaded = true
  140.  
  141. async function makeTytLock() {
  142. let c = 8;
  143. while (!document.documentElement) {
  144. if(--c === 0) return
  145. await new Promise(window.requestAnimationFrame)
  146. }
  147. if (_isPageFirstLoaded) document.documentElement.setAttribute('tyt-lock', '')
  148. }
  149. if (location.pathname === '/watch') makeTytLock()
  150. /*
  151.  
  152. youtube page
  153.  
  154. = Init::browse
  155. yt-page-data-fetched
  156. data-changed...
  157. yt-page-data-updated
  158. yt-navigate-finish
  159. data-changed...
  160. yt-watch-comments-ready
  161. = browse -> watch
  162. yt-player-updated
  163. yt-navigate
  164. yt-navigate-start
  165. yt-page-type-changed
  166. yt-player-updated
  167. yt-page-data-fetched
  168. yt-navigate-finish
  169. data-changed...
  170. yt-page-data-updated
  171. data-changed...
  172. yt-watch-comments-ready
  173. data-changed...
  174.  
  175. = watch -> watch
  176. = click video on meta panel // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo
  177. yt-navigate
  178. yt-navigate-start
  179. data-changed
  180. yt-player-updated
  181. yt-page-data-fetched
  182. yt-navigate-finish
  183. data-changed...
  184. yt-page-data-updated
  185. data-changed...
  186. yt-watch-comments-ready
  187. data-changed...
  188.  
  189. = watch -> browse (miniview)
  190. yt-navigate-cache
  191. yt-page-data-fetched
  192. yt-page-type-changed
  193. yt-page-data-updated
  194. yt-navigate-finish
  195.  
  196. = browse (miniview) -> watch (Restore)
  197. yt-navigate-cache
  198. yt-page-data-fetched
  199. yt-navigate-finish
  200. yt-page-type-changed
  201. yt-page-data-updated
  202. data-changed...
  203. yt-watch-comments-ready
  204.  
  205. = watch -> search (miniview)
  206. yt-navigate
  207. yt-navigate-start
  208. data-changed
  209. yt-page-data-fetched
  210. yt-page-type-changed
  211. data-changed
  212. yt-page-data-updated
  213. yt-navigate-finish
  214. data-changed...
  215.  
  216. = Init::search
  217. yt-page-data-fetched
  218. data-changed
  219. yt-page-data-updated
  220. yt-navigate-finish
  221. data-changed...
  222. yt-watch-comments-ready
  223.  
  224. = Init::watch
  225. yt-page-data-fetched
  226. yt-navigate-finish
  227. data-changed...
  228. yt-page-data-updated
  229. data-changed...
  230. yt-watch-comments-ready
  231. yt-player-updated
  232. data-changed...
  233.  
  234. = watch -> watch (history back)
  235. yt-player-updated
  236. yt-page-data-fetched
  237. yt-navigate-finish
  238. data-changed...
  239. yt-page-data-updated
  240. data-changed...
  241. yt-watch-comments-ready
  242.  
  243. = watch -> click video time // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo
  244. yt-navigate
  245.  
  246. */
  247.  
  248.  
  249.  
  250. const LAYOUT_VAILD = 1;
  251.  
  252. const LAYOUT_TWO_COLUMNS = 2;
  253. const LAYOUT_THEATER = 4;
  254. const LAYOUT_FULLSCREEN = 8;
  255. const LAYOUT_CHATROOM = 16;
  256. const LAYOUT_CHATROOM_COLLAPSED = 32;
  257. const LAYOUT_TAB_EXPANDED = 64;
  258. const LAYOUT_ENGAGEMENT_PANEL_EXPANDED = 128;
  259. const LAYOUT_CHATROOM_EXPANDED = 256;
  260. const LAYOUT_DONATION_SHELF_EXPANDED = 512;
  261.  
  262. const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  263.  
  264. const nullFunc = function () { };
  265.  
  266.  
  267. const iframeCSS = (() => {
  268. return `
  269. body {
  270. --tabview-msg-cursor: default;
  271. --tabview-msg-pointer-events: none;
  272. --tabview-img-pointer-events: auto;
  273. }
  274. body.tabview-allow-pointer-events {
  275. --tabview-msg-cursor: '-NULL-';
  276. --tabview-msg-pointer-events: '-NULL-';
  277. --tabview-img-pointer-events: '-NULL-';
  278. }
  279. body #input-panel.yt-live-chat-renderer::after {
  280. background: transparent;
  281. }
  282. .style-scope.yt-live-chat-item-list-renderer {
  283. box-sizing: border-box;
  284. }
  285. yt-live-chat-text-message-renderer:nth-last-child(-n+30):hover #menu.yt-live-chat-text-message-renderer {
  286. transition-delay: 87ms;
  287. }
  288. yt-live-chat-text-message-renderer #menu.yt-live-chat-text-message-renderer {
  289. transition-delay: 1ms;
  290. }
  291. #item.style-scope.yt-live-chat-item-list-renderer,
  292. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  293. transition-delay: 42ms;
  294. }
  295. yt-live-chat-item-list-renderer img[alt] {
  296. pointer-events: auto;
  297. }
  298. body yt-live-chat-item-list-renderer img[alt]~tp-yt-paper-tooltip,
  299. body yt-live-chat-item-list-renderer #image~tp-yt-paper-tooltip {
  300. --paper-tooltip-delay-in: 120ms !important;
  301. white-space: nowrap;
  302. }
  303. #items.style-scope.yt-live-chat-item-list-renderer>yt-live-chat-text-message-renderer.yt-live-chat-item-list-renderer {
  304. --tabview-chat-message-display: block;
  305. --tabview-chat-message-mt: 2px;
  306. --tabview-chat-message-mb: 4px;
  307. }
  308. #message.yt-live-chat-text-message-renderer {
  309. display: var(--tabview-chat-message-display);
  310. margin-top: var(--tabview-chat-message-mt);
  311. margin-bottom: var(--tabview-chat-message-mb);
  312. }
  313. [collapsed] #message.yt-live-chat-text-message-renderer {
  314. --tabview-chat-message-display: 'VOID';
  315. --tabview-chat-message-mt: 'VOID';
  316. --tabview-chat-message-mb: 'VOID';
  317. }
  318. @supports (contain: layout paint style) {
  319.  
  320. /*
  321. contain: layout paint style;
  322. // #item-offset uses transform, it is buggy in Opera 93.0 with contain: layout & paint
  323. */
  324.  
  325. body yt-live-chat-app {
  326. contain: size layout paint style;
  327. /* content-visibility: auto; */
  328. transform: translate3d(0, 0, 0);
  329. overflow: hidden;
  330. }
  331.  
  332. #items.style-scope.yt-live-chat-item-list-renderer{
  333. contain: layout paint style;
  334. }
  335.  
  336. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  337. contain: style;
  338. }
  339.  
  340. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  341. contain: size style;
  342. }
  343.  
  344. #contents.style-scope.yt-live-chat-item-list-renderer,
  345. #chat.style-scope.yt-live-chat-renderer,
  346. img.style-scope.yt-img-shadow[width][height] {
  347. contain: size layout paint style;
  348. }
  349.  
  350. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label],
  351. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label]>#container {
  352. contain: layout paint style;
  353. }
  354.  
  355.  
  356. yt-img-shadow#author-photo.style-scope {
  357. contain: layout paint style;
  358. /*
  359. content-visibility: auto;
  360. contain-intrinsic-size: 24px 24px;
  361. */
  362. }
  363.  
  364. yt-live-chat-text-message-renderer:not([author-is-owner]) #author-photo.style-scope.yt-live-chat-text-message-renderer,
  365. yt-live-chat-text-message-renderer:not([author-is-owner]) yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer {
  366. pointer-events: var(--tabview-msg-pointer-events);
  367. }
  368.  
  369. yt-live-chat-text-message-renderer:not([author-is-owner]) span#message.style-scope.yt-live-chat-text-message-renderer>img.emoji.yt-formatted-string.style-scope.yt-live-chat-text-message-renderer {
  370. cursor: var(--tabview-msg-cursor);
  371. }
  372.  
  373.  
  374. yt-live-chat-text-message-renderer:not([author-is-owner]) span#message.style-scope.yt-live-chat-text-message-renderer,
  375. yt-live-chat-paid-message-renderer #message.yt-live-chat-paid-message-renderer,
  376. yt-live-chat-text-message-renderer:not([author-is-owner]) #timestamp.style-scope.yt-live-chat-text-message-renderer,
  377. yt-live-chat-membership-item-renderer #header-content.style-scope.yt-live-chat-membership-item-renderer,
  378. yt-live-chat-membership-item-renderer #timestamp.style-scope.yt-live-chat-membership-item-renderer,
  379. yt-live-chat-paid-message-renderer #header-content.yt-live-chat-paid-message-renderer,
  380. yt-live-chat-paid-message-renderer #timestamp.style-scope.yt-live-chat-paid-message-renderer,
  381. yt-live-chat-paid-sticker-renderer #content.style-scope.yt-live-chat-paid-sticker-renderer,
  382. yt-live-chat-paid-sticker-renderer #timestamp.style-scope.yt-live-chat-paid-sticker-renderer {
  383. cursor: var(--tabview-msg-cursor);
  384. pointer-events: var(--tabview-msg-pointer-events);
  385. }
  386.  
  387. yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  388. yt-live-chat-membership-item-renderer.style-scope.yt-live-chat-item-list-renderer,
  389. yt-live-chat-paid-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  390. yt-live-chat-banner-manager.style-scope.yt-live-chat-item-list-renderer {
  391. contain: layout style;
  392. }
  393.  
  394. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  395. contain: layout paint style;
  396. }
  397.  
  398. /*
  399. yt-live-chat-banner-renderer[collapsed] #contents, yt-live-chat-banner-renderer[collapsed] #contents * {
  400.  
  401. content-visibility: visible !important;
  402. contain: none !important;
  403.  
  404. }
  405. */
  406.  
  407. /* YouTube Native Bug - style="height:0px; translateY(0px);" */
  408. yt-live-chat-banner-renderer[collapsed]:has(#contents[style*="0px;"][style*="translateY(0px);"]) #header.yt-live-chat-banner-renderer{
  409. position:absolute;
  410. }
  411.  
  412. yt-live-chat-banner-renderer[collapsed]:has(#contents[style*="0px;"][style*="translateY(0px);"]) #contents.yt-live-chat-banner-renderer{
  413. height:auto !important;
  414. transform:initial !important;
  415. }
  416.  
  417. }
  418. #chat-messages tp-yt-iron-dropdown#dropdown.style-scope.tp-yt-paper-menu-button {
  419. margin-right: var(--ytd-margin-12x);
  420. }
  421.  
  422.  
  423. tp-yt-iron-dropdown.yt-live-chat-app[vertical-align="top"] ytd-menu-popup-renderer.yt-live-chat-app {
  424. max-height: 60vh !important; /* override style */
  425. }
  426.  
  427. `.trim();
  428. });
  429.  
  430.  
  431. let iframePointEventsAllow = false; // default to discard unnecessary mouse events for iframe
  432.  
  433. let scriptEnable = false;
  434.  
  435. let comments_loader = 0; // for comment count (might omit)
  436.  
  437. let cmTime = 0;
  438. const mTime = Date.now() - 152000000;
  439.  
  440. //let lastScrollFetch = 0;
  441. //let lastOffsetTop = 0;
  442. let mtf_forceCheckLiveVideo_disable = 0;
  443.  
  444. let tabsUiScript_setclick = false;
  445. let pageFetchedData = null; // data object; for future use
  446. let pageFetchedDataVideoId = null; // integer; for comment checking
  447. let pageType = null; // pageType = 'watch', 'browse', 'playlist', ...
  448. let chatroomDetails = null;
  449. let switchTabActivity_lastTab = null
  450.  
  451. let lstTab = null;
  452.  
  453. let storeLastPanel = null; // WeakRef
  454.  
  455.  
  456. let mtf_chatBlockQ = null; // for chat layout status change
  457.  
  458. let enableHoverSliderDetection = false; // for hover slider
  459.  
  460.  
  461. let firstLoadStatus = 2 | 8; // for page init
  462.  
  463.  
  464. let m_last_count = ''; // for comment count
  465.  
  466.  
  467.  
  468. let sVideosITO = null;
  469.  
  470. /** @type {WeakRef | null} */
  471. let ytdFlexy = null; // WeakRef
  472.  
  473. const Q = {}
  474. const settings = {
  475. defaultTab: "#tab-videos"
  476. };
  477.  
  478. const STORE_VERSION = 1;
  479. const STORE_key = 'userscript-tabview-settings';
  480.  
  481. let fetchCounts = {
  482. base: null,
  483. new: null,
  484. fetched: false,
  485. count: null,
  486. }
  487.  
  488. let pageLang = 'en';
  489. const langWords = {
  490. 'en': {
  491. //'share':'Share',
  492. 'info': 'Info',
  493. 'videos': 'Videos',
  494. 'playlist': 'Playlist'
  495. },
  496. 'jp': {
  497. //'share':'共有',
  498. 'info': '情報',
  499. 'videos': '動画',
  500. 'playlist': '再生リスト'
  501. },
  502. 'tw': {
  503. //'share':'分享',
  504. 'info': '資訊',
  505. 'videos': '影片',
  506. 'playlist': '播放清單'
  507. },
  508. 'cn': {
  509. //'share':'分享',
  510. 'info': '资讯',
  511. 'videos': '视频',
  512. 'playlist': '播放列表'
  513. },
  514. 'du': {
  515. //'share':'Teilen',
  516. 'info': 'Info',
  517. 'videos': 'Videos',
  518. 'playlist': 'Playlist'
  519. },
  520. 'fr': {
  521. //'share':'Partager',
  522. 'info': 'Info',
  523. 'videos': 'Vidéos',
  524. 'playlist': 'Playlist'
  525. },
  526. 'kr': {
  527. //'share':'공유',
  528. 'info': '정보',
  529. 'videos': '동영상',
  530. 'playlist': '재생목록'
  531. },
  532. 'ru': {
  533. //'share':'Поделиться',
  534. 'info': 'Описание',
  535. 'videos': 'Видео',
  536. 'playlist': 'Плейлист'
  537. }
  538. };
  539.  
  540. const getGMT = () => {
  541. let m = new Date('2023-01-01T00:00:00Z');
  542. return m.getDate() === 1 ? `+${m.getHours()}` : `-${24 - m.getHours()}`;
  543. };
  544.  
  545. function durationInfoTS(durationInfo) {
  546. /**
  547. * @type {{ hrs: number, mins: number, seconds: number }}
  548. */
  549. let _durationInfo = durationInfo
  550. return _durationInfo
  551. }
  552.  
  553. function formatDateReqTS(req) {
  554. /**
  555. * @type {{ bd1: KDate | undefined, bd2: KDate | undefined, isSameDay: number | undefined, durationInfo: object | undefined, formatDates: object | undefined }}
  556. */
  557. let _req = req
  558. return _req
  559. }
  560.  
  561. function durationLocaleEN(durationInfo) {
  562.  
  563. const { hrs, mins, seconds } = durationInfoTS(durationInfo)
  564. let ret = []
  565. ret.push(`Duration:`)
  566. if (hrs > 0) ret.push(`${hrs} ${hrs === 1 ? 'hour' : 'hours'}`)
  567. if (mins > 0) ret.push(`${mins} ${mins === 1 ? 'minute' : 'minutes'}`)
  568. if (seconds !== null) ret.push(`${seconds} ${seconds === 1 ? 'second' : 'seconds'}`)
  569. return ret.join(' ')
  570. }
  571.  
  572. function formatDateResultEN(type, req) {
  573.  
  574. const { bd1, bd2, durationInfo, formatDates } = formatDateReqTS(req)
  575.  
  576. switch (type) {
  577. case 0x200:
  578. return [
  579. `The livestream was in ${bd1.lokStringDate()} from ${bd1.lokStringTime()} to ${bd2.lokStringTime()}. [GMT${getGMT()}]`,
  580. durationLocaleEN(durationInfo)
  581. ].join('\n');
  582. case 0x210:
  583. return [
  584. `The livestream was from ${bd1.lokStringDate()} ${bd1.lokStringTime()} to ${bd2.lokStringDate()} ${bd2.lokStringTime()}. [GMT${getGMT()}]`,
  585. durationLocaleEN(durationInfo)
  586. ].join('\n');
  587. case 0x300:
  588. return `The livestream started at ${bd1.lokStringTime()} [GMT${getGMT()}] in ${bd1.lokStringDate()}.`;
  589. case 0x600:
  590. return `The video was uploaded in ${formatDates.uploadDate} and published in ${formatDates.publishDate}.`;
  591. case 0x610:
  592. return `The video was uploaded in ${formatDates.uploadDate}.`;
  593. case 0x700:
  594. return `The video was published in ${formatDates.publishDate}.`;
  595. }
  596. return '';
  597.  
  598. }
  599.  
  600. const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER', 'YTD-MENU-RENDERER']
  601.  
  602. let globalHook_symbols = [];
  603. let globalHook_hashs = {};
  604.  
  605.  
  606. let singleColumnScrolling_dt = 0;
  607.  
  608. let isStickyHeaderEnabled = false;
  609.  
  610. let theater_mode_changed_dt = 0;
  611. let detailsTriggerReset = false;
  612.  
  613.  
  614. let isMakeHeaderFloatCalled = false;
  615.  
  616. let _viTimeNum = 200;
  617. let _updateTimeAccum = 0;
  618.  
  619. /** @type {WeakMap<HTMLElement>} */
  620. let loadedCommentsDT = new WeakMap();
  621.  
  622.  
  623.  
  624. // for weakref variable management
  625. const es = {
  626. get ytdFlexy() {
  627. /** @type { HTMLElement | null } */
  628. let res = kRef(ytdFlexy);
  629. return res;
  630. },
  631. get storeLastPanel() {
  632. /** @type { HTMLElement | null } */
  633. let res = kRef(storeLastPanel);
  634. return res;
  635. }
  636. }
  637.  
  638.  
  639. const _console = new Proxy(console, {
  640. get(target, prop, receiver) {
  641. if (!DEBUG_LOG && prop === 'log') {
  642. return nullFunc
  643. }
  644. return Reflect.get(...arguments)
  645. }
  646. });
  647.  
  648. let generalLog901 = !DEBUG_LOG ? 0 : (evt) => {
  649. _console.log(901, evt.type)
  650. }
  651.  
  652. const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
  653. // https://caniuse.com/?search=observer
  654. // https://caniuse.com/?search=addEventListener%20passive
  655.  
  656. const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;
  657. const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;
  658.  
  659.  
  660. /** @type { (str: string) => (HTMLElement | null) } */
  661. const querySelectorFromAnchor = HTMLElement.prototype.querySelector; // nodeType==1 // since 2022/07/12
  662.  
  663. /** @type { (str: string) => (NodeList) } */
  664. const querySelectorAllFromAnchor = HTMLElement.prototype.querySelectorAll; // nodeType==1 // since 2022/07/12
  665. const closestDOM = HTMLElement.prototype.closest;
  666. //const elementRemove = HTMLElement.prototype.remove;
  667. //const elementContains = HTMLElement.prototype.contains; // since 2022/07/12
  668.  
  669.  
  670. /**
  671. *
  672. * @param {number} f bit flag
  673. * @param {number} w bit flag (with)
  674. * @param {number} wo bit flag (without)
  675. * @returns
  676. */
  677. const fT = function (f, w, wo) {
  678. return (f & (w | wo)) === w
  679. }
  680.  
  681. /* globals WeakRef:false */
  682.  
  683. /** @type {(o: Object | null) => WeakRef | null} */
  684. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  685.  
  686. /** @type {(wr: Object | null) => Object | null} */
  687. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  688.  
  689.  
  690. function setTimeout3(f) {
  691. Promise.race([new Promise(r => requestAnimationFrame(r)), new Promise(r => setTimeout(r, 300))]).then(f);
  692. }
  693.  
  694. const timeline = {
  695. // after initialized (initObserver)
  696. cn1: {},
  697. cn2: {},
  698. setTimeout( /** @type {TimerHandler} */ f,/** @type {number} */ d) {
  699. let cid = setTimeout(f, d)
  700. timeline.cn1[cid] = true
  701. return cid;
  702. },
  703. clearTimeout(/** @type {number} */ cid) {
  704. timeline.cn1[cid] = false; return clearTimeout(cid)
  705. },
  706. setInterval(/** @type {TimerHandler} */ f,/** @type {number} */ d) {
  707. let cid = setInterval(f, d);
  708. timeline.cn2[cid] = true
  709. return cid;
  710. },
  711. clearInterval(/** @type {number} */ cid) {
  712. timeline.cn2[cid] = false; return clearInterval(cid)
  713. },
  714. reset() {
  715. for (let cid in timeline.cn1) timeline.cn1[cid] && clearTimeout(cid)
  716. for (let cid in timeline.cn2) timeline.cn2[cid] && clearInterval(cid)
  717. timeline.cn1 = {}
  718. timeline.cn2 = {}
  719. }
  720. }
  721.  
  722.  
  723. // function prettyElm(/** @type {Element} */ elm) {
  724. // if (!elm || !elm.nodeName) return null;
  725. // const eId = elm.id || null;
  726. // const eClsName = elm.className || null;
  727. // return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  728. // }
  729.  
  730. // function extractTextContent(/** @type {Node} */ elm) {
  731. // 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, '')
  732. // }
  733.  
  734. function addScript(/** @type {string} */ scriptText) {
  735. const scriptNode = document.createElement('script');
  736. scriptNode.type = 'text/javascript';
  737. scriptNode.textContent = scriptText;
  738. try {
  739. document.documentElement.appendChild(scriptNode);
  740. } catch (e) {
  741. console.log('addScript Error', e)
  742. }
  743. return scriptNode;
  744. }
  745.  
  746. function addScriptByURL(/** @type {string} */ scriptURL) {
  747. const scriptNode = document.createElement('script');
  748. scriptNode.type = 'text/javascript';
  749. scriptNode.src = scriptURL;
  750. try {
  751. document.documentElement.appendChild(scriptNode);
  752. } catch (e) {
  753. console.log('addScriptByURL Error', e)
  754. }
  755. return scriptNode;
  756. }
  757.  
  758. function addStyle(/** @type {string} */ styleText, /** @type {HTMLElement | Document} */ container) {
  759. const styleNode = document.createElement('style');
  760. //styleNode.type = 'text/css';
  761. styleNode.textContent = styleText;
  762. (container || document.documentElement).appendChild(styleNode);
  763. return styleNode;
  764. }
  765.  
  766.  
  767.  
  768. /*
  769.  
  770. yt-action yt-add-element-to-app yt-autonav-pause-blur yt-autonav-pause-focus
  771. yt-autonav-pause-guide-closed yt-autonav-pause-guide-opened yt-autonav-pause-player
  772. yt-autonav-pause-player-ended yt-autonav-pause-scroll yt-autoplay-on-changed
  773. yt-close-tou-form yt-consent-bump-display-changed yt-focus-searchbox
  774. yt-get-context-provider yt-guide-close yt-guide-hover yt-guide-toggle
  775. yt-history-load yt-history-pop yt-load-invalidation-continuation
  776. yt-load-next-continuation yt-load-reload-continuation yt-load-tou-form
  777. yt-masthead-height-changed yt-navigate yt-navigate-cache yt-navigate-error
  778. yt-navigate-finish yt-navigate-redirect yt-navigate-set-page-offset
  779. yt-navigate-start yt-next-continuation-data-updated yt-open-hotkey-dialog
  780. yt-open-tou-form-loading-state yt-page-data-fetched yt-page-data-updated
  781. yt-page-data-will-update yt-page-manager-navigate-start yt-page-navigate-start
  782. yt-page-type-changed yt-player-attached yt-player-detached yt-player-released
  783. yt-player-requested yt-player-updated yt-popup-canceled yt-popup-closed
  784. yt-popup-opened yt-preconnect-urls yt-register-action yt-report-form-closed
  785. yt-report-form-opened yt-request-panel-mode-change yt-retrieve-location
  786. yt-service-request-completed yt-service-request-error yt-service-request-sent
  787. yt-set-theater-mode-enabled yt-show-survey yt-subscription-changed
  788. yt-swatch-changed yt-theater-mode-allowed yt-unregister-action yt-update-title
  789. yt-update-unseen-notification-count yt-viewport-scanned yt-visibility-refresh
  790.  
  791. */
  792.  
  793.  
  794. _console.log(38489)
  795.  
  796. class Session {
  797. constructor(initValue) {
  798. this.sid = initValue;
  799. }
  800. session() {
  801. let pageSession = this;
  802. let s = pageSession.sid; // inaccessible from external
  803. return {
  804. get isValid() {
  805. return s === pageSession.sid;
  806. }
  807. };
  808. }
  809. set(newValue) {
  810. this.sid = newValue;
  811. }
  812. inc() {
  813. this.sid++;
  814. }
  815. }
  816.  
  817. class Deferred {
  818. constructor() {
  819. this.reset();
  820. }
  821. debounce(f) {
  822. return Promise.race([this.promise]).then(f).catch(console.warn); // avoid promise.then.then.then ...
  823. }
  824. d() {
  825. return Promise.race([this.promise]).catch(console.warn);
  826. }
  827. reset() {
  828. this.resolved = false;
  829. this.promise = new Promise((resolve, reject) => {
  830. //this.reject = reject
  831. this._resolve = resolve
  832. })
  833. }
  834. resolve() {
  835. if (this._resolve === null) return null;
  836. if (this.resolved !== false) return false;
  837. this.resolved = true;
  838. this._resolve(...arguments);
  839. return true;
  840. }
  841. }
  842.  
  843. class Mutex {
  844.  
  845. constructor() {
  846. this.p = Promise.resolve()
  847. }
  848.  
  849. lockWith(f) {
  850.  
  851. this.p = this.p.then(() => {
  852. return new Promise(f)
  853. }).catch(console.warn)
  854. }
  855.  
  856. }
  857.  
  858.  
  859.  
  860. /* FireMonkey unable to extend MutationObserver correctly */
  861. class AttributeMutationObserver extends MutationObserver {
  862. constructor(flist) {
  863. super((mutations, observer) => {
  864. for (const mutation of mutations) {
  865. if (mutation.type === 'attributes') {
  866. this.checker(mutation.target, mutation.attributeName)
  867. }
  868. }
  869. })
  870. this.flist = flist;
  871. this.res = {}
  872. }
  873.  
  874. takeRecords() {
  875. super.takeRecords();
  876. }
  877. disconnect() {
  878. this._target = null;
  879. super.disconnect();
  880. }
  881. observe(/** @type {Node} */ target) {
  882. if (this._target) return;
  883. //console.log(123124, target)
  884. this._target = mWeakRef(target);
  885.  
  886. //console.log(123125, kRef(this._target))
  887. const options = {
  888. attributes: true,
  889. attributeFilter: Object.keys(this.flist),
  890. //attributeFilter: [ "status", "username" ],
  891. attributeOldValue: true
  892. }
  893. super.observe(target, options)
  894. }
  895. checker(/** @type {Node} */ target,/** @type {string} */ attributeName) {
  896. let nv = target.getAttribute(attributeName);
  897. if (this.res[attributeName] !== nv) {
  898. this.res[attributeName] = nv
  899. let f = this.flist[attributeName];
  900. if (f) f(attributeName, nv);
  901.  
  902. }
  903. }
  904. check(delay = 0) {
  905. setTimeout(() => {
  906. let target = kRef(this._target)
  907. if (target !== null) {
  908. for (const key of Object.keys(this.flist)) {
  909. this.checker(target, key)
  910. }
  911. } else {
  912. console.log('target is null') //disconnected??
  913. }
  914. target = null;
  915. }, delay)
  916. }
  917. }
  918.  
  919.  
  920. class KDate extends Date {
  921.  
  922. constructor(...args) {
  923. super(...args)
  924. this.dayBack = false
  925. }
  926.  
  927. browserSupported() {
  928.  
  929. }
  930.  
  931.  
  932. lokStringDate() {
  933. const d = this
  934.  
  935. let y = d.getFullYear()
  936. let m = d.getMonth() + 1
  937. let date = d.getDate()
  938.  
  939. let sy = y < 1000 ? (`0000${y}`).slice(-4) : '' + y
  940.  
  941. let sm = m < 10 ? '0' + m : '' + m
  942. let sd = date < 10 ? '0' + date : '' + date
  943.  
  944. return `${sy}.${sm}.${sd}`
  945.  
  946. }
  947.  
  948. lokStringTime() {
  949. const d = this
  950.  
  951. let h = d.getHours()
  952. let m = d.getMinutes()
  953.  
  954. const k = this.dayBack
  955.  
  956. if (k) h += 24
  957.  
  958. let sh = h < 10 ? '0' + h : '' + h
  959. let sm = m < 10 ? '0' + m : '' + m
  960.  
  961.  
  962. return `${sh}:${sm}`
  963.  
  964. }
  965.  
  966.  
  967.  
  968.  
  969. }
  970.  
  971. console.assert('browserSupported' in (new KDate()),
  972. { error: "0x87FF", errorMsg: "Your userscript manager is not supported. FireMonkey is not recommended." }
  973. );
  974.  
  975.  
  976.  
  977. let pageSession = new Session(0);
  978. const tabsDeferred = new Deferred();
  979. tabsDeferred.resolve();
  980.  
  981. let layoutStatusMutex = new Mutex();
  982.  
  983. let sliderMutex = new Mutex();
  984. const renderDeferred = new Deferred(); //pageRendered
  985. let pageRendered = 0;
  986. let renderIdentifier = 0;
  987.  
  988. const scriptletDeferred = new Deferred();
  989.  
  990.  
  991. function scriptInjector(script_id, url_chrome, response_id) {
  992.  
  993. let res = {
  994. script_id: script_id,
  995. inject: function () {
  996.  
  997. let res = this, script_id = this.script_id;
  998.  
  999. if (!document.querySelector(`script#${script_id}`)) {
  1000. if (res.runtime_url) {
  1001. addScriptByURL(res.runtime_url).id = script_id;
  1002. } else {
  1003. addScript(`${res.injection_script}`).id = script_id;
  1004. }
  1005. }
  1006.  
  1007. }
  1008. }
  1009. res.script_id = script_id;
  1010.  
  1011. if (isMyScriptInChromeRuntime()) {
  1012. res.runtime_url = window.chrome.runtime.getURL(url_chrome)
  1013. } else {
  1014. res.injection_script = GM_getResourceText(response_id);
  1015. }
  1016.  
  1017. return res;
  1018.  
  1019.  
  1020. }
  1021.  
  1022. const script_inject_js1 = scriptInjector(
  1023. 'userscript-tabview-injection-1',
  1024. 'js/injection_script_1.js',
  1025. "injectionJS1");
  1026.  
  1027.  
  1028. function nonCryptoRandStr(/** @type {number} */ n) {
  1029. const result = new Array(n);
  1030. const baseStr = nonCryptoRandStr_base;
  1031. const bLen = baseStr.length;
  1032. for (let i = 0; i < n; i++) {
  1033. let t = null
  1034. do {
  1035. t = baseStr.charAt(Math.floor(Math.random() * bLen));
  1036. } while (i === 0 && 10 - t > 0)
  1037. result[i] = t;
  1038. }
  1039. return result.join('');
  1040. }
  1041.  
  1042. const uidMAP = new Map();
  1043.  
  1044. function uidGEN(s) {
  1045. let uid = uidMAP.get(s);
  1046. if (!uid) {
  1047. const uidStore = ObserverRegister.uidStore;
  1048. do {
  1049. uid = nonCryptoRandStr(5);
  1050. } while (uidStore[uid])
  1051. uidMAP.set(s, uid);
  1052. }
  1053. return uid;
  1054. }
  1055.  
  1056. /**
  1057. * Class definition
  1058. * @property {string} propName - propriety description
  1059. * ...
  1060. */
  1061. class ObserverRegister {
  1062.  
  1063. constructor(/** @type {()=>MutationObserver | IntersectionObserver} */ observerCreator) {
  1064. let uid = null;
  1065. const uidStore = ObserverRegister.uidStore;
  1066. do {
  1067. uid = nonCryptoRandStr(5);
  1068. } while (uidStore[uid])
  1069. uidStore[uid] = true;
  1070.  
  1071. /**
  1072. * uid is the unique string for each observer
  1073. * @type {string}
  1074. * @public
  1075. */
  1076. this.uid = uid;
  1077.  
  1078. /**
  1079. * observerCreator is a function to create the observer
  1080. * @type {Function}
  1081. * @public
  1082. */
  1083. this.observerCreator = observerCreator
  1084.  
  1085. /**
  1086. * observer is the actual observer object
  1087. * @type {MutationObserver | IntersectionObserver}
  1088. * @public
  1089. */
  1090. this.observer = null;
  1091. this.bindCount = 0;
  1092. }
  1093. bindElement(/** @type {HTMLElement} */ elm, ...args) {
  1094. if (elm.hasAttribute(`o3r-${this.uid}`)) return false;
  1095. elm.setAttribute(`o3r-${this.uid}`, '')
  1096. this.bindCount++;
  1097. if (this.observer === null) {
  1098. this.observer = this.observerCreator();
  1099. }
  1100. this.observer.observe(elm, ...args)
  1101. return true
  1102. }
  1103. clear(/** @type {boolean} */ flag) {
  1104. if (this.observer !== null) {
  1105. //const uidStore = ObserverRegister.uidStore;
  1106. if (flag === true) {
  1107. this.observer.takeRecords();
  1108. this.observer.disconnect();
  1109. }
  1110. this.observer = null;
  1111. this.bindCount = 0;
  1112. for (const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`)
  1113. //uidStore[this.uid]=false;
  1114. //this.uid = null;
  1115. }
  1116. }
  1117. }
  1118.  
  1119. /**
  1120. * 'uidStore' is the static store of strings used.
  1121. * @static
  1122. */
  1123. ObserverRegister.uidStore = {}; //backward compatible with FireFox 55.
  1124.  
  1125.  
  1126. const mtoObservationDetails = new ObserverRegister(() => {
  1127. return new IntersectionObserver(ito_details, {
  1128. root: null,
  1129. rootMargin: "0px"
  1130. })
  1131. });
  1132.  
  1133.  
  1134. const mtoFlexyAttr = new ObserverRegister(() => {
  1135. return new MutationObserver(mtf_attrFlexy)
  1136. });
  1137.  
  1138. const mtoVisibility_EngagementPanel = new ObserverRegister(() => {
  1139. return new MutationObserver(FP.mtf_attrEngagementPanel)
  1140. });
  1141.  
  1142. const mtoVisibility_Playlist = new ObserverRegister(() => {
  1143. return new AttributeMutationObserver({
  1144. "hidden": FP.mtf_attrPlaylist
  1145. })
  1146. })
  1147. const sa_playlist = mtoVisibility_Playlist.uid;
  1148.  
  1149. const mtoVisibility_Comments = new ObserverRegister(() => {
  1150. return new AttributeMutationObserver({
  1151. "hidden": FP.mtf_attrComments
  1152. })
  1153. })
  1154. const sa_comments = mtoVisibility_Comments.uid;
  1155.  
  1156.  
  1157. const mtoVisibility_Chatroom = new ObserverRegister(() => {
  1158. return new AttributeMutationObserver({
  1159. "collapsed": FP.mtf_attrChatroom
  1160. })
  1161. })
  1162. // const sa_chatroom = mtoVisibility_Chatroom.uid;
  1163.  
  1164.  
  1165.  
  1166.  
  1167. function isDOMVisible(/** @type {HTMLElement} */ elem) {
  1168. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  1169. return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
  1170. }
  1171.  
  1172. function isNonEmptyString(s) {
  1173. return typeof s == 'string' && s.length > 0;
  1174. }
  1175.  
  1176.  
  1177. async function nativeCall(/** @type {EventTarget} */ dom, /** @type {any[]} */ detail) {
  1178. //console.log(1231)
  1179. dom.dispatchEvent(new CustomEvent("userscript-call-dom", { detail: detail }))
  1180. //console.log(1232)
  1181. }
  1182.  
  1183. async function nativeFunc(/** @type {EventTarget} */ dom, /** @type {string} */ property, /** @type {any} */ args) {
  1184. dom.dispatchEvent(new CustomEvent("userscript-call-dom-func", { detail: { property, args } }))
  1185. }
  1186.  
  1187. // async function nativeValue(dom, property, args) {
  1188. // dom.dispatchEvent(new CustomEvent("userscript-call-dom-value", { detail: { property, args } }))
  1189. // }
  1190. // async function nativeFuncStacked(/** @type {string} */ selector, /** @type {string} */ property, /** @type {any} */ args){
  1191. // document.dispatchEvent(new CustomEvent("userscript-call-dom-func-stacked", { detail: { selector, property, args } }))
  1192. // }
  1193. // async function nativeValueStacked(selector, property, args){
  1194. // document.dispatchEvent(new CustomEvent("userscript-call-dom-value-stacked", { detail: { selector, property, args } }))
  1195. // }
  1196. // async function nativeConstStacked(selector, property, args){
  1197. // document.dispatchEvent(new CustomEvent("userscript-call-dom-const-stacked", { detail: { selector, property, args } }))
  1198. // }
  1199.  
  1200. async function dispatchWindowResize() {
  1201. // for youtube to detect layout resize for adjusting Player tools
  1202. return window.dispatchEvent(new Event('resize'));
  1203. }
  1204.  
  1205. async function dispatchCommentRowResize() {
  1206.  
  1207. if (pageType !== "watch") return;
  1208.  
  1209. const ytdFlexyElm = es.ytdFlexy;
  1210. if (!ytdFlexyElm) return;
  1211. if (ytdFlexyElm.getAttribute('tyt-tab') !== '#tab-comments') return;
  1212.  
  1213. scriptletDeferred.debounce(() => {
  1214. document.dispatchEvent(new CustomEvent('tabview-resize-comments-rows'));
  1215. })
  1216.  
  1217.  
  1218. }
  1219.  
  1220. function enterPIP(video) {
  1221. if (video && typeof video.requestPictureInPicture === 'function' && isVideoPlaying(video)) {
  1222. if (document.pictureInPictureElement === null && typeof document.exitPictureInPicture === 'function') {
  1223. video.requestPictureInPicture().then(res => {
  1224.  
  1225. }).catch(console.warn)
  1226. }
  1227. }
  1228. }
  1229.  
  1230. function exitPIP() {
  1231. if (document.pictureInPictureElement !== null && typeof document.exitPictureInPicture === 'function') {
  1232. document.exitPictureInPicture().then(res => {
  1233.  
  1234. }).catch(console.warn)
  1235. }
  1236. }
  1237.  
  1238. function setToggleBtnTxt() {
  1239.  
  1240. if (chatroomDetails) {
  1241. _console.log(124234, 'c=== ')
  1242.  
  1243. let chat = document.querySelector('ytd-live-chat-frame#chat');
  1244. if (!chat) return;
  1245. let txt = querySelectorFromAnchor.call(chat, 'span.yt-core-attributed-string[role="text"]');
  1246. let c = (txt || 0).textContent;
  1247.  
  1248. if (typeof c === 'string' && c.length > 2) {
  1249. if (chat.hasAttribute('collapsed')) {
  1250. _console.log(124234, 'collapsed show expand ', chatroomDetails.txt_expand)
  1251. if (c !== chatroomDetails.txt_expand) {
  1252. txt.textContent = chatroomDetails.txt_expand;
  1253. }
  1254. } else {
  1255. _console.log(124234, 'not collapsed show collapse ', chatroomDetails.txt_collapse)
  1256. if (c !== chatroomDetails.txt_collapse) {
  1257. txt.textContent = chatroomDetails.txt_collapse;
  1258. }
  1259. }
  1260. }
  1261. }
  1262. }
  1263.  
  1264.  
  1265. function handlerTabExpanderClick() {
  1266.  
  1267. async function b() {
  1268.  
  1269. let h1 = document.documentElement.clientHeight;
  1270. let h2 = (document.querySelector('#right-tabs') || 0).clientHeight;
  1271.  
  1272. await Promise.resolve(0);
  1273. if (h1 > 300 && h2 > 300) {
  1274. let ratio = h2 / h1; // positive below 1.0
  1275.  
  1276. return ratio;
  1277. }
  1278. return 0;
  1279. }
  1280.  
  1281. async function a() {
  1282.  
  1283.  
  1284. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  1285. if (secondary) {
  1286.  
  1287.  
  1288. if (!secondary.classList.contains('tabview-hover-slider-enable')) {
  1289.  
  1290. let secondaryInner = querySelectorFromAnchor.call(secondary, '#secondary-inner.ytd-watch-flexy');
  1291.  
  1292. if (secondaryInner) {
  1293.  
  1294. if (!secondary.classList.contains('tabview-hover-slider')) {
  1295. // without hover
  1296.  
  1297. //let rect = secondary.getBoundingClientRect();
  1298. //let rectI = secondaryInner.getBoundingClientRect();
  1299.  
  1300. secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`)
  1301.  
  1302. }
  1303.  
  1304. let ratio = await b();
  1305. if (ratio > 0.0 && ratio <= 1.0) {
  1306.  
  1307. secondaryInner.style.setProperty('--ytd-watch-flexy-sidebar-width-d', `${Math.round(100 * ratio * 10) / 10}vw`);
  1308. secondary.classList.add('tabview-hover-slider');
  1309. secondary.classList.add('tabview-hover-slider-enable');
  1310.  
  1311. let video = document.querySelector('#player video');
  1312. enterPIP(video);
  1313.  
  1314. }
  1315.  
  1316. }
  1317.  
  1318.  
  1319. } else {
  1320.  
  1321.  
  1322. secondary.dispatchEvent(new CustomEvent("tabview-hover-slider-restore"));
  1323. //console.log(1994)
  1324.  
  1325. }
  1326.  
  1327. // no animation event triggered for hover -> enable
  1328. dispatchCommentRowResize();
  1329.  
  1330. }
  1331.  
  1332.  
  1333.  
  1334. }
  1335.  
  1336.  
  1337. a();
  1338.  
  1339.  
  1340. }
  1341.  
  1342. let global_columns_end_ito = null;
  1343.  
  1344. function setupHoverSlider(secondary, columns) {
  1345.  
  1346. if (!secondary || !columns) return;
  1347. let attrName = `o4r-${uidGEN('tabview-hover-slider-restore')}`;
  1348.  
  1349. if (secondary.hasAttribute(attrName)) return;
  1350. secondary.setAttribute(attrName, '');
  1351.  
  1352. let elmB = document.querySelector('tabview-view-secondary-xpander');
  1353. if (!elmB) {
  1354. elmB = document.createElement('tabview-view-secondary-xpander');
  1355. prependTo(elmB, secondary);
  1356. }
  1357.  
  1358. let elmA = document.querySelector('tabview-view-columns-endpos');
  1359. if (elmA) elmA.remove();
  1360. elmA = document.createElement('tabview-view-columns-endpos');
  1361.  
  1362. let itoA = new IntersectionObserver((entries) => {
  1363. let t = null;
  1364. let w = enableHoverSliderDetection
  1365. for (const entry of entries) {
  1366. if (entry.rootBounds === null) continue;
  1367. let bcr = entry.boundingClientRect;
  1368. let rb = entry.rootBounds;
  1369. t = !entry.isIntersecting && (bcr.left > rb.right) && (rb.left <= 0);
  1370. // if entries.length>1 (unlikely); take the last intersecting
  1371. // supplement cond 1. ensure the col element is in the right side
  1372. // supplement cond 2. ensure column is wide enough for overflow checking
  1373. // it can also avoid if the layout change happened but attribute not yet changed during the intersection observation
  1374. }
  1375.  
  1376. let columns = document.querySelector('#columns.style-scope.ytd-watch-flexy');
  1377. if (columns) columns.classList.toggle('tyt-column-overflow', t);
  1378.  
  1379. if (w !== t && t !== null) {
  1380. // t can be true when the layout enters single column mode
  1381. enableHoverSliderDetection = t;
  1382. }
  1383. //console.log(entries, enableHoverSliderDetection, t)
  1384. })
  1385. columns.appendChild(elmA); // append to dom first before observe
  1386. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  1387. //to trigger observation at the time layout being changed
  1388. itoA.observe(elmA);
  1389. }
  1390. global_columns_end_ito = itoA;
  1391.  
  1392.  
  1393. secondary.addEventListener('tabview-hover-slider-restore', function (evt) {
  1394.  
  1395. let secondary = evt.target;
  1396.  
  1397. if (!secondary.classList.contains('tabview-hover-slider-enable')) return;
  1398.  
  1399. let secondaryInner = querySelectorFromAnchor.call(secondary, '#secondary-inner.ytd-watch-flexy')
  1400.  
  1401. if (!secondaryInner) return;
  1402.  
  1403. if (secondary.classList.contains('tabview-hover-slider-hover')) {
  1404.  
  1405. Promise.resolve(0).then(() => {
  1406. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  1407. }).then(() => {
  1408. secondary.classList.remove('tabview-hover-slider-enable')
  1409. exitPIP();
  1410. })
  1411.  
  1412. } else {
  1413.  
  1414. let secondary = evt.target;
  1415. secondary.classList.remove('tabview-hover-slider')
  1416. secondary.classList.remove('tabview-hover-slider-enable')
  1417.  
  1418. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  1419. secondaryInner.style.removeProperty('--tabview-slider-right')
  1420.  
  1421. exitPIP();
  1422.  
  1423. }
  1424.  
  1425. setTimeout(() => {
  1426. updateFloatingSlider()
  1427. }, 30);
  1428.  
  1429. }, false);
  1430.  
  1431. }
  1432.  
  1433. function addTabExpander(tabContent) {
  1434.  
  1435. if (!tabContent) return null;
  1436. let id = tabContent.id;
  1437. if (!id || typeof id !== 'string') return null;
  1438.  
  1439. if (querySelectorFromAnchor.call(tabContent, `#${id} > tabview-view-tab-expander`)) return false;
  1440.  
  1441. let elm = document.createElement('tabview-view-tab-expander')
  1442. prependTo(elm, tabContent);
  1443. elm.innerHTML = `<div>${svgElm(16, 16, 12, 12, svgDiag1, 'svg-expand')}${svgElm(16, 16, 12, 12, svgDiag2, 'svg-collapse')}</div>`
  1444. elm.addEventListener('click', handlerTabExpanderClick, false);
  1445. return true;
  1446.  
  1447. }
  1448.  
  1449. function getColumnOverflowWidth() {
  1450.  
  1451. let screenWidth = document.documentElement.getBoundingClientRect().width;
  1452.  
  1453. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  1454.  
  1455. if (posElm1) {
  1456.  
  1457. let offset = posElm1.getBoundingClientRect().x - screenWidth;
  1458. return offset
  1459.  
  1460. }
  1461. return null
  1462. }
  1463.  
  1464. function getSecondaryInnerRight() {
  1465.  
  1466. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  1467.  
  1468. let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander');
  1469.  
  1470. if (posElm1 && posElm2) {
  1471.  
  1472. let offset = posElm1.getBoundingClientRect().x - posElm2.getBoundingClientRect().right;
  1473. return offset
  1474.  
  1475. }
  1476. return null
  1477.  
  1478. }
  1479.  
  1480. const setFloatingSliderOffset = (secondaryInner) => {
  1481.  
  1482.  
  1483. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  1484.  
  1485. let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander');
  1486.  
  1487. if (posElm1 && posElm2) {
  1488.  
  1489. let offset = getColumnOverflowWidth();
  1490.  
  1491. let k = 1.0
  1492. if (offset >= 125) {
  1493. k = 1.0
  1494. } else if (offset >= 75) {
  1495. k = 1.0;
  1496. } else if (offset >= 25) {
  1497. k = 0.25;
  1498. } else {
  1499. k = 0.0
  1500. }
  1501. secondaryInner.style.setProperty('--tabview-slider-offset-k2', `${k}`);
  1502. secondaryInner.style.setProperty('--tabview-slider-offset', `${offset}px`) // unnecessary
  1503.  
  1504. let oriWidth = posElm2.getBoundingClientRect().width;
  1505. secondaryInner.style.setProperty('--tabview-slider-ow', `${oriWidth}px`)
  1506.  
  1507. let s1 = 'var(--ytd-watch-flexy-sidebar-width-d)';
  1508. // new width
  1509.  
  1510. let s2 = `var(--tabview-slider-ow)`;
  1511. // ori width - youtube changing the code -> not reliable to use css prop.
  1512.  
  1513. let s3 = `${offset}px`;
  1514. // how many px wider than the page
  1515.  
  1516. secondaryInner.style.setProperty('--tabview-slider-offset-actual', `calc(${s1} - ${s2} + ${s3})`)
  1517.  
  1518. }
  1519.  
  1520. }
  1521.  
  1522. async function updateFloatingSlider_A(secondaryInner) {
  1523.  
  1524. // [is-extra-wide-video_]
  1525.  
  1526. await new Promise(r => setTimeout(r, 30)); // time allowed for dom changes and value change of enableHoverSliderDetection
  1527.  
  1528. let secondary = secondaryInner.parentNode;
  1529. if (!secondary) return;
  1530.  
  1531. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  1532. return;
  1533. }
  1534.  
  1535. if (!secondary.matches('#columns.ytd-watch-flexy #primary.ytd-watch-flexy ~ #secondary.ytd-watch-flexy')) {
  1536. return;
  1537. }
  1538.  
  1539. const bool = enableHoverSliderDetection === true;
  1540. const hasClassHover = secondary.classList.contains('tabview-hover-slider-hover') === true;
  1541.  
  1542. if (bool || hasClassHover) {
  1543. } else {
  1544. return;
  1545. }
  1546.  
  1547. await Promise.resolve(0);
  1548.  
  1549. secondary.classList.add('tabview-hover-final')
  1550.  
  1551. if (hasClassHover && !bool) {
  1552. secondaryInner.style.removeProperty('--tabview-slider-right')
  1553. secondaryInner.style.removeProperty('--tabview-slider-offset')
  1554. } else {
  1555.  
  1556. if (!hasClassHover) {
  1557. secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`)
  1558. }
  1559.  
  1560. setFloatingSliderOffset(secondaryInner);
  1561. }
  1562.  
  1563. if (bool ^ hasClassHover) {
  1564. secondary.classList.toggle('tabview-hover-slider', bool)
  1565. secondary.classList.toggle('tabview-hover-slider-hover', bool)
  1566. }
  1567.  
  1568. await Promise.resolve(0);
  1569.  
  1570.  
  1571. setTimeout(() => {
  1572. secondary.classList.remove('tabview-hover-final')
  1573. }, 350)
  1574.  
  1575.  
  1576. }
  1577.  
  1578.  
  1579. function updateFloatingSlider() {
  1580.  
  1581. let secondaryInner = document.querySelector('ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')
  1582.  
  1583. if (!secondaryInner) return;
  1584.  
  1585. let secondary = secondaryInner.parentNode;
  1586. if (!secondary) return;
  1587.  
  1588. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  1589. return;
  1590. }
  1591.  
  1592. let t = document.documentElement.clientWidth; //integer
  1593.  
  1594. sliderMutex.lockWith(unlock => {
  1595.  
  1596. let v = document.documentElement.clientWidth; //integer
  1597.  
  1598. if (t === v && secondaryInner.matches('body ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')) {
  1599.  
  1600. updateFloatingSlider_A(secondaryInner).then(unlock);
  1601. } else {
  1602. unlock();
  1603. }
  1604.  
  1605. })
  1606.  
  1607. }
  1608.  
  1609.  
  1610. function setToActiveTab(defaultTab) {
  1611. if (isTheater() && isWideScreenWithTwoColumns()) return;
  1612. const jElm = document.querySelector(`a[tyt-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  1613. document.querySelector(`a[tyt-tab-content="${(defaultTab || settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  1614. document.querySelector("a[tyt-tab-content]:not(.tab-btn-hidden)") ||
  1615. null;
  1616.  
  1617. switchTabActivity(jElm);
  1618. return !!jElm;
  1619. }
  1620.  
  1621. let enableLivePopupCheck = false
  1622.  
  1623. function layoutStatusChanged(/** @type {number} */ old_layoutStatus, /** @type {number} */ new_layoutStatus) {
  1624.  
  1625.  
  1626. if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === 0) makeHeaderFloat();
  1627.  
  1628. //if (old_layoutStatus === new_layoutStatus) return;
  1629.  
  1630. const cssElm = es.ytdFlexy;
  1631.  
  1632. if (!cssElm) return;
  1633.  
  1634.  
  1635. const BF_TWOCOL_N_THEATER = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER
  1636.  
  1637. let new_isExpandedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_EXPANDED)
  1638. let new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  1639.  
  1640. let new_isTabExpanded = !!(new_layoutStatus & LAYOUT_TAB_EXPANDED);
  1641. let new_isFullScreen = !!(new_layoutStatus & LAYOUT_FULLSCREEN);
  1642. let new_isExpandedEPanel = !!(new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPANDED);
  1643. let new_isExpandedDonationShelf = !!(new_layoutStatus & LAYOUT_DONATION_SHELF_EXPANDED);
  1644.  
  1645.  
  1646. function showTabOrChat() {
  1647.  
  1648. layoutStatusMutex.lockWith(unlock => {
  1649.  
  1650. if (lstTab.lastPanel == '#chatroom') {
  1651.  
  1652. if (new_isTabExpanded) switchTabActivity(null)
  1653. if (!new_isExpandedChat) ytBtnExpandChat();
  1654.  
  1655. } else if (lstTab.lastPanel && lstTab.lastPanel.indexOf('#engagement-panel-') == 0) {
  1656.  
  1657. if (new_isTabExpanded) switchTabActivity(null)
  1658. if (!new_isExpandedEPanel) ytBtnOpenEngagementPanel(lstTab.lastPanel);
  1659.  
  1660. } else if (lstTab.lastPanel == '#donation-shelf') {
  1661.  
  1662. if (new_isTabExpanded) switchTabActivity(null)
  1663. if (!new_isExpandedDonationShelf) openDonationShelf();
  1664.  
  1665. } else {
  1666.  
  1667. if (new_isExpandedChat) ytBtnCollapseChat()
  1668. if (!new_isTabExpanded) { setToActiveTab(); }
  1669.  
  1670. }
  1671.  
  1672. timeline.setTimeout(unlock, 40);
  1673.  
  1674. })
  1675. }
  1676.  
  1677. function hideTabAndChat() {
  1678.  
  1679. layoutStatusMutex.lockWith(unlock => {
  1680.  
  1681. if (new_isTabExpanded) switchTabActivity(null)
  1682. if (new_isExpandedChat) ytBtnCollapseChat()
  1683. if (new_isExpandedEPanel) ytBtnCloseEngagementPanels();
  1684. if (new_isExpandedDonationShelf) closeDonationShelf();
  1685.  
  1686.  
  1687. timeline.setTimeout(unlock, 40);
  1688.  
  1689. })
  1690.  
  1691. }
  1692.  
  1693. const statusCollapsedFalse = !!(new_layoutStatus & (LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED))
  1694. const statusCollapsedTrue = !statusCollapsedFalse
  1695.  
  1696. let changes = (old_layoutStatus & LAYOUT_VAILD) ? old_layoutStatus ^ new_layoutStatus : 0;
  1697.  
  1698. let chat_collapsed_changed = !!(changes & LAYOUT_CHATROOM_COLLAPSED)
  1699. let chat_expanded_changed = !!(changes & LAYOUT_CHATROOM_EXPANDED)
  1700. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  1701. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  1702. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  1703. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  1704. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPANDED)
  1705. let ds_expanded_changed = !!(changes & LAYOUT_DONATION_SHELF_EXPANDED)
  1706.  
  1707. _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)
  1708.  
  1709.  
  1710. //console.log(169, 1, chat_collapsed_changed, tab_expanded_changed)
  1711. //console.log(169, 2, new_isExpandedChat, new_isCollapsedChat, new_isTabExpanded)
  1712.  
  1713. let BF_LayoutCh_Panel = (changes & (LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED))
  1714. let tab_change = BF_LayoutCh_Panel;
  1715. let isChatOrTabExpandTriggering = !!((new_layoutStatus) & BF_LayoutCh_Panel);
  1716. let isChatOrTabCollaspeTriggering = !!((~new_layoutStatus) & BF_LayoutCh_Panel);
  1717.  
  1718.  
  1719. const moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandedEPanel + new_isExpandedDonationShelf) > 1
  1720.  
  1721.  
  1722. const base_twoCol_NoTheather_chatExpand_a = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED
  1723. const base_twoCol_NoTheather_chatExpand_b = LAYOUT_TWO_COLUMNS | 0 | LAYOUT_CHATROOM | 0
  1724.  
  1725. // two column; not theater; tab collapse; chat expand; ep expand
  1726. const IF_01a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  1727. const IF_01b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  1728.  
  1729. // two column; not theater; tab collapse; chat expand; ep expand
  1730. const IF_07a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED;
  1731. const IF_07b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_DONATION_SHELF_EXPANDED;
  1732.  
  1733.  
  1734. // two column; not theater;
  1735. const IF_02a = BF_TWOCOL_N_THEATER;
  1736. const IF_02b = LAYOUT_TWO_COLUMNS;
  1737.  
  1738. // two column; not theater; tab expand; chat expand;
  1739. const IF_03a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED;
  1740. const IF_03b = base_twoCol_NoTheather_chatExpand_b | LAYOUT_TAB_EXPANDED;
  1741.  
  1742.  
  1743. // two column; tab expand; chat expand;
  1744. const IF_06a = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED;
  1745. const IF_06b = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | 0;
  1746.  
  1747.  
  1748. // two column; theater;
  1749. const IF_04a = BF_TWOCOL_N_THEATER;
  1750. const IF_04b = BF_TWOCOL_N_THEATER;
  1751.  
  1752. // not fullscreen; two column; not theater; not tab expand; not EP expand; not expand chat; not donation shelf
  1753. const IF_05a = LAYOUT_FULLSCREEN | LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED;
  1754. const IF_05b = 0 | LAYOUT_TWO_COLUMNS | 0 | 0 | 0 | 0 | 0;
  1755.  
  1756. let _isChatPopupedF = null
  1757. let isChatPopupedF = ()=>{
  1758. return _isChatPopupedF===null ? ( _isChatPopupedF = cssElm.classList.contains('tyt-chat-popup') ) : _isChatPopupedF
  1759. }
  1760.  
  1761. if (new_isFullScreen) {
  1762.  
  1763.  
  1764. if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_06a) === IF_06b && statusCollapsedFalse && !column_mode_changed) {
  1765.  
  1766. // two column; tab expand; chat expand;
  1767.  
  1768. switchTabActivity(null);
  1769.  
  1770. }
  1771.  
  1772. if (!!(tab_change & LAYOUT_CHATROOM_EXPANDED) && new_isExpandedChat) {
  1773. //tab_change = LAYOUT_CHATROOM_EXPANDED
  1774. //tab_change = LAYOUT_CHATROOM_EXPANDED|LAYOUT_TAB_EXPANDED
  1775.  
  1776.  
  1777. timeline.setTimeout(() => {
  1778. let scrollElement = document.querySelector('ytd-app[scrolling]')
  1779. if (!scrollElement) return;
  1780. // single column view; click button; scroll to tab content area 100%
  1781. let chatFrame = document.querySelector('ytd-live-chat-frame#chat');
  1782. if (chatFrame && isChatExpand()) {
  1783. _console.log(7290, 1)
  1784. chatFrame.scrollIntoView(true);
  1785. }
  1786. }, 60)
  1787.  
  1788. }
  1789.  
  1790. if (!!(tab_change & LAYOUT_ENGAGEMENT_PANEL_EXPANDED) && new_isExpandedEPanel) {
  1791.  
  1792. timeline.setTimeout(() => {
  1793. let scrollElement = document.querySelector('ytd-app[scrolling]')
  1794. if (!scrollElement) return;
  1795. // single column view; click button; scroll to tab content area 100%
  1796. 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])');
  1797. if (epPanel) {
  1798. _console.log(7290, 2)
  1799.  
  1800. let pi = 50;
  1801. let cid = setInterval(() => {
  1802. if (--pi) epPanel.scrollIntoView(true); else clearInterval(cid)
  1803. }, 17)
  1804. //
  1805. }
  1806. }, 60)
  1807.  
  1808. }
  1809.  
  1810.  
  1811. } else if (fullscreen_mode_changed) {
  1812.  
  1813. // new_isFullScreen: false
  1814. // fullscreen_mode_changed: true
  1815.  
  1816. if (!new_isFullScreen && statusCollapsedTrue && isWideScreenWithTwoColumns() && !isTheater()) {
  1817. showTabOrChat();
  1818. } else if (!new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns() && isTheater()) {
  1819.  
  1820. if(cisChatPopupedF()){
  1821. }else{
  1822.  
  1823. ytBtnCancelTheater();
  1824.  
  1825. }
  1826. }
  1827.  
  1828. } else if ((new_layoutStatus & IF_01a) === IF_01b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED)) {
  1829.  
  1830. // new_isFullScreen: false
  1831. // fullscreen_mode_changed: false
  1832.  
  1833. // two column; not theater; tab collapse; chat expand; ep expand
  1834.  
  1835. if (epanel_expanded_changed) {
  1836. layoutStatusMutex.lockWith(unlock => {
  1837. ytBtnCollapseChat();
  1838. setTimeout(unlock, 13)
  1839. })
  1840. } else if (chat_collapsed_changed) {
  1841. layoutStatusMutex.lockWith(unlock => {
  1842. ytBtnCloseEngagementPanels();
  1843. setTimeout(unlock, 13)
  1844. })
  1845.  
  1846. }
  1847.  
  1848. } else if ((new_layoutStatus & IF_07a) === IF_07b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_DONATION_SHELF_EXPANDED)) {
  1849.  
  1850. // new_isFullScreen: false
  1851. // fullscreen_mode_changed: false
  1852.  
  1853. // two column; not theater; tab collapse; chat expand; ds expand
  1854.  
  1855. if (ds_expanded_changed) {
  1856. layoutStatusMutex.lockWith(unlock => {
  1857. ytBtnCollapseChat();
  1858. setTimeout(unlock, 13)
  1859. })
  1860. } else if (chat_collapsed_changed) {
  1861. layoutStatusMutex.lockWith(unlock => {
  1862. closeDonationShelf();
  1863. setTimeout(unlock, 13)
  1864. })
  1865.  
  1866. }
  1867.  
  1868. } else if (!tab_change && column_mode_changed && (new_layoutStatus & IF_02a) === IF_02b && moreThanOneShown) {
  1869.  
  1870. // new_isFullScreen: false
  1871. // fullscreen_mode_changed: false
  1872.  
  1873. // two column; not theater;
  1874. // moreThanOneShown
  1875.  
  1876. showTabOrChat();
  1877.  
  1878. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_03a) === IF_03b && statusCollapsedFalse && !column_mode_changed) {
  1879.  
  1880. // new_isFullScreen: false
  1881. // fullscreen_mode_changed: false
  1882.  
  1883. // two column; not theater; tab expand; chat expand;
  1884.  
  1885. switchTabActivity(null);
  1886.  
  1887. } else if (isChatOrTabExpandTriggering && (new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse && (changes & BF_TWOCOL_N_THEATER) === 0) {
  1888.  
  1889. // new_isFullScreen: false
  1890. // fullscreen_mode_changed: false
  1891.  
  1892. ytBtnCancelTheater();
  1893.  
  1894. } else if ((new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse) {
  1895.  
  1896. // new_isFullScreen: false
  1897. // fullscreen_mode_changed: false
  1898.  
  1899. if (isChatPopupedF()) {
  1900.  
  1901. } else {
  1902.  
  1903. hideTabAndChat();
  1904. }
  1905.  
  1906. } else if (isChatOrTabCollaspeTriggering && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue && !column_mode_changed) {
  1907.  
  1908. // new_isFullScreen: false
  1909. // fullscreen_mode_changed: false
  1910.  
  1911. if (tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED) {
  1912.  
  1913. lstTab.lastPanel = null;
  1914.  
  1915. if (new_isFullScreen) {
  1916.  
  1917. } else {
  1918. showTabOrChat();
  1919. }
  1920. } else if (tab_change == LAYOUT_DONATION_SHELF_EXPANDED) {
  1921.  
  1922. lstTab.lastPanel = null;
  1923.  
  1924. if (new_isFullScreen) {
  1925.  
  1926. } else {
  1927. showTabOrChat();
  1928. }
  1929. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED) {
  1930.  
  1931. lstTab.lastPanel = null;
  1932.  
  1933. if (new_isFullScreen) {
  1934.  
  1935. } else {
  1936. showTabOrChat();
  1937. }
  1938. } else {
  1939.  
  1940.  
  1941. if (new_isFullScreen) {
  1942.  
  1943. } else {
  1944.  
  1945. ytBtnSetTheater();
  1946.  
  1947. }
  1948.  
  1949. }
  1950.  
  1951. } else if (!tab_change && !!(changes & BF_TWOCOL_N_THEATER) && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue) {
  1952.  
  1953. // new_isFullScreen: false
  1954. // fullscreen_mode_changed: false
  1955.  
  1956. showTabOrChat();
  1957.  
  1958. } else if ((new_layoutStatus & IF_05a) === IF_05b) {
  1959. // bug fix for restoring from mini player
  1960.  
  1961. layoutStatusMutex.lockWith(unlock => {
  1962. setToActiveTab();
  1963. timeline.setTimeout(unlock, 40);
  1964. });
  1965.  
  1966. }
  1967.  
  1968. if (theater_mode_changed) {
  1969. let tdt = Date.now();
  1970. theater_mode_changed_dt = tdt
  1971. setTimeout(() => {
  1972. if (theater_mode_changed_dt !== tdt) return;
  1973. updateFloatingSlider();
  1974. }, 130)
  1975. }
  1976.  
  1977. let secondary = null;
  1978. if (secondary = document.querySelector('.tabview-hover-slider-enable')) {
  1979. secondary.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  1980. //console.log(1996)
  1981. }
  1982.  
  1983.  
  1984. if (fullscreen_mode_changed) {
  1985. detailsTriggerReset = true;
  1986. setTimeout(() => {
  1987. setHiddenStateForDesc();
  1988. }, 80);
  1989. }
  1990.  
  1991. // resize => is-two-columns_
  1992. if (column_mode_changed) {
  1993.  
  1994.  
  1995. Promise.resolve(0).then(() => {
  1996. pageCheck();
  1997. if (global_columns_end_ito !== null) {
  1998. //to trigger observation at the time layout being changed
  1999. if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  2000. let endpos = document.querySelector('tabview-view-columns-endpos')
  2001. if (endpos !== null) {
  2002. global_columns_end_ito.observe(endpos)
  2003. }
  2004. } else {
  2005. global_columns_end_ito.disconnect();
  2006. }
  2007. }
  2008. setTimeout3(() => {
  2009. singleColumnScrolling(true); //initalize sticky
  2010. });
  2011. })
  2012. }
  2013.  
  2014. if (enableLivePopupCheck === true) {
  2015.  
  2016. const new_isTwoColumnsTheater = fT(new_layoutStatus, LAYOUT_TWO_COLUMNS | LAYOUT_THEATER, 0)
  2017.  
  2018. let currentIsTheaterPopupChat = new_isTwoColumnsTheater && new_isExpandedChat && isChatPopupedF()
  2019. if (!currentIsTheaterPopupChat) {
  2020. enableLivePopupCheck = false
  2021. document.dispatchEvent(new CustomEvent("tyt-close-popup"))
  2022. }
  2023.  
  2024. }
  2025.  
  2026.  
  2027. }
  2028.  
  2029. function fixLayoutStatus(x) {
  2030. const new_isExpandedChat = !(x & LAYOUT_CHATROOM_COLLAPSED) && (x & LAYOUT_CHATROOM)
  2031. return new_isExpandedChat ? (x | LAYOUT_CHATROOM_EXPANDED) : (x & ~LAYOUT_CHATROOM_EXPANDED);
  2032. }
  2033.  
  2034. const wls = new Proxy({
  2035. /** @type {number | null} */
  2036. layoutStatus: undefined
  2037. }, {
  2038. get: function (target, prop) {
  2039. return target[prop];
  2040. },
  2041. set: function (target, prop, value) {
  2042. if (prop == 'layoutStatus') {
  2043.  
  2044. if (value === 0) {
  2045. target[prop] = value;
  2046. return true;
  2047. } else if (target[prop] === value) {
  2048. return true;
  2049. } else {
  2050. if (!target.layoutStatus_pending) {
  2051. target.layoutStatus_pending = true;
  2052. const old_layoutStatus = target[prop];
  2053. target[prop] = value;
  2054. layoutStatusMutex.lockWith(unlock => {
  2055. target.layoutStatus_pending = false;
  2056. let new_layoutStatus = target[prop];
  2057. if (old_layoutStatus !== new_layoutStatus) {
  2058. layoutStatusChanged(old_layoutStatus, new_layoutStatus);
  2059. timeline.setTimeout(unlock, 40)
  2060. } else {
  2061. unlock();
  2062. }
  2063. })
  2064. return true;
  2065. }
  2066. }
  2067. }
  2068. target[prop] = value;
  2069. return true;
  2070. },
  2071. has: function (target, prop) {
  2072. return (prop in target);
  2073. }
  2074. });
  2075.  
  2076. 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>`
  2077.  
  2078. function isVideoPlaying(video) {
  2079. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  2080. }
  2081.  
  2082. function wAttr(elm, attr, kv) {
  2083. if (elm) {
  2084. if (kv === true) {
  2085. elm.setAttribute(attr, '');
  2086. } else if (kv === false) {
  2087. elm.removeAttribute(attr);
  2088. } else if (kv === null) {
  2089. //;
  2090. } else if (typeof kv == 'string') {
  2091. elm.setAttribute(attr, kv);
  2092. }
  2093. }
  2094. }
  2095.  
  2096. function hideTabBtn(tabBtn) {
  2097. //console.log('hideTabBtn', tabBtn)
  2098. let isActiveBefore = tabBtn.classList.contains('active');
  2099. tabBtn.classList.add("tab-btn-hidden");
  2100. if (isActiveBefore) {
  2101. setToActiveTab();
  2102. }
  2103. }
  2104.  
  2105. // function hasAttribute(obj, key) {
  2106. // return obj && obj.hasAttribute(key);
  2107. // }
  2108.  
  2109. function isTheater() {
  2110. const cssElm = es.ytdFlexy;
  2111. return (cssElm && cssElm.hasAttribute('theater'))
  2112. }
  2113.  
  2114. function isFullScreen() {
  2115. const cssElm = es.ytdFlexy;
  2116. return (cssElm && cssElm.hasAttribute('fullscreen'))
  2117. }
  2118.  
  2119. function isChatExpand() {
  2120. const cssElm = es.ytdFlexy;
  2121. return cssElm && (cssElm.getAttribute('tyt-chat') || '').charAt(0) === '+'
  2122. }
  2123.  
  2124. function isWideScreenWithTwoColumns() {
  2125. const cssElm = es.ytdFlexy;
  2126. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  2127. }
  2128.  
  2129. function isAnyActiveTab() {
  2130. return document.querySelector('#right-tabs .tab-btn.active') !== null
  2131. }
  2132. // function isAnyActiveTab2() {
  2133. // return document.querySelectorAll('#right-tabs .tab-btn.active').length > 0
  2134. // }
  2135.  
  2136. function isEngagementPanelExpanded() { //note: not checking the visual elements
  2137. const cssElm = es.ytdFlexy;
  2138. return (cssElm && +cssElm.getAttribute('tyt-ep-visible') > 0)
  2139. }
  2140.  
  2141. function isDonationShelfExpanded() {
  2142. const cssElm = es.ytdFlexy;
  2143. return (cssElm && cssElm.hasAttribute('tyt-donation-shelf'))
  2144. }
  2145.  
  2146. const engagementIdMap = new Map();
  2147. let engagementIdNext = 1; // max 1 << 62
  2148.  
  2149. function engagement_panels_() {
  2150.  
  2151. let res = [];
  2152. // let shownRes = [];
  2153.  
  2154. let v = 0;
  2155. // let k = 1;
  2156. // let count = 0;
  2157.  
  2158. for (const ePanel of document.querySelectorAll(
  2159. `ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
  2160. )) {
  2161. let targetId = ePanel.getAttribute('target-id')
  2162. if (typeof targetId !== 'string') continue;
  2163. let eId = engagementIdMap.get(targetId)
  2164. if (!eId) {
  2165. engagementIdMap.set(targetId, eId = engagementIdNext)
  2166. if (engagementIdNext === (1 << 62)) {
  2167. engagementIdNext = 1;
  2168. console.warn('engagementId reached 1 << 62')
  2169. } else {
  2170. engagementIdNext = engagementIdNext << 1;
  2171. }
  2172. }
  2173. // console.log(55,eId, targetId)
  2174.  
  2175. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  2176.  
  2177. let k = eId
  2178. switch (visibility) {
  2179. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED':
  2180. v |= k;
  2181. // count++;
  2182. // shownRes.push(ePanel)
  2183. res.push({ ePanel, k, visible: true });
  2184. break;
  2185. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN':
  2186. res.push({ ePanel, k, visible: false });
  2187. break;
  2188. default:
  2189. res.push({ ePanel, k, visible: false });
  2190. }
  2191.  
  2192. //k = k << 1;
  2193.  
  2194. }
  2195. return { list: res, value: v };
  2196. // return { list: res, value: v, count: count, shownRes };
  2197. }
  2198.  
  2199.  
  2200. function ytBtnOpenEngagementPanel(/** @type {number | string} */ panel_id) {
  2201.  
  2202. // console.log(panel_id)
  2203. if (typeof panel_id == 'string') {
  2204. panel_id = panel_id.replace('#engagement-panel-', '');
  2205. panel_id = parseInt(panel_id);
  2206. }
  2207. if (panel_id >= 0) { } else return false;
  2208.  
  2209. let panels = engagement_panels_();
  2210. // console.log(panels)
  2211.  
  2212. let actions = []
  2213. for (const { ePanel, k, visible } of panels.list) {
  2214. if ((panel_id & k) === k) {
  2215. if (!visible) {
  2216. actions.push({
  2217. panelId: ePanel.getAttribute('target-id'),
  2218. toShow: true
  2219. })
  2220. }
  2221. } else {
  2222. if (visible) {
  2223. actions.push({
  2224. panelId: ePanel.getAttribute('target-id'),
  2225. toHide: true
  2226. })
  2227. }
  2228. }
  2229. }
  2230.  
  2231. if (actions.length > 0) {
  2232. // console.log(4545,actions)
  2233. scriptletDeferred.debounce(() => {
  2234. document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
  2235. detail: actions
  2236. }))
  2237. actions = null
  2238. })
  2239. }
  2240.  
  2241. }
  2242.  
  2243. function ytBtnCloseEngagementPanel(/** @type {HTMLElement} */ s) {
  2244. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  2245.  
  2246. let panelId = s.getAttribute('target-id')
  2247. scriptletDeferred.debounce(() => {
  2248. document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
  2249. detail: {
  2250. panelId,
  2251. toHide: true
  2252. }
  2253. }))
  2254. })
  2255.  
  2256. }
  2257.  
  2258. function ytBtnCloseEngagementPanels() {
  2259. if (isEngagementPanelExpanded()) {
  2260. for (const s of document.querySelectorAll(
  2261. `ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
  2262. )) {
  2263. if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  2264. }
  2265. }
  2266. }
  2267.  
  2268. function openDonationShelf() {
  2269. if (!isDonationShelfExpanded()){
  2270. let btn = document.querySelector('#tyt-donation-shelf-toggle-btn')
  2271. if(btn){
  2272. btn.click();
  2273. return true;
  2274. }
  2275. }
  2276. return false;
  2277. }
  2278. function closeDonationShelf() {
  2279. if (isDonationShelfExpanded()){
  2280. let btn = document.querySelector('#tyt-donation-shelf-toggle-btn')
  2281. if(btn){
  2282. btn.click();
  2283. return true;
  2284. }
  2285. }
  2286. return false;
  2287. }
  2288.  
  2289. function ytBtnSetTheater() {
  2290. if (!isTheater()) {
  2291. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  2292. if (sizeBtn) sizeBtn.click();
  2293. }
  2294. }
  2295.  
  2296. function ytBtnCancelTheater() {
  2297. if (isTheater()) {
  2298. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  2299. if (sizeBtn) sizeBtn.click();
  2300. }
  2301. }
  2302.  
  2303. function ytBtnExpandChat() {
  2304. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button')
  2305. if (button) {
  2306. button =
  2307. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  2308. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  2309. if (button) button.click();
  2310. }
  2311. }
  2312.  
  2313. function ytBtnCollapseChat() {
  2314. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button')
  2315. if (button) {
  2316. button =
  2317. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  2318. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  2319. if (button) button.click();
  2320. }
  2321. }
  2322.  
  2323.  
  2324. async function makeVideosAutoLoad2() {
  2325. let sVideosList = document.querySelector('ytd-watch-flexy #tab-videos [placeholder-videos]');
  2326.  
  2327. if (!sVideosList) return null;
  2328.  
  2329. //let ab = sVideosList.getAttribute('tabview-videos-autoload')
  2330. await Promise.resolve(0);
  2331.  
  2332. let endPosDOM = document.querySelector('tabview-view-videos-endpos')
  2333. if (endPosDOM) endPosDOM.remove(); // just in case
  2334. endPosDOM = document.createElement('tabview-view-videos-endpos')
  2335. insertAfterTo(endPosDOM, sVideosList);
  2336.  
  2337. await Promise.resolve(0);
  2338.  
  2339.  
  2340. //sVideosList.setAttribute('tabview-videos-autoload', '1')
  2341.  
  2342. _console.log(9333)
  2343. if (!sVideosITO) {
  2344.  
  2345. sVideosITO = new IntersectionObserver((entries) => {
  2346.  
  2347. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) return;
  2348.  
  2349. _console.log(9334, entries)
  2350. if (entries.length !== 1) return;
  2351. if (entries[0].isIntersecting !== true) return;
  2352. let elm = ((entries[0] || 0).target || 0);
  2353. if (!elm) return;
  2354. elm = null;
  2355. entries = null;
  2356.  
  2357. new Promise(resolve => {
  2358.  
  2359. // compatibile with Search While Watching Video
  2360. 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]');
  2361. if (isSearchGeneratedWithHiddenContinuation) return;
  2362.  
  2363. // native YouTube coding use different way to handle custom videos, unknown condition for the continutation loading.
  2364. 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"]')
  2365. if (isOtherChipSelected) return;
  2366.  
  2367. setTimeout(resolve, 30); // delay required to allow YouTube generate the continuation elements
  2368.  
  2369.  
  2370. }).then(() => {
  2371.  
  2372. let res = setVideosTwoColumns(2 | 4, true)
  2373.  
  2374. _console.log(9335, res)
  2375.  
  2376. if (res.m2 && res.m3) {
  2377. let m4 = closestDOM.call(res.m2, 'ytd-continuation-item-renderer');
  2378. let m5, m6;
  2379.  
  2380. _console.log(9336, m4)
  2381. if (m4) {
  2382. m5 = querySelectorFromAnchor.call(m4, 'ytd-button-renderer.style-scope.ytd-continuation-item-renderer, yt-button-renderer.style-scope.ytd-continuation-item-renderer');
  2383.  
  2384. // YouTube coding bug - correct is 'ytd-button-renderer'. If the page is redirected under single column mode, the tag become 'yt-button-renderer'
  2385. // under 'yt-button-renderer', the
  2386.  
  2387. if (m5)
  2388. m6 = querySelectorFromAnchor.call(m5, 'button.yt-spec-button-shape-next--call-to-action'); // main
  2389.  
  2390. _console.log(9337, m4, m5, m6)
  2391.  
  2392. if (m6) {
  2393. m6.click() // generic solution
  2394. } else if (m5) {
  2395. m5.click(); // not sure
  2396. } else {
  2397. m4.dispatchEvent(new Event('yt-service-request-sent-button-renderer')); // only for correct YouTube coding
  2398. }
  2399. }
  2400. m4 = null;
  2401. m5 = null;
  2402. m6 = null;
  2403. }
  2404. res = null;
  2405.  
  2406. });
  2407.  
  2408. }, {
  2409. rootMargin: `0px`, // refer to css margin-top:-30vh
  2410. threshold: [0]
  2411. })
  2412. sVideosITO.observe(endPosDOM);
  2413. } else {
  2414. sVideosITO.disconnect();
  2415. sVideosITO.observe(endPosDOM);
  2416. }
  2417.  
  2418.  
  2419. }
  2420.  
  2421.  
  2422. function fixTabs() {
  2423.  
  2424. if (!scriptEnable) return;
  2425.  
  2426. let queryElement = document.querySelector('*:not(#tab-videos) > #related.ytd-watch-flexy > ytd-watch-next-secondary-results-renderer');
  2427.  
  2428. let isRelocated = !!queryElement;
  2429.  
  2430. if (isRelocated) {
  2431.  
  2432. _console.log(3202, 2)
  2433.  
  2434. let relatedElm = closestDOM.call(queryElement, '#related.ytd-watch-flexy'); // NOT NULL
  2435.  
  2436. let right_tabs = document.querySelector('#right-tabs'); // can be NULL
  2437.  
  2438. let tab_videos = right_tabs ? querySelectorFromAnchor.call(right_tabs, "#tab-videos") : null; // can be NULL
  2439.  
  2440. if (tab_videos !== null) {
  2441.  
  2442. _console.log(3202, 4)
  2443.  
  2444. 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')
  2445. if (target_container) target_container.appendChild(right_tabs) // last-child
  2446.  
  2447. tab_videos.appendChild(relatedElm);
  2448. // no any other element set these attr. only init / relocation
  2449. relatedElm.setAttribute('placeholder-for-youtube-play-next-queue', '')
  2450. relatedElm.setAttribute('placeholder-videos', '')
  2451.  
  2452. makeVideosAutoLoad2();
  2453.  
  2454. }
  2455.  
  2456. }
  2457.  
  2458.  
  2459. /** @type {HTMLElement | null} */
  2460. let chatroom = null;
  2461. if (chatroom = document.querySelector('*:not([data-positioner="before|#chat"]) + ytd-live-chat-frame#chat, ytd-live-chat-frame#chat:first-child')) {
  2462.  
  2463. let pHolderElm = document.querySelector('tabview-view-pholder[data-positioner="before|#chat"]');
  2464. if (pHolderElm) pHolderElm.remove();
  2465.  
  2466. if (document.querySelector('.YouTubeLiveFilledUpView')) {
  2467. // no relocation
  2468. } else {
  2469.  
  2470. let rightTabs = document.querySelector('#right-tabs');
  2471. if (rightTabs) {
  2472. insertBeforeTo(chatroom, rightTabs);
  2473. }
  2474.  
  2475. }
  2476.  
  2477. if (!pHolderElm) {
  2478. pHolderElm = document.createElement('tabview-view-pholder');
  2479. pHolderElm.setAttribute('data-positioner', 'before|#chat');
  2480. }
  2481.  
  2482. insertBeforeTo(pHolderElm, chatroom)
  2483.  
  2484. }
  2485.  
  2486.  
  2487. }
  2488.  
  2489. async function isDocumentInFullScreenMode() {
  2490. return document.fullscreenElement !== null;
  2491. }
  2492. async function energizedByVideoTimeUpdate() {
  2493.  
  2494. const isFullscreen = await isDocumentInFullScreenMode();
  2495. if (isFullscreen) return;
  2496.  
  2497. // force browser to load the videostream during playing (primarily for music videos)
  2498. // both background and foreground
  2499.  
  2500. _updateTimeAccum++;
  2501.  
  2502. if ((_updateTimeAccum + _viTimeNum) % 11 === 0) {
  2503. // console.log(document.querySelector('video').currentTime) // 2.55, 2.64, 3.12, ...
  2504. // about 2.66s
  2505.  
  2506. if (_viTimeNum > 211) {
  2507. // around 30.9s ~ 31.9s
  2508. _viTimeNum = 200;
  2509. _updateTimeAccum = (_updateTimeAccum % 8) + 1; // reset to 1 ~ 8
  2510. postMessage({ tabviewEnergized: true }, 'https://www.youtube.com'); // post message to make alive
  2511. }
  2512.  
  2513. document.head.dataset.viTime = `${_viTimeNum + 1}`;
  2514. await Promise.resolve(0)
  2515. _viTimeNum = +document.head.dataset.viTime || 0;
  2516. }
  2517.  
  2518.  
  2519. }
  2520.  
  2521. function autoCompletePosCreate(){
  2522. let positioner = document.createElement("tabview-view-autocomplete-pos");
  2523. let oldPositioner = document.querySelector("tabview-view-autocomplete-pos");
  2524. if(oldPositioner) oldPositioner.remove();
  2525. return positioner
  2526. }
  2527.  
  2528. function handlerAutoCompleteExist() {
  2529. // Youtube - Search While Watching Video
  2530.  
  2531. /** @type {HTMLElement} */
  2532. let searchBox, autoComplete;
  2533. searchBox = this;
  2534. this.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  2535. let domId = this.getAttribute('data-autocomplete-results-id')
  2536.  
  2537. autoComplete = document.querySelector(`[data-autocomplete-input-id="${domId}"]`)
  2538.  
  2539. if (!domId || !searchBox) return;
  2540.  
  2541. let positioner = searchBox.nextSibling;
  2542. if (positioner) {
  2543. if (positioner.nodeName.toLowerCase() !== "tabview-view-autocomplete-pos") {
  2544. positioner = autoCompletePosCreate();
  2545. insertAfterTo(positioner, searchBox);
  2546. }
  2547. } else {
  2548. positioner = autoCompletePosCreate();
  2549. prependTo(positioner, searchBox.parentNode);
  2550. }
  2551. prependTo(autoComplete, positioner);
  2552.  
  2553. setupSearchBox(searchBox, positioner);
  2554.  
  2555.  
  2556. }
  2557.  
  2558. async function setupSearchBox(searchBox, positioner) {
  2559.  
  2560. let mb = getComputedStyle(searchBox).marginBottom
  2561. let h = searchBox.offsetHeight + 'px'
  2562.  
  2563. positioner.style.setProperty('--tyt-swwv-searchbox-mb', mb)
  2564. positioner.style.setProperty('--tyt-swwv-searchbox-h', h)
  2565.  
  2566. mtf_autocomplete_search()
  2567.  
  2568. }
  2569.  
  2570. function mtf_autocomplete_search() {
  2571. // Youtube - Search While Watching Video
  2572.  
  2573. /** @type {HTMLElement | null} */
  2574. const ytdFlexyElm = es.ytdFlexy;
  2575. if (!scriptEnable || !ytdFlexyElm) return;
  2576.  
  2577. const autocomplete = querySelectorFromAnchor.call(ytdFlexyElm, '[placeholder-for-youtube-play-next-queue] input#suggestions-search + tabview-view-autocomplete-pos > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])')
  2578.  
  2579. if (autocomplete) {
  2580.  
  2581. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  2582.  
  2583.  
  2584. if (searchBox) {
  2585.  
  2586. const rAutoComplete = mWeakRef(autocomplete);
  2587.  
  2588. function setVisible(autocomplete, b) {
  2589. autocomplete.style.display = (b ? 'block' : 'none');
  2590. }
  2591.  
  2592. function isContentNotEmpty(searchbox, autocomplete) {
  2593. return (searchbox.value || '').length > 0 && (autocomplete.textContent || '').length > 0;
  2594. }
  2595.  
  2596. autocomplete.parentNode.setAttribute('position-fixed-by-tabview-youtube', '');
  2597. autocomplete.setAttribute('position-fixed-by-tabview-youtube', '');
  2598. autocomplete.setAttribute('userscript-scrollbar-render', '')
  2599.  
  2600. //let cancelClickToggle = false;
  2601.  
  2602. if (!searchBox.hasAttribute('is-set-click-to-toggle')) {
  2603. searchBox.setAttribute('is-set-click-to-toggle', '')
  2604.  
  2605. searchBox.addEventListener('click', function () {
  2606.  
  2607. Promise.resolve(0).then(() => {
  2608.  
  2609. const autocomplete = kRef(rAutoComplete);
  2610.  
  2611. if (!autocomplete) return;
  2612.  
  2613. const isNotEmpty = isContentNotEmpty(this, autocomplete);
  2614.  
  2615. if (isNotEmpty) {
  2616.  
  2617. let elmVisible = isDOMVisible(autocomplete);
  2618.  
  2619. if (elmVisible) {
  2620. setVisible(autocomplete, false)
  2621. }
  2622. else {
  2623. setVisible(autocomplete, true)
  2624. }
  2625.  
  2626. }
  2627.  
  2628. })
  2629.  
  2630. }, bubblePassive)
  2631.  
  2632. let cacheScrollIntoView = null;
  2633. let rafID = 0;
  2634. searchBox.addEventListener('keydown', function (evt) {
  2635. //cancelClickToggle = true;
  2636. switch (evt.code) {
  2637. case 'ArrowUp':
  2638. case 'ArrowDown':
  2639.  
  2640. let t = Date.now();
  2641. if (rafID === 0) {
  2642. rafID = requestAnimationFrame(() => {
  2643. rafID = 0;
  2644. let d = Date.now();
  2645. if (d - t > 300) return;
  2646.  
  2647. const autocomplete = kRef(rAutoComplete);
  2648.  
  2649. let selected = querySelectorFromAnchor.call(autocomplete, '.autocomplete-suggestion.selected');
  2650. let bool = selected && selected !== cacheScrollIntoView;
  2651. cacheScrollIntoView = selected;
  2652. if (bool) {
  2653.  
  2654. try {
  2655. selected.scrollIntoView({ block: "nearest", inline: "nearest" });
  2656. } catch (e) { }
  2657.  
  2658. }
  2659.  
  2660. })
  2661. }
  2662. default:
  2663. //
  2664. }
  2665.  
  2666.  
  2667. }, bubblePassive)
  2668.  
  2669. searchBox.addEventListener('tyt-autocomplete-suggestions-change', function (evt) {
  2670.  
  2671. //cancelClickToggle = true;
  2672. if (evt.target !== document.activeElement) return;
  2673. setTimeout(() => {
  2674. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${this.getAttribute('data-autocomplete-results-id')}"]`);
  2675. if (!autocomplete) return;
  2676. const isNotEmpty = isContentNotEmpty(this, autocomplete);
  2677. if (isNotEmpty) {
  2678. // dont detect visibility; just set to visible
  2679. setVisible(autocomplete, true);
  2680. }
  2681. }, 20);
  2682.  
  2683. }, bubblePassive)
  2684.  
  2685. }
  2686.  
  2687. }
  2688.  
  2689. }
  2690.  
  2691. }
  2692.  
  2693. const insertBeforeTo = HTMLElement.prototype.before ? (elm, target) => {
  2694. if (!target || !elm) return null;
  2695. // using before
  2696. HTMLElement.prototype.before.call(target, elm);
  2697. return true;
  2698. } : (elm, target) => {
  2699. if (!target || !elm) return null;
  2700. // using insertBefore
  2701. try {
  2702. HTMLElement.prototype.insertBefore.call(target.parentNode, elm, target);
  2703. return true;
  2704. } catch (e) {
  2705. console.log('element insert failed in old browser CE')
  2706. }
  2707. return false;
  2708. }
  2709.  
  2710. const insertAfterTo = HTMLElement.prototype.after ? (elm, target) => {
  2711. if (!target || !elm) return null;
  2712. // using after
  2713. HTMLElement.prototype.after.call(target, elm);
  2714. return true;
  2715. } : (elm, target) => {
  2716. if (!target || !elm) return null;
  2717. // using insertBefore
  2718. try {
  2719. HTMLElement.prototype.insertBefore.call(target.parentNode, elm, target.nextSibling);
  2720. return true;
  2721. } catch (e) {
  2722. console.log('element insert failed in old browser CE')
  2723. }
  2724. return false;
  2725. }
  2726.  
  2727. const prependTo = HTMLElement.prototype.prepend ? (elm, target) => {
  2728. if (!target || !elm) return null;
  2729. // using prepend
  2730. HTMLElement.prototype.prepend.call(target, elm);
  2731. return true;
  2732. } : (elm, target) => {
  2733. if (!target || !elm) return null;
  2734. // using insertBefore
  2735. try {
  2736. HTMLElement.prototype.insertBefore.call(target, elm, target.firstChild);
  2737. return true;
  2738. } catch (e) {
  2739. console.log('element insert failed in old browser CE')
  2740. }
  2741. return false;
  2742. }
  2743.  
  2744. const appends = HTMLElement.prototype.append ? (target, ...args) => {
  2745. HTMLElement.prototype.append.call(target, ...args);
  2746. return true;
  2747. } : (target, ...args) => {
  2748. for (const s of args) {
  2749. target.appendChild(s)
  2750. }
  2751. return true;
  2752. }
  2753.  
  2754.  
  2755. // css animation check for element relocation
  2756. function mtf_liveChatBtnF(node) {
  2757.  
  2758. if (!node || node.nodeType !== 1) return;
  2759.  
  2760. /** @type {HTMLElement | null} */
  2761. const ytdFlexyElm = es.ytdFlexy;
  2762. if (!scriptEnable || !ytdFlexyElm) return;
  2763.  
  2764. let button = node;
  2765.  
  2766. if (button) {
  2767. prependTo(button, button.parentNode);
  2768. }
  2769.  
  2770.  
  2771. }
  2772.  
  2773.  
  2774. function getWrapper(wrapperId) {
  2775. let wrapper = document.getElementById(wrapperId);
  2776. if (!wrapper) {
  2777. wrapper = document.createElement('div');
  2778. wrapper.id = wrapperId;
  2779. }
  2780. return wrapper;
  2781. }
  2782.  
  2783. // continuous check for element relocation
  2784. // fired at begining & window resize, etc
  2785. // might moved to #primary
  2786. function mtf_append_playlist(/** @type {HTMLElement | null} */ playlist) {
  2787.  
  2788. if (playlist === null) {
  2789. playlist = document.querySelector('ytd-watch-flexy[playlist] *:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer.style-scope.ytd-watch-flexy#playlist:not(.ytd-miniplayer)');
  2790. // this playlist is highly possible to have '#items'
  2791. if (!playlist) return;
  2792. }
  2793.  
  2794. /** @type {HTMLElement | null} */
  2795. const ytdFlexyElm = es.ytdFlexy;
  2796. if (!scriptEnable || !ytdFlexyElm) return;
  2797.  
  2798. let items = querySelectorFromAnchor.call(playlist, "*:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer#playlist:not(.ytd-miniplayer) #items.ytd-playlist-panel-renderer:not(:empty)");
  2799.  
  2800. if (items !== null && playlist.nodeName.toUpperCase() === 'YTD-PLAYLIST-PANEL-RENDERER') {
  2801.  
  2802. let tab_list = document.querySelector("#tab-list");
  2803.  
  2804. if (!tab_list) return;
  2805.  
  2806. let w = getWrapper("tabview-playlist-wrapper");
  2807. let docFrag = new DocumentFragment();
  2808. // avoid immediate reflow for append playlist before append to tab_list
  2809. docFrag.appendChild(w);
  2810. w.appendChild(playlist);
  2811. tab_list.appendChild(docFrag);
  2812. docFrag = null;
  2813. w = null;
  2814.  
  2815. }
  2816. }
  2817.  
  2818.  
  2819. function getCountHText(elm) {
  2820. return `${pageFetchedDataVideoId || 0}...${elm.textContent}`
  2821. }
  2822.  
  2823. // content fix - info & playlist
  2824. // fired at begining, and keep for in case any change
  2825. function mtf_fix_details() {
  2826.  
  2827. if (!scriptEnable) return Promise.resolve(0); // in case
  2828.  
  2829. return Promise.all([
  2830. new Promise(resolve => {
  2831.  
  2832.  
  2833. 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])');
  2834.  
  2835. if (contentToggleBtn) {
  2836.  
  2837. (() => {
  2838. const domElement = contentToggleBtn;
  2839. contentToggleBtn = null;
  2840. // if(!domElement.parentElement) return; // not working in pseudo custom element - parentNode = documentFragment
  2841. const expander = closestDOM.call(domElement, 'ytd-watch-flexy #tab-info ytd-expander')
  2842.  
  2843. if (!expander || expander.nodeType !== 1) return; // checking whether it is still on the page
  2844.  
  2845. if (expander.style.getPropertyValue('--ytd-expander-collapsed-height')) {
  2846. expander.style.setProperty('--ytd-expander-collapsed-height', '')
  2847. }
  2848. nativeCall(expander, [
  2849. { 'property': 'canToggleJobId', 'value': 1 }, // false disable calculateCanCollapse in childrenChanged
  2850. { 'property': 'alwaysToggleable', 'value': false }, // this is checked in childrenChanged
  2851. { 'property': 'recomputeOnResize', 'value': false }, // no need to check toggleable
  2852. { 'property': 'isToggled', 'value': true }, // show full content
  2853. { 'property': 'canToggle', 'value': false }, // hide show more or less btn
  2854. { 'property': 'collapsedHeight', 'value': 999999 } // disable collapsed height check
  2855. ])
  2856.  
  2857. })();
  2858. }
  2859.  
  2860. resolve();
  2861.  
  2862.  
  2863. }),
  2864.  
  2865. new Promise(resolve => {
  2866.  
  2867.  
  2868. let strcturedInfo = document.querySelector('ytd-watch-flexy #tab-info ytd-structured-description-content-renderer.style-scope.ytd-video-secondary-info-renderer[hidden]')
  2869. if (strcturedInfo) {
  2870.  
  2871. (() => {
  2872.  
  2873. strcturedInfo.removeAttribute('hidden');
  2874.  
  2875.  
  2876. setTimeout(() => {
  2877.  
  2878.  
  2879. let e = closestDOM.call(strcturedInfo, 'ytd-watch-flexy #tab-info ytd-expander');
  2880.  
  2881. if (!e) return;
  2882. let s = querySelectorAllFromAnchor.call(e, '#tab-info .more-button.style-scope.ytd-video-secondary-info-renderer[role="button"]');
  2883. if (s.length === 1) {
  2884. let sp = s[0].parentNode
  2885. if (sp.nodeName.toUpperCase() === 'TP-YT-PAPER-BUTTON') {
  2886.  
  2887. sp.click();
  2888. }
  2889. }
  2890.  
  2891. }, 300)
  2892.  
  2893. })();
  2894. }
  2895.  
  2896.  
  2897. resolve();
  2898.  
  2899. }),
  2900.  
  2901. new Promise(resolve => {
  2902.  
  2903.  
  2904. // just in case the playlist is collapsed
  2905. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  2906. if (playlist && playlist.matches('[collapsed], [collapsible]')) {
  2907.  
  2908. (() => {
  2909.  
  2910. const domElement = playlist;
  2911. playlist = null;
  2912. // if(!domElement.parentElement || domElement.nodeType!==1) return; // not working in pseudo custom element - parentNode = documentFragment
  2913. const tablist = closestDOM.call(domElement, 'ytd-watch-flexy #tab-list')
  2914.  
  2915. if (!tablist || tablist.nodeType !== 1) return; // checking whether it is still on the page
  2916.  
  2917. if (domElement.hasAttribute('collapsed')) wAttr(domElement, 'collapsed', false);
  2918. if (domElement.hasAttribute('collapsible')) wAttr(domElement, 'collapsible', false);
  2919. })();
  2920. }
  2921.  
  2922. resolve();
  2923.  
  2924.  
  2925. }),
  2926.  
  2927. new Promise(resolve => {
  2928.  
  2929.  
  2930. let subscribersCount = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #owner #owner-sub-count')
  2931.  
  2932. if (subscribersCount) {
  2933. if (!subscribersCount.hasAttribute('title')) {
  2934. // assume YouTube native coding would not implement [title]
  2935.  
  2936. let ytdWatchMetaDataElm = closestDOM.call(subscribersCount, 'body #primary.ytd-watch-flexy #below ytd-watch-metadata[modern-metapanel-order]:not([tabview-uploader-hover])');
  2937. if (ytdWatchMetaDataElm) {
  2938. ytdWatchMetaDataElm.setAttribute('tabview-uploader-hover', '')
  2939. let _h = 0;
  2940. ytdWatchMetaDataElm.addEventListener('transitionend', function (evt) {
  2941. // no css selector rule required; no delay js function call required
  2942.  
  2943. if (evt.propertyName === 'background-position-y') { // string comparision only
  2944.  
  2945. // If the cursor initially stayed at the owner info area,
  2946. // the mechanism will be broken
  2947. // so implement the true leave detection at their parent
  2948.  
  2949. let isHover = evt.elapsedTime < 0.03; // 50ms @normal; 10ms @hover;
  2950.  
  2951. let cssRoot = this; // no querySelector is required
  2952. if (!isHover) {
  2953. // 50ms is slowest than sub element leave effect
  2954. if (_h > 0) cssRoot.classList.toggle('tabview-uploader-hover', false) // even the order is incorrect, removal of class is safe.
  2955. _h = 0; // in case
  2956. } else if (isHover) {
  2957. // 10ms is faster than sub element hover effect
  2958. // no removal of class in case browser's transition implemention order is incorrect
  2959. _h = 0; // in case
  2960. }
  2961.  
  2962. } else if (evt.propertyName === 'background-position-x') { // string comparision only
  2963.  
  2964. //from one element to another element; hover effect of 2nd element transition end first.
  2965. // _h: 0 -> 1 -> 2 -> 1
  2966.  
  2967. let isHover = evt.elapsedTime < 0.03; // 40ms @normal; 20ms @hover;
  2968. if (isHover) _h++; else _h--;
  2969. let cssRoot = this; // no querySelector is required
  2970. if (_h <= 0) {
  2971. cssRoot.classList.toggle('tabview-uploader-hover', false)
  2972. _h = 0; // in case
  2973. } else if (_h === 1) {
  2974. cssRoot.classList.toggle('tabview-uploader-hover', true)
  2975. }
  2976.  
  2977. }
  2978.  
  2979. }, capturePassive) // capture the hover effect inside the cssRoot
  2980. }
  2981.  
  2982. }
  2983. subscribersCount.setAttribute('title', subscribersCount.textContent); // set at every page update
  2984.  
  2985. }
  2986.  
  2987. resolve();
  2988.  
  2989. })
  2990.  
  2991.  
  2992. ]);
  2993.  
  2994.  
  2995. }
  2996.  
  2997.  
  2998. const innerCommentsFuncs = [
  2999. // comments
  3000. function () {
  3001.  
  3002. let elm = kRef(this.elm);
  3003. _console.log(2907, 1, !!elm)
  3004. if (!elm) return;
  3005.  
  3006. let span = document.querySelector("span#tyt-cm-count")
  3007. let r = '0';
  3008. let txt = elm.textContent
  3009. if (typeof txt == 'string') {
  3010. let m = txt.match(/[\d\,\s]+/)
  3011. if (m) {
  3012. r = m[0].trim()
  3013. }
  3014. }
  3015.  
  3016. if (span) {
  3017. let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]')
  3018. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'normal')
  3019. span.textContent = r;
  3020. }
  3021.  
  3022. setCommentSection(1);
  3023. m_last_count = getCountHText(elm);
  3024. _console.log(2907, 2, m_last_count)
  3025. return true;
  3026. },
  3027. // message
  3028. function () {
  3029.  
  3030. let elm = kRef(this.elm);
  3031. _console.log(2907, 2, !!elm)
  3032. if (!elm) return;
  3033.  
  3034. let span = document.querySelector("span#tyt-cm-count")
  3035. if (span) {
  3036. let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]')
  3037. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'message')
  3038. span.textContent = '\u200B';
  3039. }
  3040.  
  3041. setCommentSection(1);
  3042. m_last_count = getCountHText(elm);
  3043. _console.log(2907, 2, m_last_count)
  3044. return true;
  3045. }
  3046. ]
  3047.  
  3048.  
  3049. let innerDOMCommentsCountTextCache = null;
  3050. function innerDOMCommentsCountLoader() {
  3051. // independent of tabs initialization
  3052. // f() is executed after tabs being ready
  3053.  
  3054. /** @type {HTMLElement | null} */
  3055. const ytdFlexyElm = es.ytdFlexy;
  3056. if (!scriptEnable || !ytdFlexyElm) return;
  3057.  
  3058. _console.log(3434, pageType)
  3059. if (pageType !== 'watch') return;
  3060.  
  3061.  
  3062. /** @type {Array<HTMLElement>} */
  3063. let qmElms = [...document.querySelectorAll('ytd-comments#comments #count.ytd-comments-header-renderer, ytd-comments#comments ytd-item-section-renderer.ytd-comments#sections #header ~ #contents > ytd-message-renderer.ytd-item-section-renderer')]
  3064.  
  3065.  
  3066. const eTime = +`${Date.now() - mTime}00`;
  3067.  
  3068. let res = new Array(qmElms.length);
  3069. res.newFound = false;
  3070.  
  3071.  
  3072. let ci = 0;
  3073. let latest = -1;
  3074.  
  3075. let retrival = cmTime;
  3076. cmTime = eTime;
  3077.  
  3078. for (const qmElm of qmElms) {
  3079.  
  3080. let mgz = 0
  3081. if (qmElm.id === 'count') {
  3082. //#count.ytd-comments-header-renderer
  3083. mgz = 1;
  3084.  
  3085. } else if ((qmElm.textContent || '').trim()) {
  3086. //ytd-message-renderer.ytd-item-section-renderer
  3087. mgz = 2;
  3088. // it is possible to get the message before the header generation.
  3089. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  3090. // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  3091. }
  3092.  
  3093. if (mgz > 0) {
  3094.  
  3095.  
  3096. let lastUpdate = loadedCommentsDT.get(qmElm) || 0;
  3097. let diff = retrival - lastUpdate
  3098. _console.log(2907, diff)
  3099. let isNew = (diff > 4 || diff < -4);
  3100. if (!isNew) {
  3101. loadedCommentsDT.set(qmElm, eTime);
  3102. } else {
  3103. loadedCommentsDT.set(qmElm, eTime + 1);
  3104. res.newFound = true;
  3105. latest = ci;
  3106. }
  3107.  
  3108. res[ci] = {
  3109. elm: mWeakRef(qmElm),
  3110. isNew: isNew,
  3111. isLatest: false, //set afterwards
  3112. f: innerCommentsFuncs[mgz - 1]
  3113. }
  3114.  
  3115. if (DEBUG_LOG) {
  3116. res[ci].status = mgz;
  3117. res[ci].text = qmElm.textContent;
  3118. }
  3119.  
  3120. ci++;
  3121.  
  3122. }
  3123.  
  3124. }
  3125. if (res.length > ci) res.length = ci;
  3126.  
  3127. if (latest >= 0) {
  3128.  
  3129. res[latest].isLatest = true;
  3130.  
  3131.  
  3132. let elm = kRef(res[latest].elm);
  3133. if (elm)
  3134. innerDOMCommentsCountTextCache = elm.textContent;
  3135.  
  3136. } else if (res.length === 1) {
  3137.  
  3138. let qmElm = kRef(res[0].elm);
  3139. let t = null;
  3140. if (qmElm) {
  3141.  
  3142. let t = qmElm.textContent;
  3143. if (t !== innerDOMCommentsCountTextCache) {
  3144.  
  3145.  
  3146. loadedCommentsDT.set(qmElm, eTime + 1);
  3147. res.newFound = true;
  3148.  
  3149. res[0].isNew = true;
  3150. latest = 0;
  3151.  
  3152. res[latest].isLatest = true;
  3153.  
  3154. }
  3155.  
  3156. innerDOMCommentsCountTextCache = t;
  3157.  
  3158.  
  3159. }
  3160.  
  3161.  
  3162. }
  3163.  
  3164.  
  3165. _console.log(2908, res, Q.comments_section_loaded)
  3166.  
  3167. _console.log(696, res.map(e => ({
  3168.  
  3169. text: kRef(e.elm).textContent,
  3170. isNew: e.isNew,
  3171. isLatest: e.isLatest
  3172.  
  3173. })))
  3174.  
  3175. return res;
  3176.  
  3177. }
  3178.  
  3179. function restoreFetching() {
  3180.  
  3181.  
  3182. if (mtf_forceCheckLiveVideo_disable === 2) return;
  3183.  
  3184.  
  3185. const ytdFlexyElm = es.ytdFlexy;
  3186. if (!ytdFlexyElm) return;
  3187.  
  3188. _console.log(2901)
  3189.  
  3190. if ((ytdFlexyElm.getAttribute('tyt-comments') || '').indexOf('K') >= 0) return;
  3191.  
  3192. _console.log(2902)
  3193.  
  3194. let visibleComments = querySelectorFromAnchor.call(ytdFlexyElm, 'ytd-comments#comments:not([hidden])')
  3195. if (!visibleComments) return;
  3196.  
  3197. _console.log(2903)
  3198.  
  3199.  
  3200. ytdFlexyElm.setAttribute('tyt-comments', 'Kz');
  3201.  
  3202. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]');
  3203. let span = querySelectorFromAnchor.call(tabBtn, 'span#tyt-cm-count');
  3204. tabBtn.removeAttribute('loaded-comment')
  3205. span.innerHTML = '';
  3206.  
  3207. if (tabBtn) {
  3208. tabBtn.classList.remove("tab-btn-hidden")
  3209. }
  3210.  
  3211. _console.log(2905)
  3212.  
  3213.  
  3214. }
  3215.  
  3216. const resultCommentsCountCaching = (res) => {
  3217. // update fetchCounts by res
  3218. // indepedent of previous state of fetchCounts
  3219. _console.log(2908, 10, res)
  3220. if (!res) return;
  3221. fetchCounts.count = res.length;
  3222. //if(fetchCounts.new && !document.documentElement.contains(fetchCounts.new.elm)) fetchCounts.new = null;
  3223. //if(fetchCounts.base && !document.documentElement.contains(fetchCounts.base.elm)) fetchCounts.base = null;
  3224. if (fetchCounts.new) return;
  3225. let newFound = res.newFound;
  3226. if (!newFound) {
  3227. if (res.length === 1) {
  3228. fetchCounts.base = res[0];
  3229. return false;
  3230. }
  3231. } else if (res.length === 1) {
  3232. fetchCounts.new = res[0];
  3233. return true;
  3234. } else if (res.length > 1) {
  3235. for (const entry of res) {
  3236. if (entry.isLatest === true && entry.isNew) {
  3237. fetchCounts.new = entry;
  3238. return true;
  3239. }
  3240. }
  3241. }
  3242. }
  3243.  
  3244. const domInit_comments = () => {
  3245.  
  3246.  
  3247. let comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  3248. if (!comments) return;
  3249.  
  3250. // once per {ytd-comments#comments} detection
  3251.  
  3252. const ytdFlexyElm = es.ytdFlexy;
  3253. if (!scriptEnable || !ytdFlexyElm) return;
  3254.  
  3255. _console.log(3901)
  3256.  
  3257. if (mtoVisibility_Comments.bindElement(comments)) {
  3258. mtoVisibility_Comments.observer.check(9);
  3259. }
  3260.  
  3261.  
  3262. };
  3263.  
  3264. const domInit_teaserInfo = () => {
  3265. //obsolete?
  3266.  
  3267. let teaserInfo = document.querySelector('#description-and-actions.style-scope.ytd-watch-metadata > #description ytd-text-inline-expander:not([tabview-removed-duplicate])');
  3268.  
  3269. if (!teaserInfo) return;
  3270.  
  3271. // for Teaser UI
  3272. // once per {#description-and-actions.style-scope.ytd-watch-metadata > #description > ytd-text-inline-expander} detection
  3273.  
  3274. const ytdFlexyElm = es.ytdFlexy;
  3275. if (!scriptEnable || !ytdFlexyElm) return;
  3276. let addedInfo = document.querySelector('#tab-info ytd-expander[tabview-info-expander]');
  3277.  
  3278. if (!addedInfo) return;
  3279.  
  3280. scriptletDeferred.debounce(() => {
  3281.  
  3282. teaserInfo.setAttribute('tabview-removed-duplicate', '')
  3283. teaserInfo.dispatchEvent(new CustomEvent('tabview-no-duplicate-info'))
  3284.  
  3285. })
  3286.  
  3287.  
  3288. }
  3289.  
  3290.  
  3291. const FP = {
  3292.  
  3293. mtf_attrPlaylist: (attrName, newValue) => {
  3294. //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single
  3295. //::attr ~ hidden
  3296. //console.log(1210)
  3297.  
  3298. _console.log(21311)
  3299. if (!scriptEnable) return;
  3300. if (pageType !== 'watch') return;
  3301. /** @type {HTMLElement|null} */
  3302. let cssElm = es.ytdFlexy;
  3303. if (!cssElm) return;
  3304.  
  3305. _console.log(21312)
  3306.  
  3307. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist'); // can be null if it is manually triggered
  3308. let isAnyPlaylistExist = playlist && !playlist.hasAttribute('hidden');
  3309. const tabBtn = document.querySelector('[tyt-tab-content="#tab-list"]');
  3310. //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden'))
  3311. if (tabBtn) {
  3312. //console.log('attr playlist changed')
  3313. let isPlaylistTabHidden = tabBtn.classList.contains('tab-btn-hidden')
  3314. if (isPlaylistTabHidden && isAnyPlaylistExist) {
  3315. //console.log('attr playlist changed - no hide')
  3316. tabBtn.classList.remove("tab-btn-hidden");
  3317. } else if (!isPlaylistTabHidden && !isAnyPlaylistExist) {
  3318. //console.log('attr playlist changed - add hide')
  3319. hideTabBtn(tabBtn);
  3320. }
  3321. }
  3322. /* visible layout for triggering hidden removal */
  3323.  
  3324. },
  3325. mtf_attrComments: (attrName, newValue) => {
  3326. //attr mutation checker - {ytd-comments#comments} \single
  3327. //::attr ~ hidden
  3328.  
  3329. // *** consider this can happen immediately after pop state. timeout / interval might clear out.
  3330.  
  3331. renderDeferred.resolved && resultCommentsCountCaching(innerDOMCommentsCountLoader());
  3332. // this is triggered by mutationobserver, the comment count update might have ouccred
  3333.  
  3334. if (pageType !== 'watch') return;
  3335.  
  3336. let comments = document.querySelector('ytd-comments#comments')
  3337. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]');
  3338. if (!comments || !tabBtn) return;
  3339. let isCommentHidden = comments.hasAttribute('hidden')
  3340. //console.log('attr comments changed')
  3341.  
  3342.  
  3343. const ytdFlexyElm = es.ytdFlexy;
  3344. if (!scriptEnable || !ytdFlexyElm) return;
  3345.  
  3346. if (mtf_forceCheckLiveVideo_disable === 2) {
  3347.  
  3348. } else if (!isCommentHidden) {
  3349.  
  3350. ytdFlexyElm.setAttribute('tyt-comments', 'Kv');
  3351. if (!fetchCounts.fetched) {
  3352. emptyCommentSection();
  3353. }
  3354. //_console.log(9360, 71);
  3355. tabBtn.classList.remove("tab-btn-hidden") //if contains
  3356.  
  3357. } else if (isCommentHidden) {
  3358.  
  3359. ytdFlexyElm.setAttribute('tyt-comments', 'Kh');
  3360. if (pageType === 'watch' && Q.comments_section_loaded === 1) {
  3361. emptyCommentSection();
  3362. _console.log(9360, 72);
  3363. }
  3364.  
  3365. }
  3366.  
  3367.  
  3368. },
  3369.  
  3370. mtf_attrChatroom: (attrName, newValue) => {
  3371. //attr mutation checker - {ytd-live-chat-frame#chat} \single
  3372. //::attr ~ collapsed
  3373.  
  3374. const ytdFlexyElm = es.ytdFlexy;
  3375. if (!scriptEnable || !ytdFlexyElm) return;
  3376. if (pageType !== 'watch') return;
  3377.  
  3378. setToggleBtnTxt();
  3379.  
  3380. layoutStatusMutex.lockWith(unlock => {
  3381.  
  3382. const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
  3383. /** @type {HTMLElement | null} */
  3384. const cssElm = es.ytdFlexy;
  3385.  
  3386. if (!chatBlock || !cssElm) {
  3387. unlock();
  3388. return;
  3389. }
  3390.  
  3391. if (pageType !== 'watch') {
  3392. unlock();
  3393. return;
  3394. }
  3395.  
  3396. let newAttrV = '';
  3397. //mtf_attrChatroom => chat exist => tyt-chat non-null
  3398.  
  3399. let isCollapsed = !!chatBlock.hasAttribute('collapsed');
  3400.  
  3401. let currentAttr = cssElm.getAttribute('tyt-chat');
  3402.  
  3403. if (currentAttr !== null) { //string // [+-]?[az]+[az\$]+
  3404. let isPlusMinus = currentAttr.charCodeAt(0) < 46; // 43 OR 45
  3405. if (isPlusMinus) newAttrV = currentAttr.substring(1);
  3406. }
  3407.  
  3408. if (isCollapsed) newAttrV = `-${newAttrV}`;
  3409. if (!isCollapsed) newAttrV = `+${newAttrV}`;
  3410.  
  3411. wAttr(cssElm, 'tyt-chat', newAttrV);
  3412.  
  3413.  
  3414. if (typeof newAttrV === 'string' && !isCollapsed) lstTab.lastPanel = '#chatroom';
  3415.  
  3416. if (!isCollapsed && isAnyActiveTab() && isWideScreenWithTwoColumns() && !isTheater()) {
  3417. switchTabActivity(null);
  3418. timeline.setTimeout(unlock, 40);
  3419. } else {
  3420. unlock();
  3421. }
  3422.  
  3423. if (isCollapsed) {
  3424. chatBlock.removeAttribute('tyt-iframe-loaded');
  3425. // console.log(922,1)
  3426. // buggy; this section might not be correctly executed.
  3427. // guess no collaspe change but still iframe will distory and reload.
  3428. let btn = document.querySelector('tyt-iframe-popup-btn')
  3429. if(btn) btn.remove();
  3430. }
  3431.  
  3432. })
  3433.  
  3434.  
  3435. },
  3436.  
  3437. mtf_attrEngagementPanel: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  3438. //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple
  3439. //::attr ~ visibility
  3440.  
  3441. const cssElm = es.ytdFlexy;
  3442. if (!scriptEnable || !cssElm) return;
  3443. let found = null
  3444. if (mutations === 9) {
  3445. found = observer
  3446. } else {
  3447. 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])')) {
  3448. // do nothing
  3449. } else {
  3450. mtoVisibility_EngagementPanel.clear(true)
  3451. storeLastPanel = null;
  3452. wAttr(cssElm, 'tyt-ep-visible', false);
  3453. }
  3454. return
  3455. }
  3456. let nextValue = engagement_panels_().value;
  3457. let previousValue = +cssElm.getAttribute('tyt-ep-visible') || 0;
  3458. if (nextValue === 0 || previousValue === nextValue) return
  3459. cssElm.setAttribute('tyt-ep-visible', nextValue);
  3460. lstTab.lastPanel = `#engagement-panel-${nextValue}`;
  3461. storeLastPanel = mWeakRef(found)
  3462. let tabsDeferredSess = pageSession.session();
  3463. if (!scriptEnable && tabsDeferred.resolved) { }
  3464. else tabsDeferred.debounce(() => {
  3465. if (!tabsDeferredSess.isValid) return;
  3466. tabsDeferredSess = null;
  3467. if (es.storeLastPanel !== found) return
  3468. layoutStatusMutex.lockWith(unlock => {
  3469. if (es.storeLastPanel === found && whenEngagemenetPanelVisible()) {
  3470. timeline.setTimeout(unlock, 40);
  3471. } else {
  3472. unlock();
  3473. }
  3474. })
  3475. })
  3476. }
  3477.  
  3478. }
  3479.  
  3480.  
  3481. function variableResets() {
  3482.  
  3483. // reset variables when it is confirmed a new page is loaded
  3484.  
  3485. lstTab =
  3486. {
  3487. lastTab: null, //tab-xxx
  3488. lastPanel: null,
  3489. last: null
  3490. };
  3491.  
  3492. scriptEnable = false;
  3493. ytdFlexy = null;
  3494. wls.layoutStatus = 0;
  3495.  
  3496. mtoVisibility_Playlist.clear(true)
  3497. mtoVisibility_Comments.clear(true)
  3498.  
  3499. mtoVisibility_Chatroom.clear(true)
  3500. mtoFlexyAttr.clear(true)
  3501.  
  3502.  
  3503. for (const elem of document.querySelectorAll('ytd-expander[tabview-info-expander]')) {
  3504. elem.removeAttribute('tabview-info-expander');
  3505. }
  3506.  
  3507. mtf_chatBlockQ = null;
  3508.  
  3509. }
  3510.  
  3511.  
  3512. function getWord(tag) {
  3513. return langWords[pageLang][tag] || langWords['en'][tag] || '';
  3514. }
  3515.  
  3516.  
  3517. function getTabsHTML() {
  3518.  
  3519. const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}<span>${getWord('videos')}</span>`;
  3520. const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}<span>${getWord('info')}</span>`;
  3521. const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}<span>${getWord('playlist')}</span>`;
  3522.  
  3523. let str1 = `
  3524. <paper-ripple class="style-scope yt-icon-button">
  3525. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  3526. <div id="waves" class="style-scope paper-ripple"></div>
  3527. </paper-ripple>
  3528. `;
  3529.  
  3530. let str_fbtns = `
  3531. <div class="font-size-right">
  3532. <div class="font-size-btn font-size-plus" tyt-di="8rdLQ">
  3533. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  3534. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  3535. <path d="M12 25H38M25 12V38"/>
  3536. </svg>
  3537. </div><div class="font-size-btn font-size-minus" tyt-di="8rdLQ">
  3538. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  3539. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  3540. <path d="M12 25h26"/>
  3541. </svg>
  3542. </div>
  3543. </div>
  3544. `.replace(/[\r\n]+/g, '');
  3545.  
  3546. const str_tabs = [
  3547. `<a id="tab-btn1" tyt-di="q9Kjc" tyt-tab-content="#tab-info" class="tab-btn">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
  3548. `<a id="tab-btn3" tyt-di="q9Kjc" tyt-tab-content="#tab-comments" class="tab-btn">${svgElm(16, 16, 120, 120, svgComments)}<span id="tyt-cm-count"></span>${str1}${str_fbtns}</a>`,
  3549. `<a id="tab-btn4" tyt-di="q9Kjc" tyt-tab-content="#tab-videos" class="tab-btn">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
  3550. `<a id="tab-btn5" tyt-di="q9Kjc" tyt-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`
  3551. ].join('');
  3552.  
  3553. let addHTML = `
  3554. <div id="right-tabs">
  3555. <tabview-view-pos-thead></tabview-view-pos-thead>
  3556. <header>
  3557. <div id="material-tabs">
  3558. ${str_tabs}
  3559. </div>
  3560. </header>
  3561. <div class="tab-content">
  3562. <div id="tab-info" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3563. <div id="tab-comments" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3564. <div id="tab-videos" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3565. <div id="tab-list" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3566. </div>
  3567. </div>
  3568. `;
  3569.  
  3570. return addHTML;
  3571.  
  3572. }
  3573.  
  3574. function getLang() {
  3575.  
  3576. let lang = 'en';
  3577. let htmlLang = ((document || 0).documentElement || 0).lang || '';
  3578. switch (htmlLang) {
  3579. case 'en':
  3580. case 'en-GB':
  3581. lang = 'en';
  3582. break;
  3583. case 'de':
  3584. case 'de-DE':
  3585. lang = 'du';
  3586. break;
  3587. case 'fr':
  3588. case 'fr-CA':
  3589. case 'fr-FR':
  3590. lang = 'fr';
  3591. break;
  3592. case 'zh-Hant':
  3593. case 'zh-Hant-HK':
  3594. case 'zh-Hant-TW':
  3595. lang = 'tw';
  3596. break;
  3597. case 'zh-Hans':
  3598. case 'zh-Hans-CN':
  3599. lang = 'cn';
  3600. break;
  3601. case 'ja':
  3602. case 'ja-JP':
  3603. lang = 'jp';
  3604. break;
  3605. case 'ko':
  3606. case 'ko-KR':
  3607. lang = 'kr';
  3608. break;
  3609. case 'ru':
  3610. case 'ru-RU':
  3611. lang = 'ru';
  3612. break;
  3613. default:
  3614. lang = 'en';
  3615. }
  3616.  
  3617. if (langWords[lang]) pageLang = lang; else pageLang = 'en';
  3618.  
  3619. }
  3620.  
  3621. // function checkEvtTarget(evt, nodeNames) {
  3622. // return nodeNames.includes((((evt || 0).target || 0).nodeName || 0));
  3623. // }
  3624.  
  3625. function pageCheck() {
  3626. // yt-player-updated
  3627. // yt-page-data-updated
  3628. // yt-watch-comments-ready - omitted
  3629. // [is-two-columns_] attr changed => layout changed
  3630.  
  3631. /** @type {HTMLElement | null} */
  3632. const ytdFlexyElm = es.ytdFlexy;
  3633. if (!scriptEnable || !ytdFlexyElm) return;
  3634.  
  3635. let comments = querySelectorFromAnchor.call(ytdFlexyElm, '#primary.ytd-watch-flexy ytd-watch-metadata ~ ytd-comments#comments');
  3636. if (comments) {
  3637. let tabComments = document.querySelector('#tab-comments');
  3638. if (tabComments) {
  3639. tabComments.appendChild(comments);
  3640. }
  3641. }
  3642.  
  3643. mtf_append_playlist(null); // playlist relocated after layout changed
  3644.  
  3645. fixTabs();
  3646.  
  3647. mtf_autocomplete_search();
  3648.  
  3649. }
  3650.  
  3651. function globalHook(eventType, func) {
  3652. if (!func) return;
  3653.  
  3654. const count = (globalHook_hashs[eventType] || 0) + 1;
  3655.  
  3656. globalHook_hashs[eventType] = count;
  3657.  
  3658. const s = globalHook_symbols[count - 1] || (globalHook_symbols[count - 1] = Symbol());
  3659.  
  3660. document.addEventListener(eventType, function (evt) {
  3661. if (evt[s]) return;
  3662. evt[s] = true;
  3663. new Promise(() => {
  3664. func(evt);
  3665. })
  3666.  
  3667. }, capturePassive)
  3668.  
  3669. }
  3670.  
  3671. async function makeHeaderFloat() {
  3672. if (isMakeHeaderFloatCalled) return;
  3673. isMakeHeaderFloatCalled = true;
  3674. await Promise.resolve(0);
  3675.  
  3676.  
  3677. const [header, headerP, navElm] = await Promise.all([
  3678. new Promise(f => f(document.querySelector("#right-tabs header"))),
  3679.  
  3680. new Promise(f => f(document.querySelector("#right-tabs tabview-view-pos-thead"))),
  3681.  
  3682. new Promise(f => f(document.querySelector('#masthead-container, #masthead')))
  3683.  
  3684. ]);
  3685.  
  3686. let ito_dt = 0;
  3687. let ito = new IntersectionObserver((entries) => {
  3688.  
  3689. let xyStatus = null;
  3690.  
  3691. //console.log(entries);
  3692.  
  3693. let xRect = null;
  3694. let rRect = null;
  3695.  
  3696. for (const entry of entries) {
  3697. if (!entry.boundingClientRect || !entry.rootBounds) continue; // disconnected from DOM tree
  3698. if (!entry.isIntersecting && entry.boundingClientRect.y <= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  3699. xyStatus = 2;
  3700. xRect = entry.boundingClientRect;
  3701. rRect = entry.rootBounds;
  3702. } else if (entry.isIntersecting && entry.boundingClientRect.y >= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  3703. xyStatus = 1;
  3704. xRect = entry.boundingClientRect;
  3705. rRect = entry.rootBounds;
  3706. }
  3707. }
  3708. let p = wls.layoutStatus;
  3709. //console.log(document.documentElement.clientWidth)
  3710. if (xyStatus !== null) {
  3711.  
  3712. if (xyStatus === 2 && isStickyHeaderEnabled === true) {
  3713.  
  3714. } else if (xyStatus === 1 && isStickyHeaderEnabled === false) {
  3715.  
  3716. } else {
  3717. singleColumnScrolling2(xyStatus, xRect.width, {
  3718. left: xRect.left,
  3719. right: rRect.width - xRect.right
  3720. });
  3721. }
  3722.  
  3723. }
  3724.  
  3725. let tdt = Date.now();
  3726. ito_dt = tdt;
  3727. setTimeout(() => {
  3728. if (ito_dt !== tdt) return;
  3729. if (p !== wls.layoutStatus) singleColumnScrolling();
  3730. }, 300)
  3731.  
  3732. },
  3733. {
  3734. rootMargin: `0px 0px 0px 0px`,
  3735. threshold: [0]
  3736. })
  3737.  
  3738. ito.observe(headerP)
  3739.  
  3740. }
  3741.  
  3742. function checkPlaylistForInitialization() {
  3743. // if the page url is with playlist; renderer event might not occur.
  3744.  
  3745. // playlist already added to dom; this is to set the visibility event and change hidden status
  3746.  
  3747. let m_playlist = document.querySelector(`#tab-list ytd-playlist-panel-renderer#playlist:not([o3r-${sa_playlist}])`)
  3748.  
  3749. // once per {ytd-playlist-panel-renderer#playlist} detection
  3750.  
  3751. _console.log(3902, !!m_playlist)
  3752.  
  3753. const ytdFlexyElm = es.ytdFlexy;
  3754. if (!scriptEnable || !ytdFlexyElm) { }
  3755. else if (m_playlist) {
  3756.  
  3757. if (mtoVisibility_Playlist.bindElement(m_playlist)) {
  3758. mtoVisibility_Playlist.observer.check(9); //delay check required for browser bug - hidden changed not triggered
  3759. }
  3760. m_playlist = null;
  3761.  
  3762. }
  3763.  
  3764. FP.mtf_attrPlaylist();
  3765.  
  3766. Promise.resolve(0).then(() => {
  3767. // ['tab-btn', 'tab-btn', 'tab-btn active', 'tab-btn tab-btn-hidden']
  3768. // bug
  3769. const ytdFlexyElm = es.ytdFlexy;
  3770. if (!scriptEnable || !ytdFlexyElm) return;
  3771. if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tyt-tab') + '').indexOf('#tab-') === 0 && /https\:\/\/www\.youtube\.com\/watch.*[\?\&]list=[\w\-\_]+/.test(location.href)) {
  3772. if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  3773. }
  3774. })
  3775.  
  3776. }
  3777.  
  3778.  
  3779. const _pageBeingInit = function () {
  3780.  
  3781. pageSession.inc();
  3782. if (pageSession.sid > 9e9) pageSession.sid = 9;
  3783.  
  3784. fetchCounts = {
  3785. base: null,
  3786. new: null,
  3787. fetched: false,
  3788. count: null
  3789. }
  3790. pageFetchedData = null;
  3791. pageFetchedDataVideoId = null;
  3792. chatroomDetails = null;
  3793. }
  3794.  
  3795. const pageBeingInit = function () {
  3796.  
  3797. if(_isPageFirstLoaded && location.pathname==='/watch') document.documentElement.setAttribute('tyt-lock', '')
  3798.  
  3799. // call regardless pageType
  3800. // run once on / before pageSeq2
  3801.  
  3802. let action = 0;
  3803. if (tabsDeferred.resolved) {
  3804. action = 1;
  3805. } else if (renderDeferred.resolved) {
  3806. // in case , rarely, tabsDeferred not yet resolved but animateLoadDeferred resolved
  3807. action = 2;
  3808. }
  3809. renderIdentifier++;
  3810. if (renderIdentifier > 1e9) renderIdentifier = 9;
  3811. renderDeferred.reset();
  3812.  
  3813. if (action === 1) {
  3814. comments_loader = 1;
  3815. tabsDeferred.reset();
  3816. if ((firstLoadStatus & 8) === 0) {
  3817. innerDOMCommentsCountLoader(); //ensure the previous record is saved
  3818. // no need to cache to the rendering state
  3819. _pageBeingInit();
  3820. } else if ((firstLoadStatus & 2) === 2) {
  3821. firstLoadStatus -= 2;
  3822. script_inject_js1.inject();
  3823. }
  3824. _console.log('pageBeingInit', firstLoadStatus)
  3825. }
  3826.  
  3827. if (pageRendered === 2) {
  3828. pageRendered = 0;
  3829. let elmPL = document.querySelector('tabview-view-ploader');
  3830. if (elmPL) elmPL.remove();
  3831. pageRendered = 0;
  3832. }
  3833.  
  3834. if (!scriptletDeferred.resolved) {
  3835. // just in case, should not happen this.
  3836. // this is to clear the pending queue if scriptlet is not ready.
  3837. scriptletDeferred.reset();
  3838. }
  3839.  
  3840. };
  3841.  
  3842. const advanceFetch = async function () {
  3843. if (pageType === 'watch' && !fetchCounts.new && !fetchCounts.fetched) {
  3844. renderDeferred.resolved && resultCommentsCountCaching(innerDOMCommentsCountLoader());
  3845. if (renderDeferred.resolved && !fetchCounts.new) {
  3846. window.dispatchEvent(new Event("scroll"));
  3847. }
  3848. }
  3849. };
  3850.  
  3851. function getFinalComments() {
  3852.  
  3853. if ((comments_loader & 3) === 3) { } else return;
  3854. comments_loader = 0;
  3855.  
  3856. let ei = 0;
  3857.  
  3858. function execute() {
  3859. //sync -> animateLoadDeferred.resolved always true
  3860.  
  3861. if (!renderDeferred.resolved) return;
  3862.  
  3863. _console.log(2323)
  3864.  
  3865. if (Q.comments_section_loaded !== 0) return;
  3866. if (fetchCounts.fetched) return;
  3867.  
  3868.  
  3869. let ret = innerDOMCommentsCountLoader();
  3870. resultCommentsCountCaching(ret);
  3871.  
  3872. if (fetchCounts.new && !fetchCounts.fetched) {
  3873.  
  3874. _console.log(4512, 4, Q.comments_section_loaded, fetchCounts.new, !fetchCounts.fetched)
  3875. if (fetchCounts.new.f()) {
  3876. fetchCounts.fetched = true;
  3877. _console.log(9972, 'fetched = true')
  3878. fetchCommentsFinished();
  3879. }
  3880.  
  3881. return;
  3882. }
  3883.  
  3884.  
  3885. ei++;
  3886.  
  3887. if (fetchCounts.base && !fetchCounts.new && !fetchCounts.fetched && fetchCounts.count === 1) {
  3888.  
  3889.  
  3890. let elm = kRef(fetchCounts.base.elm);
  3891. let txt = elm ? getCountHText(elm) : null;
  3892. let condi1 = ei > 7;
  3893. let condi2 = txt === m_last_count;
  3894. if (condi1 || condi2) {
  3895.  
  3896. if (fetchCounts.base.f()) {
  3897. fetchCounts.fetched = true;
  3898. _console.log(9972, 'fetched = true')
  3899. //return true;
  3900. fetchCommentsFinished();
  3901. }
  3902.  
  3903. }
  3904.  
  3905. }
  3906.  
  3907. if (!fetchCounts.fetched) {
  3908. if (ei > 7) {
  3909. let elm = ret.length === 1 ? kRef(ret[0].elm) : null;
  3910. let txt = elm ? getCountHText(elm) : null;
  3911. if (elm && txt !== m_last_count) {
  3912. fetchCounts.base = null;
  3913. fetchCounts.new = ret[0];
  3914. fetchCounts.new.f();
  3915. fetchCounts.fetched = true;
  3916. _console.log(9979, 'fetched = true')
  3917. fetchCommentsFinished();
  3918. }
  3919. return;
  3920. }
  3921. return true;
  3922. }
  3923.  
  3924. }
  3925.  
  3926.  
  3927. async function alCheckFn(ks) {
  3928.  
  3929. let alCheckCount = 9;
  3930. let alCheckInterval = 420;
  3931.  
  3932. do {
  3933.  
  3934. if (renderIdentifier !== ks) break;
  3935. if (alCheckCount === 0) break;
  3936. if (execute() !== true) break;
  3937. --alCheckCount;
  3938.  
  3939. await new Promise(r => setTimeout(r, alCheckInterval));
  3940.  
  3941. } while (true)
  3942.  
  3943. }
  3944. let ks = renderIdentifier;
  3945. renderDeferred.debounce(() => {
  3946. if (ks !== renderIdentifier) return
  3947. alCheckFn(ks);
  3948.  
  3949. });
  3950.  
  3951.  
  3952. }
  3953.  
  3954.  
  3955. let g_check_detail_A = 0;
  3956. let checkDuplicateRes = null;
  3957. function setHiddenStateForDesc(){
  3958. let ytdFlexyElm = es.ytdFlexy
  3959. if (!ytdFlexyElm) return
  3960. let hiddenBool = !document.fullscreenElement ? ytdFlexyElm.classList.contains('tabview-info-duplicated') : false
  3961. let elm
  3962. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-watch-metadata.ytd-watch-flexy[modern-metapanel] #description #plain-snippet-text')
  3963. if (elm) {
  3964. wAttr(elm, 'hidden', hiddenBool)
  3965. }
  3966. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-watch-metadata.ytd-watch-flexy[modern-metapanel] #description #formatted-snippet-text')
  3967. if (elm) {
  3968. wAttr(elm, 'hidden', hiddenBool)
  3969. }
  3970. }
  3971. function checkDuplicatedInfo_then(isCheck, checkDuplicateRes) {
  3972.  
  3973. const ytdFlexyElm = es.ytdFlexy;
  3974. if (!ytdFlexyElm) return; //unlikely
  3975.  
  3976. let cssbool_c1 = false, cssbool_c2 = false, cssbool_c3 = false;
  3977. if (isCheck === 5) {
  3978.  
  3979. if (ytdFlexyElm.matches('.tabview-info-duplicated[flexy]')) {
  3980. cssbool_c1 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#description.style-scope.ytd-watch-metadata > #description-inner:only-child');
  3981. cssbool_c2 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#tab-info ytd-expander #description.ytd-video-secondary-info-renderer');
  3982. cssbool_c2 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#tab-info ytd-expander ytd-rich-metadata-renderer.ytd-rich-metadata-row-renderer');
  3983. }
  3984.  
  3985. if (typeof checkDuplicateRes === 'boolean') {
  3986. setHiddenStateForDesc();
  3987. }
  3988. }
  3989.  
  3990. ytdFlexyElm.setAttribute('tyt-has', `${cssbool_c1 ? 'A' : 'a'}${cssbool_c2 ? 'B' : 'b'}${cssbool_c3 ? 'C' : 'c'}`);
  3991.  
  3992. }
  3993. function checkDuplicatedInfo(req) {
  3994. // console.log('checkDuplicatedInfo')
  3995.  
  3996.  
  3997. async function checkDuplicatedInfoContentEqual(desc1, desc2) {
  3998. // basically desc1 and desc2 are content identical
  3999. // however, class name order could be different
  4000.  
  4001. let txt1 = new Promise(r => r(desc1.textContent))
  4002.  
  4003. let txt2 = new Promise(r => r(desc2.textContent))
  4004.  
  4005.  
  4006. let [res1, res2] = await Promise.all([txt1, txt2]);
  4007.  
  4008. return { res: res1 === res2 }
  4009. }
  4010.  
  4011. async function checkDuplicatedInfoInner() {
  4012.  
  4013. const ytdFlexyElm = es.ytdFlexy;
  4014. if (!ytdFlexyElm) return; //unlikely
  4015.  
  4016. let t = Date.now();
  4017. g_check_detail_A = t;
  4018.  
  4019. ytdFlexyElm.classList.toggle('tabview-info-duplicated', true) // hide first;
  4020.  
  4021. await new Promise(resolve => setTimeout(resolve, 1)); // mcrcr might be not yet initalized
  4022.  
  4023.  
  4024. if (g_check_detail_A !== t) return;
  4025.  
  4026. let elm
  4027. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-watch-metadata.ytd-watch-flexy[modern-metapanel] #description #plain-snippet-text')
  4028. if (elm) {
  4029. wAttr(elm, 'hidden', false)
  4030. }
  4031. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-watch-metadata.ytd-watch-flexy[modern-metapanel] #description #formatted-snippet-text')
  4032. if (elm) {
  4033. wAttr(elm, 'hidden', false)
  4034. }
  4035. await Promise.resolve(0);
  4036.  
  4037. // the class added before can be removed from the external coding
  4038.  
  4039. function mrcrf(mrcr) {
  4040. let tmp;
  4041. if (mrcr) {
  4042. if (tmp = querySelectorFromAnchor.call(mrcr, '#always-shown[hidden]:empty')) tmp.removeAttribute('hidden')
  4043. if (tmp = querySelectorFromAnchor.call(mrcr, '#collapsible[hidden]:empty')) tmp.removeAttribute('hidden')
  4044. }
  4045. }
  4046.  
  4047. let mrcr1 = document.querySelector('ytd-watch-metadata.ytd-watch-flexy[modern-metapanel] > ytd-metadata-row-container-renderer.style-scope.ytd-watch-metadata')
  4048. mrcrf(mrcr1);
  4049. await Promise.resolve(0);
  4050. let mrcr2 = document.querySelector('ytd-expander.ytd-video-secondary-info-renderer ytd-metadata-row-container-renderer.style-scope.ytd-video-secondary-info-renderer')
  4051. mrcrf(mrcr2);
  4052. await Promise.resolve(0);
  4053.  
  4054. let desc1 = null;
  4055. let desc2 = document.querySelector('ytd-expander.ytd-video-secondary-info-renderer #description.style-scope.ytd-video-secondary-info-renderer > yt-formatted-string.content.style-scope.ytd-video-secondary-info-renderer[split-lines]:not(:empty)');
  4056. await Promise.resolve(0);
  4057.  
  4058. if (desc2 && desc2.firstElementChild === null) {
  4059. // plainText = true;
  4060. desc1 = document.querySelector('ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata #plain-snippet-text.ytd-text-inline-expander');
  4061. }
  4062. if (!desc1) desc1 = document.querySelector('ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata yt-formatted-string#formatted-snippet-text.style-scope.ytd-text-inline-expander:not(:empty)');
  4063. await Promise.resolve(0);
  4064.  
  4065. if (desc1) {
  4066. let parentContainer = req.descMetaLines;
  4067. // hidden
  4068.  
  4069. // example video
  4070. // https://www.youtube.com/watch?v=R65uouhSYJ0
  4071.  
  4072. if (parentContainer) {
  4073.  
  4074. let m = querySelectorFromAnchor.call(parentContainer, 'ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata yt-formatted-string[split-lines].ytd-text-inline-expander');
  4075.  
  4076. if (m) {
  4077.  
  4078. if (m.hasAttribute('hidden')) {
  4079.  
  4080. let expandBtn = querySelectorFromAnchor.call(parentContainer, 'tp-yt-paper-button#expand.ytd-text-inline-expander:not([hidden])');
  4081.  
  4082. if (expandBtn) {
  4083.  
  4084. expandBtn.click();
  4085. await new Promise(r => setTimeout(r, 30));
  4086. if (!m.hasAttribute('hidden')) desc1 = m;
  4087. }
  4088.  
  4089. } else {
  4090.  
  4091. desc1 = m;
  4092.  
  4093. }
  4094.  
  4095. }
  4096.  
  4097. }
  4098. }
  4099.  
  4100. //console.log(desc1, desc2)
  4101.  
  4102. let infoDuplicated = true;
  4103.  
  4104. let mb1 = null, mb2 = null;
  4105.  
  4106. if (desc2 === null && desc1 !== null && desc1.textContent === '') {
  4107. // example: https://www.youtube.com/watch?v=l9m3OpH9pbI
  4108. desc1 = null
  4109. }
  4110.  
  4111. if ((desc1 === null) ^ (desc2 === null)) {
  4112. infoDuplicated = false;
  4113. } else if ((mrcr1 === null) ^ (mrcr2 === null)) {
  4114. infoDuplicated = false;
  4115. } else {
  4116.  
  4117. await Promise.all([
  4118.  
  4119. (mrcr1 !== mrcr2 && mrcr1 !== null && mrcr2 !== null) ?
  4120. checkDuplicatedInfoContentEqual(mrcr1, mrcr2).then((o) => {
  4121. //console.log('mrcr', o.res)
  4122. let { res, pNodeA, pNodeB } = o;
  4123. mb1 = res;
  4124.  
  4125. if (res !== true) infoDuplicated = false;
  4126. }) : null,
  4127.  
  4128. (desc1 !== desc2 && desc1 !== null && desc2 !== null) ?
  4129. checkDuplicatedInfoContentEqual(desc1, desc2).then((o) => {
  4130. //console.log('desc', o.res)
  4131. let { res, pNodeA, pNodeB } = o;
  4132. mb2 = res;
  4133.  
  4134. if (!mb2) {
  4135. // console.log('mb2', desc1, desc2, desc1.textContent, desc2.textContent)
  4136. }
  4137.  
  4138. if (res !== true) infoDuplicated = false;
  4139.  
  4140. }) : null
  4141.  
  4142. ]);
  4143.  
  4144. }
  4145. req = null;
  4146.  
  4147. console.log('[tyt] modern-info-duplicate', `(r, b1, b2) = (${infoDuplicated ? 1 : 0}, ${mb1 ? 1 : 0}, ${mb2 ? 1 : 0})`, `${infoDuplicated && mb1 && mb2 ? 'Success' : 'Failed'}`)
  4148.  
  4149. if (g_check_detail_A !== t) return;
  4150.  
  4151. //ytdFlexyElm.classList.toggle('tabview-info-duplicated', infoDuplicated)
  4152. checkDuplicateRes = infoDuplicated;
  4153.  
  4154. return 5; // other than 5, duplicated check = false
  4155.  
  4156. };
  4157.  
  4158.  
  4159. return checkDuplicatedInfoInner();
  4160.  
  4161.  
  4162. }
  4163.  
  4164.  
  4165. function setupChatFrameDOM(node) {
  4166. // this function calls 3 times per each new video page
  4167.  
  4168. // 'tyt-chat' is initialized in setupChatFrameDisplayState1()
  4169.  
  4170. if (!chatroomDetails) return;
  4171. let liveChatFrame = node || document.querySelector('ytd-live-chat-frame#chat')
  4172. if (liveChatFrame) {
  4173.  
  4174. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  4175.  
  4176. let ytdFlexyElm = es.ytdFlexy;
  4177. if (scriptEnable && ytdFlexyElm) {
  4178. if (mtoVisibility_Chatroom.bindElement(liveChatFrame)) {
  4179. mtoVisibility_Chatroom.observer.check(9)
  4180. }
  4181. }
  4182.  
  4183. liveChatFrame = null;
  4184. ytdFlexyElm = null;
  4185.  
  4186. setToggleBtnTxt(); // immediate update when page changed
  4187.  
  4188. if (node !== null) {
  4189. // button might not yet be rendered
  4190. requestAnimationFrame(setToggleBtnTxt); // bool = true must be front page
  4191. } else {
  4192.  
  4193. // this is due to page change
  4194. let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat:not([collapsed])')
  4195. if (incorrectChat) {
  4196. incorrectChat.setAttribute('collapsed', '')
  4197. }
  4198.  
  4199. }
  4200.  
  4201. }
  4202.  
  4203. }
  4204.  
  4205. function whenEngagemenetPanelVisible() {
  4206.  
  4207. const layoutStatus = wls.layoutStatus;
  4208. if ((layoutStatus & (LAYOUT_TWO_COLUMNS | LAYOUT_THEATER)) === LAYOUT_TWO_COLUMNS) {
  4209.  
  4210. if (layoutStatus & LAYOUT_TAB_EXPANDED) {
  4211. switchTabActivity(null);
  4212. return true;
  4213. } else if (layoutStatus & LAYOUT_CHATROOM_EXPANDED) {
  4214. ytBtnCollapseChat();
  4215. return true;
  4216. }
  4217.  
  4218. }
  4219.  
  4220. return false;
  4221.  
  4222. }
  4223.  
  4224.  
  4225. function removeFocusOnLeave(evt) {
  4226. let node = (evt || 0).target || 0
  4227. let activeElement = document.activeElement || 0
  4228. if (node.nodeType === 1 && activeElement.nodeType === 1) {
  4229. new Promise(() => {
  4230. if (node.contains(activeElement)) {
  4231. activeElement.blur();
  4232. }
  4233. })
  4234. }
  4235. }
  4236.  
  4237. async function setupVideo(node){
  4238. // this can be fired even in background without tabs rendered
  4239. const attrKey = 'gM7Cp'
  4240. let video = querySelectorFromAnchor.call(node, `#movie_player video[src]:not([${attrKey}])`);
  4241. if (video) {
  4242. video.setAttribute(attrKey, '')
  4243.  
  4244. video.addEventListener('timeupdate', (evt) => {
  4245. energizedByVideoTimeUpdate();
  4246. }, bubblePassive);
  4247.  
  4248. video.addEventListener('ended', (evt) => {
  4249. // scrollIntoView => auto start next video
  4250. // otherwise it cannot auto paly next
  4251. if (pageType === 'watch') {
  4252. let elm = evt.target;
  4253. Promise.resolve(elm).then((elm) => {
  4254. if (pageType === 'watch') {
  4255. let scrollElm = closestDOM.call(elm, '#player') || closestDOM.call(elm, '#ytd-player') || elm;
  4256. // background applicable
  4257. scrollElm.scrollIntoView(false);
  4258. scrollElm = null
  4259. }
  4260. elm = null
  4261. });
  4262. }
  4263.  
  4264. }, bubblePassive)
  4265.  
  4266. }
  4267. }
  4268.  
  4269. globalHook('yt-player-updated', (evt) => {
  4270.  
  4271. const node = ((evt || 0).target) || 0
  4272.  
  4273. if (node.nodeType !== 1) return;
  4274.  
  4275. const nodeName = node.nodeName.toUpperCase();
  4276.  
  4277. _console.log(evt.target.nodeName, 904, evt.type);
  4278.  
  4279. if (nodeName !== 'YTD-PLAYER') return
  4280.  
  4281. setupVideo(node)
  4282.  
  4283.  
  4284. let tabsDeferredSess = pageSession.session();
  4285. if (!scriptEnable && tabsDeferred.resolved) { }
  4286. else tabsDeferred.debounce(() => {
  4287.  
  4288. if (!tabsDeferredSess.isValid) return;
  4289. tabsDeferredSess = null;
  4290.  
  4291.  
  4292. if (!scriptEnable) return
  4293.  
  4294. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  4295. fetchCounts.new.f();
  4296. fetchCounts.fetched = true;
  4297.  
  4298. fetchCommentsFinished();
  4299. _console.log(9972, 'fetched = true')
  4300. }
  4301.  
  4302. _console.log(2178, 4)
  4303. pageCheck();
  4304.  
  4305. domInit_comments();
  4306. setupChatFrameDOM(null);
  4307.  
  4308.  
  4309. });
  4310.  
  4311.  
  4312. });
  4313.  
  4314. function ytMicroEventsInit() {
  4315.  
  4316. _console.log(902)
  4317.  
  4318. /** @type {Map<string, Function>} */
  4319. let handleDOMAppearFN = new Map();
  4320. function handleDOMAppear( /** @type {string} */ fn, /** @type { listener: (this: Document, ev: AnimationEvent ) => any } */ func) {
  4321. if (handleDOMAppearFN.size === 0) {
  4322. document.addEventListener('animationstart', (evt) => {
  4323. let func = handleDOMAppearFN.get(evt.animationName);
  4324. if (func) func(evt);
  4325. }, capturePassive)
  4326. } else {
  4327. if (handleDOMAppearFN.has(fn)) return;
  4328. }
  4329. handleDOMAppearFN.set(fn, func);
  4330. }
  4331.  
  4332. handleDOMAppear('videosDOMAppended', function (evt) {
  4333. videosDeferred.resolve();
  4334. })
  4335.  
  4336. handleDOMAppear('liveChatFrameDOMAppended', (evt) => {
  4337.  
  4338. let node = evt.target;
  4339. if (!node) return;
  4340.  
  4341. let tabsDeferredSess = pageSession.session();
  4342. if (!scriptEnable && tabsDeferred.resolved) { }
  4343. else tabsDeferred.debounce(() => {
  4344.  
  4345. // P.S. avoid immediately dom change
  4346. // time delay to avoid attribute set after dom appended.
  4347.  
  4348. if (!tabsDeferredSess.isValid) return;
  4349. tabsDeferredSess = null;
  4350.  
  4351. setupChatFrameDOM(node); // front page
  4352. node = null;
  4353.  
  4354. })
  4355.  
  4356. });
  4357.  
  4358. handleDOMAppear('pageLoaderAnimation', (evt) => {
  4359. pageRendered = 2;
  4360. renderDeferred.resolve();
  4361. console.log('[tyt] pageRendered')
  4362.  
  4363. scriptletDeferred.debounce(() => {
  4364. document.dispatchEvent(new CustomEvent('tabview-page-rendered'))
  4365. })
  4366.  
  4367. });
  4368.  
  4369.  
  4370. handleDOMAppear('chatFrameToggleBtnAppended1', (evt) => {
  4371.  
  4372. _console.log(5099, 'chatFrameToggleBtnAppended', evt)
  4373.  
  4374. Promise.resolve(0).then(() => { // avoid immediately dom change
  4375.  
  4376. let tabsDeferredSess = pageSession.session();
  4377. if (!scriptEnable && tabsDeferred.resolved) { }
  4378. else tabsDeferred.debounce(() => {
  4379.  
  4380. if (!tabsDeferredSess.isValid) return;
  4381. tabsDeferredSess = null;
  4382.  
  4383. mtf_liveChatBtnF(evt.target);
  4384.  
  4385. })
  4386.  
  4387. })
  4388.  
  4389. });
  4390.  
  4391.  
  4392. DEBUG_LOG && handleDOMAppear('chatFrameToggleBtnAppended2', (evt) => {
  4393.  
  4394. _console.log(5099, 'chatFrameToggleBtnAppended', evt)
  4395.  
  4396.  
  4397. });
  4398.  
  4399.  
  4400. handleDOMAppear('epDOMAppended', async (evt) => {
  4401. try {
  4402. let node = evt.target;
  4403.  
  4404. 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])')
  4405.  
  4406. if (eps && eps.length > 0) {
  4407.  
  4408. if (eps.length > 1) {
  4409. let p = 0;
  4410. for (const ep of eps) {
  4411. if (ep !== node) {
  4412. ytBtnCloseEngagementPanel(ep)
  4413. p++
  4414. }
  4415. }
  4416. if (p > 0) {
  4417. await Promise.resolve(0)
  4418. }
  4419. }
  4420.  
  4421. FP.mtf_attrEngagementPanel(9, node);
  4422.  
  4423. new Promise(() => {
  4424.  
  4425. mtoVisibility_EngagementPanel.bindElement(node, {
  4426. attributes: true,
  4427. attributeFilter: ['visibility'],
  4428. attributeOldValue: true
  4429. })
  4430.  
  4431. node.removeEventListener('mouseleave', removeFocusOnLeave, false)
  4432. node.addEventListener('mouseleave', removeFocusOnLeave, false)
  4433.  
  4434. })
  4435.  
  4436. }
  4437.  
  4438.  
  4439. } catch (e) { }
  4440.  
  4441. })
  4442.  
  4443. let _tabviewSiderAnimated = false;
  4444.  
  4445. handleDOMAppear('tabviewSiderAnimation', (evt) => {
  4446. if (!_tabviewSiderAnimated) {
  4447. _tabviewSiderAnimated = true;
  4448. dispatchCommentRowResize();
  4449. }
  4450. })
  4451.  
  4452. handleDOMAppear('tabviewSiderAnimationNone', (evt) => {
  4453. if (_tabviewSiderAnimated) {
  4454. _tabviewSiderAnimated = false;
  4455. dispatchCommentRowResize();
  4456. }
  4457. })
  4458.  
  4459. handleDOMAppear('SearchWhileWatchAutocomplete', (evt) => { // Youtube - Search While Watching Video
  4460. let elm = evt.target;
  4461. elm.addEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  4462. scriptletDeferred.debounce(() => {
  4463. elm.dispatchEvent(new CustomEvent('tabview-fix-autocomplete'));
  4464. elm = null;
  4465. })
  4466. })
  4467.  
  4468. const renderStamperFunc = {
  4469. 'YTD-PLAYLIST-PANEL-RENDERER': (node) => {
  4470. mtf_append_playlist(node); // the true playlist is appended to the #tab-list
  4471. checkPlaylistForInitialization();
  4472. },
  4473. 'YTD-COMMENTS-HEADER-RENDERER': (node) => {
  4474. comments_loader = comments_loader | 4;
  4475. getFinalComments();
  4476. }
  4477. }
  4478.  
  4479. globalHook('yt-rendererstamper-finished', (evt) => {
  4480.  
  4481. if (!scriptEnable && tabsDeferred.resolved) { return }
  4482. // might occur before initialization
  4483.  
  4484. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4485.  
  4486. let node = evt.target;
  4487. const nodeName = node.nodeName.toUpperCase();
  4488. const func = renderStamperFunc[nodeName];
  4489.  
  4490. if (typeof func !== 'function') {
  4491. return;
  4492. }
  4493.  
  4494.  
  4495. let tabsDeferredSess = pageSession.session();
  4496. if (!scriptEnable && tabsDeferred.resolved) { }
  4497. else tabsDeferred.debounce(() => {
  4498.  
  4499. if (!tabsDeferredSess.isValid) return;
  4500. tabsDeferredSess = null;
  4501.  
  4502. func(node);
  4503. node = null;
  4504.  
  4505. });
  4506.  
  4507.  
  4508. });
  4509.  
  4510.  
  4511. globalHook('yt-page-data-updated', (evt) => {
  4512.  
  4513. if (!scriptEnable && tabsDeferred.resolved) { return }
  4514. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4515.  
  4516. _console.log(evt.target.nodeName, 904, evt.type);
  4517.  
  4518. advanceFetch();
  4519.  
  4520. let tabsDeferredSess = pageSession.session();
  4521. if (!scriptEnable && tabsDeferred.resolved) { }
  4522. else tabsDeferred.debounce(() => {
  4523.  
  4524. if (!tabsDeferredSess.isValid) return;
  4525. tabsDeferredSess = null;
  4526.  
  4527. if (!scriptEnable) return;
  4528.  
  4529. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  4530. fetchCounts.new.f();
  4531. fetchCounts.fetched = true;
  4532.  
  4533. fetchCommentsFinished();
  4534. _console.log(9972, 'fetched = true')
  4535. }
  4536.  
  4537.  
  4538. // if the page is navigated by history back-and-forth, not all engagement panels can be catched in rendering event.
  4539.  
  4540.  
  4541.  
  4542. _console.log(2178, 3)
  4543. pageCheck();
  4544. setupChatFrameDOM(null);
  4545.  
  4546. let expander = document.querySelector('#meta-contents ytd-expander:not([tabview-info-expander])');
  4547. if (expander) {
  4548.  
  4549. // once per $$native-info-description$$ {#meta-contents ytd-expander} detection
  4550. // append the detailed meta contents to the tab-info
  4551.  
  4552. expander.setAttribute('tabview-info-expander', '');
  4553. let tabInfo = document.querySelector("#tab-info");
  4554. if (tabInfo) {
  4555. tabInfo.appendChild(expander);
  4556. }
  4557.  
  4558. }
  4559.  
  4560.  
  4561. if (REMOVE_DUPLICATE_INFO) {
  4562.  
  4563.  
  4564. checkDuplicateRes = null;
  4565. async function alCheckFn(ks) {
  4566.  
  4567. let alCheckCount = 4;
  4568. let alCheckInterval = 270;
  4569.  
  4570. checkDuplicateRes = null;
  4571. let descExpandState = null;
  4572. let descMetaExpander = document.querySelector('ytd-watch-metadata[modern-metapanel][clickable-description]');
  4573. let descToggleBtn = null;
  4574. let descMetaLines = null;
  4575. if (descMetaExpander) {
  4576.  
  4577. // ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata
  4578. descMetaLines = querySelectorFromAnchor.call(descMetaExpander, 'ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata')
  4579. if (descMetaLines) {
  4580.  
  4581. descToggleBtn = querySelectorFromAnchor.call(descMetaLines, 'tp-yt-paper-button#collapse[role="button"]:not([hidden]), tp-yt-paper-button#expand[role="button"]:not([hidden])');
  4582. if (descToggleBtn) {
  4583. if (descMetaExpander.hasAttribute('description-collapsed') && descToggleBtn.id === 'expand') {
  4584. descExpandState = false;
  4585. } else if (!descMetaExpander.hasAttribute('description-collapsed') && descToggleBtn.id === 'collapse') {
  4586. descExpandState = true;
  4587. }
  4588. }
  4589. }
  4590.  
  4591. }
  4592. if (descMetaExpander) {
  4593. descMetaExpander.classList.add('tyt-tmp-hide-metainfo');
  4594. }
  4595.  
  4596. try{
  4597.  
  4598. let req = {
  4599. descExpandState,
  4600. descMetaExpander,
  4601. descToggleBtn,
  4602. descMetaLines
  4603. }
  4604.  
  4605. do {
  4606.  
  4607. if (renderIdentifier !== ks) break;
  4608. if (alCheckCount === 0) break;
  4609. if (checkDuplicateRes === true) break;
  4610. checkDuplicateRes = null;
  4611.  
  4612. let res = await checkDuplicatedInfo(req); //async
  4613. if (res === 5) {
  4614.  
  4615. const ytdFlexyElm = es.ytdFlexy;
  4616. if (ytdFlexyElm) {
  4617. if (checkDuplicateRes === true || (checkDuplicateRes === false && alCheckCount === 1)) {
  4618. ytdFlexyElm.classList.toggle('tabview-info-duplicated', checkDuplicateRes)
  4619. ytdFlexyElm.classList.toggle('tabview-info-duplicated-checked', true)
  4620. checkDuplicatedInfo_then(res, checkDuplicateRes);
  4621. }
  4622. }
  4623.  
  4624. }
  4625. --alCheckCount;
  4626.  
  4627. if (checkDuplicateRes === true) break;
  4628.  
  4629. await new Promise(r => setTimeout(r, alCheckInterval));
  4630.  
  4631. } while (true)
  4632.  
  4633. await Promise.resolve(0)
  4634.  
  4635. descToggleBtn = descMetaLines ? querySelectorFromAnchor.call(descMetaLines, 'tp-yt-paper-button#collapse[role="button"]:not([hidden]), tp-yt-paper-button#expand[role="button"]:not([hidden])') : null;
  4636. if (descToggleBtn) {
  4637.  
  4638. let isCollapsed = descMetaExpander.hasAttribute('description-collapsed')
  4639. let id = descToggleBtn.id
  4640. let b1 = descExpandState === true && isCollapsed && id === 'expand';
  4641. let b2 = descExpandState === false && !isCollapsed && id === 'collapse';
  4642.  
  4643. if (b1 || b2) {
  4644. descToggleBtn.click();
  4645. }
  4646.  
  4647. }
  4648.  
  4649. req = null;
  4650.  
  4651.  
  4652. }catch(e){
  4653.  
  4654. console.warn(e)
  4655.  
  4656. }
  4657.  
  4658. if (descMetaExpander) {
  4659. descMetaExpander.classList.remove('tyt-tmp-hide-metainfo');
  4660.  
  4661. await Promise.resolve(0)
  4662.  
  4663. let detailsIntersectioner = querySelectorFromAnchor.call(descMetaExpander, '#info-container.style-scope.ytd-watch-metadata');
  4664. if (detailsIntersectioner) {
  4665. Promise.resolve(detailsIntersectioner).then(detailsIntersectioner => {
  4666. let dom = detailsIntersectioner;
  4667. if (dom) mtoObservationDetails.bindElement(dom);
  4668. })
  4669. }
  4670.  
  4671. }
  4672.  
  4673.  
  4674.  
  4675. }
  4676. let ks = renderIdentifier;
  4677. renderDeferred.debounce(() => {
  4678. if (ks !== renderIdentifier) return
  4679. alCheckFn(ks);
  4680.  
  4681. });
  4682.  
  4683. } else {
  4684.  
  4685. checkDuplicatedInfo_then(0, null);
  4686.  
  4687. }
  4688.  
  4689. let renderId = renderIdentifier
  4690. renderDeferred.debounce(() => {
  4691. if (renderId !== renderIdentifier) return
  4692. domInit_teaserInfo() // YouTube obsoleted feature?
  4693.  
  4694.  
  4695. let h1 = document.querySelector('#below h1.ytd-watch-metadata yt-formatted-string')
  4696. if (h1) {
  4697.  
  4698.  
  4699. let s = '';
  4700. if (formatDates && Object.keys(formatDates).length > 0) {
  4701.  
  4702. function getDurationInfo(bd1, bd2) {
  4703.  
  4704. let bdd = bd2 - bd1
  4705. let hrs = Math.floor(bdd / 3600000)
  4706. bdd = bdd - hrs * 3600000
  4707. let mins = Math.round(bdd / 60000)
  4708. let seconds = null
  4709. if (mins < 10 && hrs === 0) {
  4710. mins = Math.floor(bdd / 60000)
  4711. bdd = bdd - mins * 60000
  4712. seconds = Math.round(bdd / 1000)
  4713. if (seconds === 0) seconds = null
  4714. }
  4715.  
  4716. return { hrs, mins, seconds }
  4717.  
  4718. }
  4719.  
  4720. const formatDateResult = formatDateResultEN
  4721.  
  4722. if (formatDates.broadcastBeginAt && formatDates.isLiveNow === false) {
  4723.  
  4724. let bd1 = new KDate(formatDates.broadcastBeginAt)
  4725. let bd2 = formatDates.broadcastEndAt ? new KDate(formatDates.broadcastEndAt) : null
  4726.  
  4727. let isSameDay = 0
  4728. if (bd2 && bd1.toLocaleDateString() === bd2.toLocaleDateString()) {
  4729. isSameDay = 1
  4730.  
  4731. } else if (bd2 && +bd2 > +bd1 && bd2 - bd1 < 86400000) {
  4732.  
  4733. if (bd1.getHours() >= 6 && bd2.getHours() < 6) {
  4734. isSameDay = 2
  4735. }
  4736.  
  4737. }
  4738.  
  4739. let durationInfo = getDurationInfo(bd1, bd2)
  4740. if (isSameDay > 0) {
  4741.  
  4742. bd2.dayBack = (isSameDay === 2)
  4743.  
  4744. s = formatDateResult(0x200, { bd1, bd2, isSameDay, durationInfo, formatDates })
  4745.  
  4746. } else if (bd2 && isSameDay === 0) {
  4747.  
  4748. s = formatDateResult(0x210, { bd1, bd2, isSameDay, durationInfo, formatDates })
  4749.  
  4750. }
  4751.  
  4752.  
  4753. } else if (formatDates.broadcastBeginAt && formatDates.isLiveNow === true) {
  4754.  
  4755. let bd1 = new KDate(formatDates.broadcastBeginAt)
  4756.  
  4757. s = formatDateResult(0x300, { bd1, formatDates })
  4758.  
  4759. } else {
  4760. if (formatDates.uploadDate) {
  4761.  
  4762. if (formatDates.publishDate && formatDates.publishDate !== formatDates.uploadDate) {
  4763.  
  4764. s = formatDateResult(0x600, { formatDates })
  4765. } else {
  4766. s = formatDateResult(0x610, { formatDates })
  4767.  
  4768. }
  4769. } else if (!formatDates.uploadDate && formatDates.publishDate) {
  4770.  
  4771. s = formatDateResult(0x700, { formatDates })
  4772.  
  4773.  
  4774. }
  4775. }
  4776.  
  4777.  
  4778. }
  4779.  
  4780. if (s) {
  4781. h1.setAttribute('data-title-details', s)
  4782. } else {
  4783. h1.removeAttribute('data-title-details')
  4784. }
  4785.  
  4786. }
  4787.  
  4788. })
  4789.  
  4790.  
  4791. checkPlaylistForInitialization();
  4792.  
  4793. mtf_fix_details().then(() => {
  4794. // setKeywords();
  4795. setToggleInfo();
  4796. renderDeferred.debounce(() => {
  4797. if (renderId !== renderIdentifier) return
  4798. setTimeout(() => {
  4799. //dispatchWindowResize(); //try to omit
  4800. dispatchWindowResize(); //add once for safe
  4801. manualResizeT();
  4802. }, 420)
  4803. })
  4804.  
  4805.  
  4806. let secondary = document.querySelector('#columns.ytd-watch-flexy #secondary.ytd-watch-flexy');
  4807.  
  4808. let columns = secondary ? closestDOM.call(secondary, '#columns.ytd-watch-flexy') : null;
  4809.  
  4810. setupHoverSlider(secondary, columns)
  4811.  
  4812. let tabInfo = document.querySelector('#tab-info');
  4813. addTabExpander(tabInfo);
  4814.  
  4815. let tabComments = document.querySelector('#tab-comments');
  4816. addTabExpander(tabComments);
  4817.  
  4818.  
  4819. });
  4820.  
  4821.  
  4822. });
  4823.  
  4824. });
  4825.  
  4826.  
  4827. globalHook('yt-watch-comments-ready', (evt) => {
  4828.  
  4829. if (!scriptEnable && tabsDeferred.resolved) { return }
  4830. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4831.  
  4832. let nodeName = evt.target.nodeName.toUpperCase()
  4833. advanceFetch();
  4834.  
  4835. comments_loader = comments_loader | 2;
  4836.  
  4837. let tabsDeferredSess = pageSession.session();
  4838. if (!scriptEnable && tabsDeferred.resolved) { }
  4839. else tabsDeferred.debounce(() => {
  4840.  
  4841. if (!tabsDeferredSess.isValid) return;
  4842. tabsDeferredSess = null;
  4843.  
  4844. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  4845. fetchCounts.new.f();
  4846. fetchCounts.fetched = true;
  4847.  
  4848. fetchCommentsFinished();
  4849. _console.log(9972, 'fetched = true')
  4850. }
  4851.  
  4852. if (nodeName === 'YTD-WATCH-FLEXY') {
  4853. domInit_comments();
  4854. if (mtf_forceCheckLiveVideo_disable !== 2) {
  4855. _console.log(3713, Q.comments_section_loaded, fetchCounts.fetched, 'fetch comments')
  4856. if (document.querySelector(`ytd-comments#comments`).hasAttribute('hidden')) {
  4857. // unavailable apart from live chat
  4858. _disableComments();
  4859. _console.log(3713, 3, 'comments hidden')
  4860. } else if (Q.comments_section_loaded === 0) {
  4861. getFinalComments();
  4862. }
  4863. }
  4864. }
  4865. });
  4866.  
  4867. })
  4868.  
  4869.  
  4870. window.addEventListener("message", (evt) => {
  4871. if (!scriptEnable && tabsDeferred.resolved) { return }
  4872. if (evt.origin === location.origin && evt.data.tabview) {
  4873. let data = evt.data.tabview;
  4874. if (data.eventType === "yt-page-type-changed") {
  4875. let detail = data.eventDetail
  4876. let { newPageType, oldPageType } = detail;
  4877. if (newPageType && oldPageType) {
  4878. let bool = false;
  4879. if (newPageType == 'ytd-watch-flexy') {
  4880. bool = true;
  4881. pageType = 'watch';
  4882. } else if (newPageType == 'ytd-browse') {
  4883. pageType = 'browse';
  4884. }
  4885. document.documentElement.classList.toggle('tabview-normal-player', bool)
  4886. }
  4887. }
  4888. }
  4889. }, bubblePassive);
  4890.  
  4891.  
  4892. globalHook('data-changed', (evt) => {
  4893.  
  4894. if (!scriptEnable && tabsDeferred.resolved) { return }
  4895.  
  4896. let nodeName = (((evt || 0).target || 0).nodeName || '').toUpperCase()
  4897.  
  4898. if (nodeName !== 'YTD-THUMBNAIL-OVERLAY-TOGGLE-BUTTON-RENDERER') return;
  4899.  
  4900. document.dispatchEvent(new CustomEvent("tabview-fix-popup-refit"));
  4901.  
  4902. })
  4903.  
  4904.  
  4905. DEBUG_LOG && globalHook('yt-rendererstamper-finished', (evt) => {
  4906.  
  4907. if (!scriptEnable && tabsDeferred.resolved) { return }
  4908. // might occur before initialization
  4909.  
  4910. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4911.  
  4912. const nodeName = evt.target.nodeName.toUpperCase();
  4913.  
  4914. // const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER','YTD-MENU-RENDERER']
  4915. if (S_GENERAL_RENDERERS.includes(nodeName)) {
  4916. return;
  4917. }
  4918.  
  4919. _console.log(evt.target.nodeName, 904, evt.type, evt.detail);
  4920.  
  4921. });
  4922.  
  4923. DEBUG_LOG && globalHook('data-changed', (evt) => {
  4924.  
  4925. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4926.  
  4927. let nodeName = evt.target.nodeName.toUpperCase()
  4928. _console.log(nodeName, evt.type)
  4929.  
  4930. if (nodeName === 'YTD-ITEM-SECTION-RENDERER' || nodeName === 'YTD-COMMENTS') {
  4931.  
  4932. _console.log(344)
  4933.  
  4934. }
  4935.  
  4936. })
  4937.  
  4938. DEBUG_LOG && globalHook('yt-navigate', (evt) => {
  4939.  
  4940. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4941. _console.log(evt.target.nodeName, evt.type)
  4942.  
  4943. })
  4944.  
  4945. DEBUG_LOG && globalHook('ytd-playlist-lockup-now-playing-active', (evt) => {
  4946.  
  4947. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4948. _console.log(evt.target.nodeName, evt.type)
  4949.  
  4950.  
  4951. })
  4952.  
  4953. DEBUG_LOG && globalHook('yt-service-request-completed', (evt) => {
  4954.  
  4955. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4956. _console.log(evt.target.nodeName, evt.type)
  4957.  
  4958.  
  4959. })
  4960.  
  4961. DEBUG_LOG && globalHook('yt-commerce-action-done', (evt) => {
  4962.  
  4963. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4964. _console.log(evt.target.nodeName, evt.type)
  4965.  
  4966.  
  4967. })
  4968.  
  4969. DEBUG_LOG && globalHook('yt-execute-service-endpoint', (evt) => {
  4970.  
  4971. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4972. _console.log(evt.target.nodeName, evt.type)
  4973.  
  4974.  
  4975. })
  4976.  
  4977.  
  4978. DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  4979.  
  4980. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4981. _console.log(evt.target.nodeName, evt.type)
  4982.  
  4983.  
  4984. })
  4985.  
  4986.  
  4987. DEBUG_LOG && globalHook('yt-visibility-refresh', (evt) => {
  4988.  
  4989. if (!evt || !evt.target /*|| evt.target.nodeType !== 1*/) return;
  4990. _console.log(evt.target.nodeName || '', evt.type)
  4991.  
  4992. const ytdFlexyElm = es.ytdFlexy;
  4993. _console.log(2784, evt.type, (ytdFlexyElm ? ytdFlexyElm.hasAttribute('hidden') : null), evt.detail)
  4994.  
  4995. _console.log(evt.detail)
  4996.  
  4997.  
  4998. })
  4999.  
  5000. DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  5001.  
  5002. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5003. _console.log(evt.target.nodeName, evt.type)
  5004.  
  5005.  
  5006. })
  5007.  
  5008. DEBUG_LOG && globalHook('app-reset-layout', (evt) => {
  5009.  
  5010. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5011. _console.log(evt.target.nodeName, evt.type)
  5012.  
  5013.  
  5014. })
  5015. DEBUG_LOG && globalHook('yt-guide-close', (evt) => {
  5016.  
  5017. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5018. _console.log(evt.target.nodeName, evt.type)
  5019.  
  5020.  
  5021. })
  5022. DEBUG_LOG && globalHook('yt-page-data-will-change', (evt) => {
  5023.  
  5024. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5025. _console.log(evt.target.nodeName, evt.type)
  5026.  
  5027.  
  5028. })
  5029.  
  5030. DEBUG_LOG && globalHook('yt-retrieve-location', (evt) => {
  5031.  
  5032. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5033. _console.log(evt.target.nodeName, evt.type)
  5034.  
  5035.  
  5036. })
  5037.  
  5038. DEBUG_LOG && globalHook('yt-refit', (evt) => {
  5039.  
  5040. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5041. _console.log(evt.target.nodeName, evt.type)
  5042.  
  5043. })
  5044.  
  5045. DEBUG_LOG && globalHook('addon-attached', (evt) => {
  5046.  
  5047. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5048. _console.log(evt.target.nodeName, evt.type)
  5049.  
  5050. })
  5051.  
  5052. DEBUG_LOG && globalHook('yt-live-chat-context-menu-opened', (evt) => {
  5053.  
  5054. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5055. _console.log(evt.target.nodeName, evt.type)
  5056.  
  5057. })
  5058.  
  5059. DEBUG_LOG && globalHook('yt-live-chat-context-menu-closed', (evt) => {
  5060.  
  5061. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5062. _console.log(evt.target.nodeName, evt.type)
  5063.  
  5064. })
  5065.  
  5066. DEBUG_LOG && globalHook('yt-commentbox-resize', (evt) => {
  5067.  
  5068. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5069. _console.log(evt.target.nodeName, evt.type)
  5070. })
  5071.  
  5072. DEBUG_LOG && globalHook('yt-rich-grid-layout-refreshed', (evt) => {
  5073.  
  5074. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5075. _console.log(2327, evt.target.nodeName, evt.type)
  5076. })
  5077.  
  5078. DEBUG_LOG && globalHook('animationend', (evt) => {
  5079.  
  5080. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5081. _console.log(evt.target.nodeName, evt.type)
  5082.  
  5083.  
  5084. })
  5085.  
  5086. DEBUG_LOG && globalHook('yt-dismissible-item-dismissed', (evt) => {
  5087.  
  5088. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5089. _console.log(evt.target.nodeName, evt.type)
  5090.  
  5091.  
  5092. })
  5093.  
  5094. DEBUG_LOG && globalHook('yt-dismissible-item-undismissed', function (evt) {
  5095.  
  5096. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5097. _console.log(evt.target.nodeName, evt.type)
  5098.  
  5099.  
  5100. })
  5101.  
  5102.  
  5103. DEBUG_LOG && globalHook('yt-load-next-continuation', function (evt) {
  5104.  
  5105. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5106. _console.log(evt.target.nodeName, evt.type)
  5107.  
  5108.  
  5109. })
  5110.  
  5111.  
  5112. DEBUG_LOG && globalHook('yt-load-reload-continuation', function (evt) {
  5113.  
  5114. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5115. _console.log(evt.target.nodeName, evt.type)
  5116.  
  5117.  
  5118. })
  5119.  
  5120. DEBUG_LOG && globalHook('yt-toggle-button', function (evt) {
  5121.  
  5122. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5123. _console.log(evt.target.nodeName, evt.type)
  5124.  
  5125.  
  5126. })
  5127.  
  5128.  
  5129. }
  5130.  
  5131.  
  5132.  
  5133.  
  5134. function addIframeStyle(cDoc) {
  5135. if (cDoc.querySelector('#tyt-chatroom-css')) return false;
  5136. addStyle((iframeCSS() || ''), cDoc.documentElement).id = 'tyt-chatroom-css'
  5137. return true;
  5138. }
  5139.  
  5140. function chatFrameContentDocument() {
  5141. // non-null if iframe exist && contentDocument && readyState = complete
  5142. /** @type {HTMLIFrameElement | null} */
  5143. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  5144. if (!iframe) return null; //iframe must be there
  5145. /** @type {Document | null} */
  5146. let cDoc = null;
  5147. try {
  5148. cDoc = iframe.contentDocument;
  5149. } catch (e) { }
  5150. if (!cDoc) return null;
  5151. if (cDoc.readyState != 'complete') return null; //we must wait for its completion
  5152.  
  5153. return cDoc;
  5154.  
  5155. }
  5156.  
  5157. function chatFrameElement(/** @type {string} */ cssSelector) {
  5158. let cDoc = chatFrameContentDocument();
  5159. if (!cDoc) return null;
  5160. /** @type {HTMLElement | null} */
  5161. let elm = null;
  5162. try {
  5163. elm = cDoc.querySelector(cssSelector)
  5164. } catch (e) {
  5165. console.log('iframe error', e)
  5166. }
  5167. return elm;
  5168. }
  5169.  
  5170.  
  5171. function forceDisplayChatReplay() {
  5172. let items = chatFrameElement('yt-live-chat-item-list-renderer #items');
  5173. if (items && items.childElementCount !== 0) return;
  5174.  
  5175. let videoElm = document.querySelector('ytd-player#ytd-player video');
  5176.  
  5177. let ct = videoElm.currentTime;
  5178. if (ct >= 0 && !videoElm.ended && videoElm.readyState > videoElm.HAVE_CURRENT_DATA) {
  5179. let chat = document.querySelector('ytd-live-chat-frame#chat');
  5180. if (chat) {
  5181. nativeFunc(chat, "postToContentWindow", [{ "yt-player-video-progress": ct }])
  5182. }
  5183. }
  5184.  
  5185. }
  5186.  
  5187. function checkIframeDblClick() {
  5188. setTimeout(() => {
  5189.  
  5190. let Itemslist = chatFrameElement('#contents.yt-live-chat-renderer');
  5191. if (Itemslist && typeof Itemslist.ondblclick === 'function') iframePointEventsAllow = true;
  5192.  
  5193. if (iframePointEventsAllow) {
  5194. chatFrameElement('body').classList.add('tabview-allow-pointer-events');
  5195. }
  5196.  
  5197. }, 300)
  5198. }
  5199.  
  5200. function addPopupButton(chat) {
  5201. let showHideBtn = chat.querySelector('div#show-hide-button')
  5202. if (showHideBtn) {
  5203.  
  5204. let btn;
  5205. btn = document.querySelector('tyt-iframe-popup-btn')
  5206. if (btn) btn.remove();
  5207.  
  5208. btn = document.createElement('tyt-iframe-popup-btn')
  5209. showHideBtn.appendChild(btn)
  5210. // console.log(334,2)
  5211. btn.dispatchEvent(new CustomEvent('tyt-iframe-popup-btn-setup'))
  5212. }
  5213.  
  5214. }
  5215.  
  5216. function iFrameContentReady(cDoc) {
  5217.  
  5218. // console.log(702, 1)
  5219. if (!cDoc) return;
  5220.  
  5221. // console.log(702, 2)
  5222. if (addIframeStyle(cDoc) === false) return;
  5223.  
  5224. // console.log(702, 3)
  5225. let frc = 0;
  5226. let cid = 0;
  5227.  
  5228. let fullReady = () => {
  5229.  
  5230. // console.log(702, 4)
  5231. if (!cDoc.documentElement.hasAttribute('style') && ++frc < 900) return;
  5232. clearInterval(cid);
  5233.  
  5234. // console.log(702, 5)
  5235. if (!scriptEnable || !isChatExpand()) return;
  5236.  
  5237. // console.log(702, 6)
  5238. let iframe = document.querySelector('body ytd-watch-flexy ytd-live-chat-frame iframe#chatframe');
  5239.  
  5240. // console.log(702, 7)
  5241. if (!iframe) return; //prevent iframe is detached from the page
  5242.  
  5243. // console.log(702, 8)
  5244. if (cDoc.querySelector('yt-live-chat-renderer #continuations')) {
  5245.  
  5246. // console.log(702, 9)
  5247. let chatFrame = document.querySelector('ytd-live-chat-frame#chat');
  5248. if (chatFrame) {
  5249. chatFrame.setAttribute('tyt-iframe-loaded', '')
  5250. // console.log(711, chatFrame)
  5251.  
  5252.  
  5253. // console.log(702, 10)
  5254. forceDisplayChatReplay();
  5255. checkIframeDblClick(); // user request for compatible with https://greasyfork.org/en/scripts/452335
  5256. iframe.dispatchEvent(new CustomEvent("tabview-chatroom-ready"))
  5257. // console.log(334,1)
  5258. addPopupButton(chatFrame)
  5259.  
  5260. }
  5261. }
  5262.  
  5263.  
  5264. }
  5265. cid = setInterval(fullReady, 10)
  5266. fullReady();
  5267.  
  5268.  
  5269. }
  5270.  
  5271. let iframeLoadHookA_id = 0
  5272.  
  5273. const iframeLoadHookA = function (evt) {
  5274.  
  5275.  
  5276. let isIframe = (((evt || 0).target || 0).nodeName === 'IFRAME');
  5277.  
  5278. if (isIframe && evt.target.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')) {
  5279. } else {
  5280. return;
  5281. }
  5282.  
  5283. let iframe = evt.target;
  5284. let tid = ++iframeLoadHookA_id;
  5285.  
  5286. // console.log(701, 2)
  5287. new Promise(resolve => {
  5288. if (tid !== iframeLoadHookA_id) return
  5289.  
  5290. // console.log(701, 3)
  5291. let k = 270
  5292. let cid = setInterval(() => {
  5293.  
  5294. if (tid !== iframeLoadHookA_id) return
  5295.  
  5296. if (!cid) return;
  5297.  
  5298. if (k-- < 1) {
  5299. clearInterval(cid);
  5300. cid = 0;
  5301. return resolve(false);
  5302. }
  5303.  
  5304. let cDoc = iframe.contentDocument;
  5305. if (!cDoc) return null;
  5306. if (cDoc.readyState != 'complete') return;
  5307. if (!cDoc.querySelector('body')) {
  5308. clearInterval(cid);
  5309. cid = 0;
  5310. return resolve(false);
  5311. }
  5312.  
  5313. if (!cDoc.querySelector('yt-live-chat-app')) return;
  5314.  
  5315. clearInterval(cid);
  5316. cid = 0;
  5317.  
  5318. if (!document.contains(iframe)) return resolve(false);
  5319.  
  5320. resolve([cDoc, iframe]);
  5321.  
  5322. cDoc = null
  5323.  
  5324. iframe = null
  5325.  
  5326.  
  5327. }, 17)
  5328.  
  5329.  
  5330. }).then((res) => {
  5331.  
  5332.  
  5333. // console.log(701, 4, res)
  5334. if (tid !== iframeLoadHookA_id) return
  5335.  
  5336. // console.log(701, 5)
  5337. if (!res) return;
  5338.  
  5339. // console.log(701, 6)
  5340.  
  5341. const [cDoc, iframe] = res
  5342.  
  5343. iFrameContentReady(cDoc)
  5344. iframe.dispatchEvent(new CustomEvent('tabview-chatframe-loaded'))
  5345.  
  5346.  
  5347. })
  5348.  
  5349.  
  5350.  
  5351. }
  5352.  
  5353. let videosDeferred = new Deferred();
  5354.  
  5355. let _navigateLoadDT = 0;
  5356.  
  5357. async function onNavigationEndAsync(isPageFirstLoaded) {
  5358.  
  5359. if (pageType !== 'watch') return
  5360.  
  5361.  
  5362. let tdt = Date.now();
  5363. _navigateLoadDT = tdt;
  5364.  
  5365. // avoid blocking the page when youtube is initializing the page
  5366. const promiseDelay = new Promise(requestAnimationFrame)
  5367. const promiseVideoRendered = videosDeferred.d()
  5368. await Promise.all([promiseVideoRendered, promiseDelay])
  5369.  
  5370. if (_navigateLoadDT !== tdt) return;
  5371. if (ytEventSequence !== 3) return;
  5372.  
  5373. const ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  5374.  
  5375. if (!ytdFlexyElm) {
  5376. ytdFlexy = null
  5377. return;
  5378. }
  5379.  
  5380. scriptEnable = true;
  5381.  
  5382. ytdFlexy = mWeakRef(ytdFlexyElm)
  5383.  
  5384. const related = querySelectorFromAnchor.call(ytdFlexyElm, "#related.ytd-watch-flexy");
  5385. if (!related) return;
  5386.  
  5387. // isPageFirstLoaded && console.time("Tabview Youtube Render")
  5388.  
  5389. if (!document.querySelector("#right-tabs")) {
  5390. getLang();
  5391. let docTmp = document.createElement('template');
  5392. docTmp.innerHTML = getTabsHTML();
  5393. let newElm = docTmp.content.firstElementChild;
  5394. if (newElm !== null) {
  5395. insertBeforeTo(newElm, related);
  5396. querySelectorFromAnchor.call(newElm, '#material-tabs').addEventListener('mousemove', (evt)=>{
  5397. evt.preventDefault();
  5398. evt.stopPropagation();
  5399. evt.stopImmediatePropagation();
  5400. }, true);
  5401. console.log('[tyt] #right-tabs inserted')
  5402. }
  5403. docTmp.textContent = '';
  5404. docTmp = null;
  5405. }
  5406.  
  5407. if (!ytdFlexyElm.hasAttribute('tyt-tab')) ytdFlexyElm.setAttribute('tyt-tab', '')
  5408.  
  5409. // append the next videos
  5410. // it exists as "related" is already here
  5411. fixTabs();
  5412.  
  5413. 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])')) {
  5414. switchTabActivity(null);
  5415. } else {
  5416. setToActiveTab(); // just switch to the default tab
  5417. }
  5418. prepareTabBtn();
  5419.  
  5420. mtoFlexyAttr.clear(true)
  5421. mtf_checkFlexy()
  5422.  
  5423. tabsDeferred.resolve();
  5424. FP.mtf_attrEngagementPanel(); // check whether no visible panels
  5425.  
  5426. // isPageFirstLoaded && console.timeEnd("Tabview Youtube Render")
  5427.  
  5428. isPageFirstLoaded && document.documentElement.removeAttribute('tyt-lock')
  5429.  
  5430. }
  5431.  
  5432.  
  5433. function fetchCommentsFinished() {
  5434. const ytdFlexyElm = es.ytdFlexy;
  5435. if (!scriptEnable || !ytdFlexyElm) return;
  5436. if (mtf_forceCheckLiveVideo_disable === 2) return;
  5437. ytdFlexyElm.setAttribute('tyt-comments', 'L');
  5438. _console.log(2909, 1)
  5439. }
  5440.  
  5441. function setCommentSection( /** @type {number} */ value) {
  5442.  
  5443. Q.comments_section_loaded = value;
  5444. if (value === 0 && fetchCounts) {
  5445. fetchCounts.fetched = false; // unknown bug
  5446. }
  5447.  
  5448. }
  5449.  
  5450.  
  5451. function emptyCommentSection() {
  5452. let tab_btn = document.querySelector('.tab-btn[tyt-tab-content="#tab-comments"]')
  5453. if (tab_btn) {
  5454. let span = querySelectorFromAnchor.call(tab_btn, 'span#tyt-cm-count');
  5455. tab_btn.removeAttribute('loaded-comment')
  5456. if (span) {
  5457. span.textContent = '';
  5458. }
  5459. }
  5460. setCommentSection(0);
  5461. _console.log(7233, 'comments_section_loaded = 0')
  5462. }
  5463.  
  5464.  
  5465. function _disableComments() {
  5466.  
  5467.  
  5468. _console.log(2909, 1)
  5469. if (!scriptEnable) return;
  5470. let cssElm = es.ytdFlexy;
  5471. if (!cssElm) return;
  5472.  
  5473. _console.log(2909, 2)
  5474.  
  5475.  
  5476. let comments = document.querySelector('ytd-comments#comments')
  5477. if (mtf_forceCheckLiveVideo_disable === 2) {
  5478. // earlier than DOM change
  5479. } else {
  5480. if (comments && !comments.hasAttribute('hidden')) return; // visible comments content)
  5481. }
  5482.  
  5483. _console.log(2909, 4)
  5484. if (Q.comments_section_loaded === 2) return; //already disabled
  5485.  
  5486. setCommentSection(2);
  5487.  
  5488. _console.log(2909, 5)
  5489.  
  5490. let tabBtn = document.querySelector('.tab-btn[tyt-tab-content="#tab-comments"]');
  5491. if (tabBtn) {
  5492. let span = querySelectorFromAnchor.call(tabBtn, 'span#tyt-cm-count');
  5493. tabBtn.removeAttribute('loaded-comment')
  5494. if (!tabBtn.classList.contains('tab-btn-hidden')) {
  5495. //console.log('hide', comments, comments && comments.hasAttribute('hidden'))
  5496. hideTabBtn(tabBtn)
  5497. }
  5498. if (span) {
  5499. span.textContent = '';
  5500. }
  5501. }
  5502.  
  5503. cssElm.setAttribute('tyt-comments', 'D');
  5504.  
  5505. _console.log(2909, 10)
  5506.  
  5507.  
  5508. }
  5509.  
  5510.  
  5511. function setKeywords() {
  5512.  
  5513. return;
  5514.  
  5515. if (typeof String.prototype.replacei !== 'function') {
  5516. // reference: https://stackoverflow.com/questions/7313395/case-insensitive-replace-all
  5517. String.replacei = String.prototype.replacei = function (rep, rby) {
  5518. var pos = this.toLowerCase().indexOf(rep.toLowerCase());
  5519. return pos == -1 ? this : this.substring(0, pos) + rby(this.substring(pos, pos + rep.length)) + this.substring(pos + rep.length);
  5520. };
  5521. }
  5522.  
  5523. let data = pageFetchedData;
  5524. console.log(data)
  5525.  
  5526.  
  5527. let keywords = ((((data || 0).pageData || 0).playerResponse || 0).videoDetails || 0).keywords;
  5528. console.log(keywords)
  5529.  
  5530. if (keywords && keywords.length > 0) {
  5531.  
  5532.  
  5533. let title = '';
  5534.  
  5535. try {
  5536. title = ((((data || 0).pageData || 0).response || 0).contents || 0).twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.title.runs[0].text;
  5537. if (typeof title !== 'string') title = '';
  5538. } catch (e) { }
  5539.  
  5540. let strText = title;
  5541.  
  5542. let tabsDeferredSess = pageSession.session();
  5543. if (!scriptEnable && tabsDeferred.resolved) { }
  5544. else tabsDeferred.debounce(() => {
  5545.  
  5546. if (!tabsDeferredSess.isValid) return;
  5547. tabsDeferredSess = null;
  5548.  
  5549.  
  5550. let res = [];
  5551. for (const keyword of keywords) {
  5552. if (strText.toUpperCase().includes(keyword.toUpperCase())) {
  5553. res.push(keyword);
  5554. }
  5555. }
  5556. if (res.length > 0) {
  5557. console.log('tabview video keywords', res)
  5558. window.postMessage({
  5559. tabview: {
  5560. eventType: 0x3700,
  5561. eventDetail: {
  5562. keywords: res
  5563. }
  5564. }
  5565. }, location.origin);
  5566. }
  5567.  
  5568.  
  5569.  
  5570. let strElms = document.querySelectorAll('#title.ytd-watch-metadata yt-formatted-string.style-scope.ytd-watch-metadata');
  5571. //console.log(keywords,strElms)
  5572. for (const strElm of strElms) {
  5573. if (strElm.id == 'super-title' || strElm.id == 'original-info') {
  5574.  
  5575. } else {
  5576. if (strElm.querySelector('*')) {
  5577.  
  5578. } else {
  5579. /** @type{string} */
  5580. let strText = strElm.textContent;
  5581. if (strText) {
  5582.  
  5583. let res = [];
  5584. for (const keyword of keywords) {
  5585. if (strText.toUpperCase().includes(keyword.toUpperCase())) {
  5586. res.push(keyword);
  5587. }
  5588. }
  5589. if (res.length > 0) {
  5590. console.log('tabview video keywords', res)
  5591.  
  5592. if (res.length > 1) res.sort((a, b) => { return b.length - a.length });
  5593. let usedKeywords = {};
  5594. for (const s of res) {
  5595. strText = strText.replacei(s, ((s) => {
  5596. usedKeywords[s] = true;
  5597. return `\n${s}\n`
  5598. }))
  5599. }
  5600. strText = strText.replace(`\n\n+`, '\n')
  5601. let retElms = strText.split('\n').map(w => {
  5602. let elm = document.createElement('tabview-txt')
  5603. if (usedKeywords[w]) elm.classList.add('tabview-title-keyword')
  5604. elm.textContent = w;
  5605. return elm
  5606. })
  5607.  
  5608. let p = querySelectorFromAnchor.call(strElm.parentNode, '.tabview-txt');
  5609.  
  5610. if (!p) {
  5611. p = strElm.cloneNode(false)
  5612. p.classList.add('tabview-txt')
  5613. strElm.after(p);
  5614. } else {
  5615. strElm.after(p);
  5616. }
  5617.  
  5618. requestAnimationFrame(() => {
  5619.  
  5620. p.textContent = 'x';
  5621. p.firstChild.replaceWith(...retElms);
  5622. p.removeAttribute('is-empty')
  5623. strElm.setAttribute('is-empty', '')
  5624.  
  5625. })
  5626.  
  5627.  
  5628. }
  5629. break;
  5630. }
  5631. }
  5632. }
  5633. }
  5634.  
  5635. })
  5636.  
  5637. }
  5638.  
  5639.  
  5640. }
  5641.  
  5642. function setToggleInfo() {
  5643.  
  5644. scriptletDeferred.d().then(() => {
  5645.  
  5646. let elem = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #info-container.ytd-watch-metadata:first-child:not([tyt-info-toggler])')
  5647. if (elem) {
  5648.  
  5649. elem.setAttribute('tyt-info-toggler', '')
  5650. elem.dispatchEvent(new CustomEvent('tyt-info-toggler'))
  5651.  
  5652. }
  5653.  
  5654. });
  5655. }
  5656.  
  5657.  
  5658. function flexyAttr_toggleFlag(mFlag, b, flag) {
  5659. return b ? (mFlag | flag) : (mFlag & ~flag);
  5660. }
  5661.  
  5662. function flexAttr_toLayoutStatus(nls, attributeName) {
  5663.  
  5664. let attrElm, b, v;
  5665. switch (attributeName) {
  5666. case 'theater':
  5667. b = isTheater();
  5668. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_THEATER);
  5669. break;
  5670. case 'tyt-chat':
  5671. attrElm = es.ytdFlexy;
  5672. v = attrElm.getAttribute('tyt-chat');
  5673.  
  5674. if (v !== null && v.charAt(0) === '-') {
  5675. nls = flexyAttr_toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED);
  5676. } else {
  5677. nls = flexyAttr_toggleFlag(nls, v !== null, LAYOUT_CHATROOM);
  5678. nls = flexyAttr_toggleFlag(nls, false, LAYOUT_CHATROOM_COLLAPSED);
  5679. }
  5680.  
  5681. break;
  5682. case 'is-two-columns_':
  5683. b = isWideScreenWithTwoColumns();
  5684. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TWO_COLUMNS);
  5685. break;
  5686.  
  5687. case 'tyt-tab':
  5688. attrElm = es.ytdFlexy;
  5689. b = isNonEmptyString(attrElm.getAttribute('tyt-tab'));
  5690. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  5691. break;
  5692.  
  5693. case 'fullscreen':
  5694. attrElm = es.ytdFlexy;
  5695. b = attrElm.hasAttribute('fullscreen');
  5696. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  5697. break;
  5698.  
  5699. case 'tyt-ep-visible':
  5700. attrElm = es.ytdFlexy;
  5701. v = attrElm.getAttribute('tyt-ep-visible');
  5702. b = (+v > 0)
  5703. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPANDED);
  5704. break;
  5705.  
  5706. case 'tyt-donation-shelf':
  5707. attrElm = es.ytdFlexy;
  5708. b = attrElm.hasAttribute('tyt-donation-shelf');
  5709. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_DONATION_SHELF_EXPANDED);
  5710. break;
  5711.  
  5712. }
  5713.  
  5714. return nls;
  5715.  
  5716.  
  5717. }
  5718.  
  5719.  
  5720.  
  5721. function ito_details(entries, observer) {
  5722. if (!detailsTriggerReset) return;
  5723. if (!entries || entries.length !== 1) return; // unlikely
  5724. let entry = entries[0];
  5725. //console.log(entries)
  5726. if (entry.isIntersecting === true) {
  5727.  
  5728. if (fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS | LAYOUT_FULLSCREEN, 0) === false) return;
  5729.  
  5730. let dom = entry.target;
  5731. if (!dom) return; //unlikely
  5732.  
  5733. let bool = false;
  5734. let descClickable = null;
  5735. if (fT(wls.layoutStatus, 0, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_TAB_EXPANDED) === false) {
  5736. descClickable = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata')
  5737. if (descClickable) {
  5738. detailsTriggerReset = false;
  5739. bool = true;
  5740. }
  5741. }
  5742.  
  5743. async function runAsync(dom, bool) {
  5744.  
  5745. if (bool) {
  5746. let descClickable = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata')
  5747. if (descClickable) {
  5748. descClickable.click();
  5749. }
  5750. }
  5751.  
  5752. await new Promise(r => setTimeout(r, 20));
  5753.  
  5754. let pInner, nw = null;
  5755. try {
  5756. let x = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata');
  5757. let h2 = x.offsetHeight;
  5758. pInner = closestDOM.call(x, '#primary-inner')
  5759. let h1 = pInner.offsetHeight;
  5760. x.setAttribute('userscript-scrollbar-render', '')
  5761. if (h1 > h2 && h2 > 0 && h1 > 0) nw = h1 - h2
  5762. } catch (e) { }
  5763. if(pInner){
  5764. pInner.style.setProperty('--tyt-desc-top-h', `${nw ? nw : 0}px`)
  5765. }
  5766. }
  5767.  
  5768. runAsync(dom, bool);
  5769.  
  5770.  
  5771. }
  5772.  
  5773. }
  5774.  
  5775. async function delayFlexyCheck(dcall) {
  5776.  
  5777. if (!scriptEnable) return;
  5778. await new Promise(r => timeline.setTimeout(r, 240));
  5779. if (!scriptEnable) return;
  5780.  
  5781. let b = false
  5782. if (dcall & 1) { // !cssElm.hasAttribute('tyt-chat')
  5783. //delayed call => check with the "no active focus" condition with chatroom status
  5784. b = b || fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_THEATER | LAYOUT_FULLSCREEN)
  5785. // b = !isAnyActiveTab() && !isChatExpand() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()
  5786. }
  5787. if (dcall & 2) { // tyt-ep-visible removed
  5788. //delayed call => check with the "no active focus" condition with engagement panel status
  5789. b = b || fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_THEATER | LAYOUT_FULLSCREEN | LAYOUT_CHATROOM_EXPANDED)
  5790. // b = !isAnyActiveTab() && !isEngagementPanelExpanded() && !isDonationShelfExpanded() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen() && !isChatExpand()
  5791. }
  5792. if (dcall & 4) { // without tyt-donation-shelf
  5793. b = b || fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_THEATER | LAYOUT_FULLSCREEN | LAYOUT_CHATROOM_EXPANDED)
  5794. // b = !isAnyActiveTab() && !isEngagementPanelExpanded() && !isDonationShelfExpanded() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen() && !isChatExpand()
  5795. }
  5796. if (b) {
  5797. setToActiveTab();
  5798. }
  5799. }
  5800.  
  5801. const mtf_attrFlexy = (mutations, observer) => {
  5802.  
  5803. //attr mutation checker - $$ytdFlexyElm$$ {ytd-watch-flexy} \single
  5804. //::attr
  5805. // ~ 'tyt-chat', 'theater', 'is-two-columns_',
  5806. // ~ 'tyt-tab', 'fullscreen', 'tyt-ep-visible',
  5807. // ~ 'hidden', 'is-extra-wide-video_'
  5808.  
  5809. //console.log(15330, scriptEnable, es.ytdFlexy, mutations)
  5810.  
  5811. if (!scriptEnable) return;
  5812.  
  5813. const cssElm = es.ytdFlexy;
  5814. if (!cssElm) return;
  5815.  
  5816. if (!mutations) return;
  5817.  
  5818. const old_layoutStatus = wls.layoutStatus
  5819. if (old_layoutStatus === 0) return;
  5820. let new_layoutStatus = old_layoutStatus;
  5821.  
  5822. let checkedChat = false;
  5823. let mss = 0;
  5824. let dcall = 0;
  5825.  
  5826. for (const mutation of mutations) {
  5827. new_layoutStatus = flexAttr_toLayoutStatus(new_layoutStatus, mutation.attributeName);
  5828. _console.log(8221, 18, mutation.attributeName)
  5829. if (mutation.attributeName === 'tyt-chat') {
  5830.  
  5831. if (!checkedChat) {
  5832. checkedChat = true; // avoid double call
  5833.  
  5834. if ((cssElm.getAttribute('tyt-chat') || '').indexOf('chat$live') >= 0) {
  5835. // assigned new attribute - "chat$live" => disable comments section
  5836.  
  5837. //console.log(3712,2)
  5838. _disableComments();
  5839. }
  5840.  
  5841. if (!cssElm.hasAttribute('tyt-chat')) {
  5842. // might or might not collapsed before
  5843. dcall |= 1;
  5844. }
  5845. }
  5846.  
  5847. } else if (mutation.attributeName === 'tyt-ep-visible') {
  5848. // assume any other active component such as tab content and chatroom
  5849.  
  5850. if (+(cssElm.getAttribute('tyt-ep-visible') || 0) === 0 && +mutation.oldValue > 0) {
  5851. dcall |= 2;
  5852. }
  5853.  
  5854. if (mss === 0) mss = 1;
  5855. else mss = -1;
  5856.  
  5857. } else if (mutation.attributeName === 'tyt-donation-shelf') {
  5858. // assume any other active component such as tab content and chatroom
  5859.  
  5860. if (!(cssElm.hasAttribute('tyt-donation-shelf'))) {
  5861. dcall |= 4;
  5862. } else {
  5863. lstTab.lastPanel = '#donation-shelf'
  5864. switchTabActivity(null);
  5865. }
  5866. if (mss === 0) mss = 2;
  5867. else mss = -1;
  5868.  
  5869. } else if (mutation.attributeName === 'is-extra-wide-video_') {
  5870. setTimeout(() => {
  5871. updateFloatingSlider(); //required for hover slider // eg video after ads
  5872. }, 1);
  5873. }
  5874. }
  5875.  
  5876. new_layoutStatus = fixLayoutStatus(new_layoutStatus);
  5877.  
  5878. if (new_layoutStatus !== old_layoutStatus) {
  5879.  
  5880. if (fT(new_layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_THEATER | LAYOUT_CHATROOM_EXPANDED)) {
  5881. if (mss === 1) {
  5882. if (fT(new_layoutStatus, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED, 0)) {
  5883. closeDonationShelf();
  5884. }
  5885. } else if (mss === 2) {
  5886. if (fT(new_layoutStatus, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED, 0)) {
  5887. ytBtnCloseEngagementPanels();
  5888. }
  5889. }
  5890. }
  5891. wls.layoutStatus = new_layoutStatus
  5892.  
  5893. }
  5894.  
  5895. if (dcall > 0) delayFlexyCheck(dcall);
  5896.  
  5897. }
  5898.  
  5899.  
  5900. function setupChatFrameDisplayState1(chatBlockR, initialDisplayState) {
  5901.  
  5902.  
  5903. const ytdFlexyElm = es.ytdFlexy;
  5904. if (!scriptEnable || !ytdFlexyElm) return;
  5905.  
  5906. let chatTypeChanged = mtf_chatBlockQ !== chatBlockR;
  5907.  
  5908. let attr_chatblock = chatBlockR === 1 ? 'chat$live' : chatBlockR === 3 ? 'chat$playback' : false;
  5909. let attr_chatcollapsed = false;
  5910.  
  5911.  
  5912. if (attr_chatblock) {
  5913. let chatFrame = document.querySelector('ytd-live-chat-frame#chat')
  5914. if (chatFrame) {
  5915. attr_chatcollapsed = chatFrame.hasAttribute('collapsed');
  5916. if (!attr_chatcollapsed) {
  5917.  
  5918. //nativeFunc(p,'setupPlayerProgressRelay')
  5919. //if(!p.isFrameReady)
  5920. //nativeFunc(p, "urlChanged")
  5921. //console.log(12399,1)
  5922. chatFrame.dispatchEvent(new CustomEvent("tabview-chatroom-newpage")); //possible empty iframe is shown
  5923.  
  5924. }
  5925. } else {
  5926. attr_chatcollapsed = initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED' ? true : false;
  5927. }
  5928. }
  5929.  
  5930. if (chatTypeChanged) {
  5931. mtf_chatBlockQ = chatBlockR
  5932.  
  5933. _console.log(932, 2, attr_chatblock, attr_chatcollapsed)
  5934.  
  5935. //LIVE_CHAT_DISPLAY_STATE_COLLAPSED
  5936. //LIVE_CHAT_DISPLAY_STATE_EXPANDED
  5937. let v = attr_chatblock
  5938. if (typeof attr_chatblock == 'string') {
  5939.  
  5940. if (attr_chatcollapsed === true) v = '-' + attr_chatblock
  5941. if (attr_chatcollapsed === false) v = '+' + attr_chatblock;
  5942. }
  5943. wAttr(ytdFlexyElm, 'tyt-chat', v)
  5944.  
  5945. _console.log(932, 3, ytdFlexyElm.hasAttribute('tyt-chat'))
  5946.  
  5947.  
  5948. }
  5949.  
  5950. return { attr_chatblock, attr_chatcollapsed, chatTypeChanged }
  5951. }
  5952.  
  5953. function setupChatFrameDisplayState2() {
  5954.  
  5955. const ytdFlexyElm = es.ytdFlexy;
  5956. if (!scriptEnable || !ytdFlexyElm) return null;
  5957.  
  5958. // this is a backup solution only; should be abandoned
  5959.  
  5960. let attr_chatblock = null
  5961. let attr_chatcollapsed = null;
  5962.  
  5963. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  5964. let elmCont = null;
  5965. if (elmChat) {
  5966. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  5967.  
  5968.  
  5969. let s = 0;
  5970. if (elmCont) {
  5971. //not found if it is collapsed.
  5972. s |= querySelectorFromAnchor.call(elmCont, 'yt-timed-continuation') ? 1 : 0;
  5973. s |= querySelectorFromAnchor.call(elmCont, 'yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0;
  5974. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  5975. if (s == 1) {
  5976. attr_chatblock = 'chat$live';
  5977. } else if (s == 2) attr_chatblock = 'chat$playback';
  5978.  
  5979. if (s == 1) {
  5980. let cmCountElm = document.querySelector("span#tyt-cm-count")
  5981. if (cmCountElm) cmCountElm.textContent = '';
  5982. }
  5983.  
  5984. } else if (!ytdFlexyElm.hasAttribute('tyt-chat')) {
  5985. // live chat frame but type not known
  5986.  
  5987. attr_chatblock = '';
  5988.  
  5989. }
  5990. //keep unknown as original
  5991.  
  5992.  
  5993. let isCollapsed = !!elmChat.hasAttribute('collapsed');
  5994. attr_chatcollapsed = isCollapsed;
  5995.  
  5996. } else {
  5997. attr_chatblock = false;
  5998. attr_chatcollapsed = false;
  5999.  
  6000. }
  6001.  
  6002. return { attr_chatblock, attr_chatcollapsed }
  6003.  
  6004. }
  6005.  
  6006.  
  6007. const mtf_checkFlexy = () => {
  6008. // once per $$native-ytd-watch-flexy$$ {ytd-watch-flexy} detection
  6009.  
  6010. const ytdFlexyElm = es.ytdFlexy;
  6011. if (!scriptEnable || !ytdFlexyElm) return true;
  6012.  
  6013.  
  6014. wls.layoutStatus = 0;
  6015.  
  6016. let isFlexyHidden = (ytdFlexyElm.hasAttribute('hidden'));
  6017.  
  6018. if (!isFlexyHidden) {
  6019. let rChatExist = setupChatFrameDisplayState2();
  6020. if (rChatExist) {
  6021. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  6022. if (attr_chatblock === null) {
  6023. //remove attribute if it is unknown
  6024. attr_chatblock = false;
  6025. attr_chatcollapsed = false;
  6026. }
  6027. let v = attr_chatblock;
  6028. if (typeof v === 'string') {
  6029. if (attr_chatcollapsed === true) v = '-' + v;
  6030. if (attr_chatcollapsed === false) v = '+' + v;
  6031. }
  6032. wAttr(ytdFlexyElm, 'tyt-chat', v)
  6033.  
  6034. }
  6035. }
  6036.  
  6037. let rTabSelection = [...querySelectorAllFromAnchor.call(ytdFlexyElm, '.tab-btn[tyt-tab-content]')]
  6038. .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') }));
  6039.  
  6040. if (rTabSelection.length === 0) {
  6041. wAttr(ytdFlexyElm, 'tyt-tab', false);
  6042. } else {
  6043. rTabSelection = rTabSelection.filter(entry => entry.hidden !== true); // all available tabs
  6044. if (rTabSelection.length === 0) wAttr(ytdFlexyElm, 'tyt-tab', '');
  6045. }
  6046. rTabSelection = null;
  6047.  
  6048. let rEP = engagement_panels_();
  6049. if (rEP && rEP.list.length > 0) {
  6050. wAttr(ytdFlexyElm, 'tyt-ep-visible', `${rEP.value}`);
  6051. } else {
  6052. wAttr(ytdFlexyElm, 'tyt-ep-visible', false);
  6053. }
  6054.  
  6055. let ls = LAYOUT_VAILD;
  6056. ls = flexAttr_toLayoutStatus(ls, 'theater')
  6057. ls = flexAttr_toLayoutStatus(ls, 'tyt-chat')
  6058. ls = flexAttr_toLayoutStatus(ls, 'is-two-columns_')
  6059. ls = flexAttr_toLayoutStatus(ls, 'tyt-tab')
  6060. ls = flexAttr_toLayoutStatus(ls, 'fullscreen')
  6061. ls = flexAttr_toLayoutStatus(ls, 'tyt-ep-visible')
  6062. ls = flexAttr_toLayoutStatus(ls, 'tyt-donation-shelf')
  6063.  
  6064. fixLayoutStatus(ls)
  6065.  
  6066. wls.layoutStatus = ls
  6067.  
  6068. mtoFlexyAttr.bindElement(ytdFlexyElm, {
  6069. attributes: true,
  6070. attributeFilter: ['tyt-chat', 'theater', 'is-two-columns_', 'tyt-tab', 'fullscreen', 'tyt-ep-visible', 'tyt-donation-shelf', 'hidden', 'is-extra-wide-video_'],
  6071. attributeOldValue: true
  6072. })
  6073.  
  6074. let columns = document.querySelector('ytd-page-manager#page-manager #columns.ytd-watch-flexy')
  6075. if (columns) {
  6076. wAttr(columns, 'userscript-scrollbar-render', true);
  6077. }
  6078.  
  6079. return false;
  6080. }
  6081.  
  6082.  
  6083. function switchTabActivity(activeLink) {
  6084.  
  6085. //console.log(4545, activeLink)
  6086. if (!scriptEnable) return;
  6087.  
  6088. const ytdFlexyElm = es.ytdFlexy;
  6089.  
  6090. if (!ytdFlexyElm) return;
  6091.  
  6092. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  6093.  
  6094. //if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null;
  6095.  
  6096.  
  6097. function runAtEnd() {
  6098.  
  6099. if (activeLink) {
  6100. lstTab.lastTab = activeLink.getAttribute('tyt-tab-content')
  6101. lstTab.lastPanel = null;
  6102.  
  6103. if (!document.querySelector(`${lstTab.lastTab}.tab-content-cld tabview-view-tab-expander`)) {
  6104.  
  6105. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  6106. if (secondary) {
  6107. secondary.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  6108. //console.log(1995)
  6109. }
  6110.  
  6111.  
  6112. }
  6113. }
  6114.  
  6115. ytdFlexyElm.setAttribute('tyt-tab', activeLink ? lstTab.lastTab : '')
  6116.  
  6117. }
  6118.  
  6119. const links = document.querySelectorAll('#material-tabs a[tyt-tab-content]');
  6120.  
  6121. //console.log(701, activeLink)
  6122.  
  6123. for (const link of links) {
  6124. let content = document.querySelector(link.getAttribute('tyt-tab-content'));
  6125. if (link && content) {
  6126. if (link !== activeLink) {
  6127. link.classList.remove("active");
  6128. content.classList.add("tab-content-hidden");
  6129. } else {
  6130. link.classList.add("active");
  6131. content.classList.remove("tab-content-hidden");
  6132.  
  6133. }
  6134. }
  6135. }
  6136.  
  6137. runAtEnd();
  6138.  
  6139.  
  6140. }
  6141.  
  6142.  
  6143. function getStore() {
  6144. let s = localStorage[STORE_key];
  6145. function resetStore() {
  6146. let ret = {
  6147. version: 1,
  6148. };
  6149. localStorage[STORE_key] = JSON.stringify(ret);
  6150. return ret;
  6151. }
  6152. if (!s) return resetStore();
  6153. let obj = null;
  6154. try {
  6155. obj = JSON.parse(s);
  6156. } catch (e) { }
  6157. return obj && obj.version === STORE_VERSION ? obj : resetStore();
  6158. }
  6159.  
  6160. function setStore(obj) {
  6161. if (!obj || typeof obj !== 'object') return false;
  6162. if (obj.version !== STORE_VERSION) return false;
  6163. localStorage[STORE_key] = JSON.stringify(obj);
  6164. return true;
  6165. }
  6166.  
  6167.  
  6168.  
  6169. async function handlerMaterialTabClickInner(tabBtn) {
  6170.  
  6171. await Promise.resolve(0);
  6172.  
  6173. const layoutStatusMutexUnlock = await new Promise(resolve => {
  6174. layoutStatusMutex.lockWith(unlock => {
  6175. resolve(unlock)
  6176. })
  6177. });
  6178.  
  6179. // locked
  6180. let unlock = layoutStatusMutexUnlock; // function of unlock inside layoutStatusMutex
  6181.  
  6182. //console.log(8515)
  6183. switchTabActivity_lastTab = tabBtn.getAttribute('tyt-tab-content');
  6184.  
  6185. let isActiveAndVisible = tabBtn.classList.contains('tab-btn') && tabBtn.classList.contains('active') && !tabBtn.classList.contains('tab-btn-hidden')
  6186.  
  6187. _console.log(8221, 15, isActiveAndVisible)
  6188.  
  6189. if (isFullScreen()) {
  6190.  
  6191.  
  6192. const fullScreenTabScrollIntoView = () => {
  6193. let scrollElement = document.querySelector('ytd-app[scrolling]')
  6194. if (!scrollElement) return;
  6195. // single column view; click button; scroll to tab content area 100%
  6196. let rightTabs = document.querySelector('#right-tabs');
  6197. let pTop = rightTabs.getBoundingClientRect().top - scrollElement.getBoundingClientRect().top
  6198. if (rightTabs && pTop > 0 && tabBtn.classList.contains('active')) {
  6199. rightTabs.scrollIntoView(false);
  6200. }
  6201. }
  6202.  
  6203. _console.log(8221, 16, 1)
  6204.  
  6205. if (isActiveAndVisible) {
  6206. timeline.setTimeout(unlock, 80);
  6207. switchTabActivity(null);
  6208. } else {
  6209.  
  6210. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  6211. ytBtnCollapseChat();
  6212. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  6213. ytBtnCloseEngagementPanels();
  6214. } else if (isDonationShelfExpanded() && isWideScreenWithTwoColumns()) {
  6215. closeDonationShelf();
  6216. }
  6217.  
  6218.  
  6219. timeline.setTimeout(fullScreenTabScrollIntoView, 60)
  6220.  
  6221. timeline.setTimeout(unlock, 80);
  6222. switchTabActivity(tabBtn)
  6223. }
  6224.  
  6225. } else if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) {
  6226.  
  6227. _console.log(8221, 16, 2)
  6228. //optional
  6229. timeline.setTimeout(unlock, 80);
  6230. switchTabActivity(null);
  6231. ytBtnSetTheater();
  6232. } else if (isActiveAndVisible) {
  6233.  
  6234. _console.log(8221, 16, 3)
  6235. timeline.setTimeout(unlock, 80);
  6236. switchTabActivity(null);
  6237. } else {
  6238.  
  6239. _console.log(8221, 16, 4)
  6240.  
  6241. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  6242. ytBtnCollapseChat();
  6243. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  6244. ytBtnCloseEngagementPanels();
  6245. } else if (isDonationShelfExpanded() && isWideScreenWithTwoColumns()) {
  6246. closeDonationShelf();
  6247. } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) {
  6248. ytBtnCancelTheater(); //ytd-watch-flexy attr [theater]
  6249. }
  6250.  
  6251. timeline.setTimeout(() => {
  6252. // single column view; click button; scroll to tab content area 100%
  6253. let rightTabs = document.querySelector('#right-tabs');
  6254. if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && tabBtn.classList.contains('active')) {
  6255. let tabButtonBar = document.querySelector('#material-tabs');
  6256. let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0;
  6257. window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight);
  6258. }
  6259. }, 60)
  6260. // _console.log(8519)
  6261.  
  6262. timeline.setTimeout(unlock, 80)
  6263. switchTabActivity(tabBtn)
  6264.  
  6265. }
  6266.  
  6267.  
  6268. }
  6269.  
  6270. function handlerMaterialTabClick(/** @type {MouseEvent} */ evt) {
  6271.  
  6272. //console.log(8510)
  6273. const ytdFlexyElm = es.ytdFlexy;
  6274. if (!scriptEnable || !ytdFlexyElm) return null;
  6275.  
  6276. let tabBtn = this;
  6277.  
  6278. if (!tabBtn.hasAttribute('tyt-tab-content')) return;
  6279.  
  6280. /** @type {HTMLElement | null} */
  6281. let dom = evt.target;
  6282. if (!dom) return;
  6283.  
  6284. if (dom.classList.contains('font-size-btn')) return;
  6285.  
  6286.  
  6287. evt.preventDefault();
  6288.  
  6289. handlerMaterialTabClickInner(tabBtn);
  6290.  
  6291.  
  6292. }
  6293.  
  6294. function prepareTabBtn() {
  6295.  
  6296. const materialTab = document.querySelector("#material-tabs")
  6297. if (!materialTab) return;
  6298.  
  6299. let noActiveTab = !!document.querySelector('ytd-watch-flexy[tyt-chat^="+"]')
  6300.  
  6301. const activeLink = querySelectorFromAnchor.call(materialTab, 'a[tyt-tab-content].active:not(.tab-btn-hidden)')
  6302. if (activeLink) switchTabActivity(noActiveTab ? null : activeLink)
  6303.  
  6304. if (!tabsUiScript_setclick) {
  6305. tabsUiScript_setclick = true;
  6306.  
  6307. let fontSizeBtnClick = null;
  6308.  
  6309. materialTab.addEventListener('click', function (evt) {
  6310.  
  6311. if (!evt.isTrusted) return; // prevent call from background
  6312. let dom = evt.target;
  6313. if ((dom || 0).nodeType !== 1) return;
  6314.  
  6315. const domInteraction = dom.getAttribute('tyt-di');
  6316. if (domInteraction === 'q9Kjc') {
  6317. handlerMaterialTabClick.call(dom, evt)
  6318. } else if (domInteraction === '8rdLQ') {
  6319. fontSizeBtnClick.call(dom, evt)
  6320. }
  6321.  
  6322.  
  6323. }, true)
  6324.  
  6325. function updateCSS_fontsize() {
  6326.  
  6327. let store = getStore();
  6328.  
  6329. const ytdFlexyElm = es.ytdFlexy;
  6330. if (ytdFlexyElm) {
  6331. if (store['font-size-#tab-info']) ytdFlexyElm.style.setProperty('--ut2257-info', store['font-size-#tab-info'])
  6332. if (store['font-size-#tab-comments']) ytdFlexyElm.style.setProperty('--ut2257-comments', store['font-size-#tab-comments'])
  6333. if (store['font-size-#tab-videos']) ytdFlexyElm.style.setProperty('--ut2257-videos', store['font-size-#tab-videos'])
  6334. if (store['font-size-#tab-list']) ytdFlexyElm.style.setProperty('--ut2257-list', store['font-size-#tab-list'])
  6335. }
  6336.  
  6337. }
  6338.  
  6339. fontSizeBtnClick = function (evt) {
  6340.  
  6341. evt.preventDefault();
  6342. evt.stopPropagation();
  6343. evt.stopImmediatePropagation();
  6344.  
  6345. /** @type {HTMLElement | null} */
  6346. let dom = evt.target;
  6347. if (!dom) return;
  6348.  
  6349.  
  6350. let value = dom.classList.contains('font-size-plus') ? 1 : dom.classList.contains('font-size-minus') ? -1 : 0;
  6351.  
  6352. let active_tab_content = closestDOM.call(dom, '[tyt-tab-content]').getAttribute('tyt-tab-content');
  6353.  
  6354. let store = getStore();
  6355. let settingKey = `font-size-${active_tab_content}`
  6356. if (!store[settingKey]) store[settingKey] = 1.0;
  6357. if (value < 0) store[settingKey] -= 0.05;
  6358. else if (value > 0) store[settingKey] += 0.05;
  6359. if (store[settingKey] < 0.1) store[settingKey] = 0.1;
  6360. else if (store[settingKey] > 10) store[settingKey] = 10.0;
  6361. setStore(store);
  6362.  
  6363.  
  6364. updateCSS_fontsize();
  6365.  
  6366. }
  6367. //$(materialTab).on("click", ".font-size-btn", );
  6368.  
  6369. updateCSS_fontsize();
  6370.  
  6371.  
  6372. }
  6373.  
  6374. }
  6375.  
  6376. function setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight) {
  6377.  
  6378. //if(isStickyHeaderEnabled===bool) return; // no update
  6379.  
  6380. if (bool === true) {
  6381. const { width, height } = getWidthHeight();
  6382. targetElm.style.setProperty("--tyt-stickybar-w", width + 'px')
  6383. targetElm.style.setProperty("--tyt-stickybar-h", height + 'px')
  6384. const res = getLeftRight();
  6385. if (res) {
  6386.  
  6387. targetElm.style.setProperty("--tyt-stickybar-l", (res.left) + 'px')
  6388. targetElm.style.setProperty("--tyt-stickybar-r", (res.right) + 'px')
  6389.  
  6390. }
  6391. wAttr(targetElm, 'tyt-stickybar', true);
  6392. isStickyHeaderEnabled = true;
  6393.  
  6394. } else if (bool === false) {
  6395.  
  6396. wAttr(targetElm, 'tyt-stickybar', false);
  6397. isStickyHeaderEnabled = false;
  6398. }
  6399.  
  6400.  
  6401. }
  6402.  
  6403. const singleColumnScrolling = async function () {
  6404. //makeHeaderFloat
  6405. // required for 1) init 2) layout change 3) resizing
  6406.  
  6407. if (!scriptEnable || pageType !== 'watch') return;
  6408.  
  6409.  
  6410. let isTwoCol = (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS;
  6411. if (isTwoCol) {
  6412.  
  6413. if (isStickyHeaderEnabled) {
  6414.  
  6415. let targetElm = document.querySelector("#right-tabs");
  6416. setStickyHeader(targetElm, false, null, null);
  6417. }
  6418. return;
  6419. }
  6420.  
  6421. let pageY = scrollY;
  6422.  
  6423.  
  6424. let tdt = Date.now();
  6425. singleColumnScrolling_dt = tdt;
  6426.  
  6427.  
  6428. _console.log(7891, 'scrolling')
  6429.  
  6430. function getXYStatus(res) {
  6431.  
  6432. const [navHeight, elmY] = res;
  6433.  
  6434. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  6435.  
  6436. let xyStatus = 0
  6437. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  6438. // 1
  6439. xyStatus = 1
  6440. }
  6441.  
  6442. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  6443.  
  6444. //2
  6445. xyStatus = 2
  6446.  
  6447. }
  6448.  
  6449. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  6450. // 3
  6451.  
  6452. xyStatus = 3
  6453.  
  6454.  
  6455. }
  6456.  
  6457. return xyStatus;
  6458. }
  6459.  
  6460. let [targetElm, header, navElm] = await Promise.all([
  6461. new Promise(f => f(document.querySelector("#right-tabs"))),
  6462.  
  6463. new Promise(f => f(document.querySelector("#right-tabs header"))),
  6464.  
  6465. new Promise(f => f(document.querySelector('#masthead-container, #masthead'))),
  6466.  
  6467. ]);
  6468.  
  6469. function emptyForGC() {
  6470. targetElm = null;
  6471. header = null;
  6472. navElm = null;
  6473. }
  6474.  
  6475. if (!targetElm || !header) {
  6476. return emptyForGC();
  6477. }
  6478. if (singleColumnScrolling_dt !== tdt) return emptyForGC();
  6479.  
  6480. let res2 = await Promise.all([
  6481. new Promise(f => f(navElm ? navElm.offsetHeight : 0)),
  6482. new Promise(f => f(targetElm.offsetTop))
  6483. ])
  6484.  
  6485. if (res2 === null) return emptyForGC();
  6486.  
  6487. if (singleColumnScrolling_dt !== tdt) return emptyForGC();
  6488.  
  6489.  
  6490. const xyStatus = getXYStatus(res2);
  6491.  
  6492.  
  6493. function getLeftRight() {
  6494.  
  6495. let thp = document.querySelector('tabview-view-pos-thead');
  6496. if (thp) {
  6497.  
  6498. let rect = thp.getBoundingClientRect()
  6499. if (rect) {
  6500. return {
  6501. left: rect.left,
  6502. right: document.documentElement.clientWidth - rect.right
  6503. };
  6504. }
  6505. }
  6506. return null;
  6507. }
  6508.  
  6509. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  6510.  
  6511. function getWidthHeight() {
  6512. return { width: targetElm.offsetWidth, height: header.offsetHeight };
  6513. }
  6514.  
  6515. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  6516.  
  6517.  
  6518. emptyForGC();
  6519.  
  6520. };
  6521.  
  6522.  
  6523. const singleColumnScrolling2 = async function (xyStatus, width, xRect) {
  6524. //makeHeaderFloat
  6525.  
  6526. if (!scriptEnable || pageType !== 'watch') return;
  6527.  
  6528.  
  6529. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  6530. return;
  6531. }
  6532.  
  6533. let [targetElm, header] = await Promise.all([
  6534. new Promise(f => f(document.querySelector("#right-tabs"))),
  6535. new Promise(f => f(document.querySelector("#right-tabs header")))
  6536. ]);
  6537.  
  6538. function emptyForGC() {
  6539. targetElm = null;
  6540. header = null;
  6541. }
  6542.  
  6543.  
  6544. if (!targetElm || !header) {
  6545. return emptyForGC();
  6546. }
  6547.  
  6548. function getLeftRight() {
  6549. return xRect;
  6550. }
  6551.  
  6552. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  6553.  
  6554. function getWidthHeight() {
  6555. return { width: (width || targetElm.offsetWidth), height: header.offsetHeight };
  6556. }
  6557.  
  6558. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  6559.  
  6560. emptyForGC();
  6561.  
  6562. };
  6563.  
  6564.  
  6565. function resetBuggyLayoutForNewVideoPage() {
  6566.  
  6567. const ytdFlexyElm = es.ytdFlexy;
  6568. if (!ytdFlexyElm) return;
  6569.  
  6570. //(flexy is visible and watch video page)
  6571.  
  6572. scriptEnable = true;
  6573.  
  6574. _console.log(27056)
  6575.  
  6576. let new_layoutStatus = wls.layoutStatus
  6577.  
  6578. new_layoutStatus & (LAYOUT_CHATROOM_COLLAPSED | LAYOUT_CHATROOM)
  6579.  
  6580. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  6581. const new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  6582.  
  6583. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  6584. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  6585. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  6586. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  6587. const new_isExpandedEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  6588. const new_isExpandedDonationShelf = new_layoutStatus & LAYOUT_DONATION_SHELF_EXPANDED;
  6589. const nothingShown = !new_isTabExpanded && !new_isExpandedChat && !new_isExpandedEPanel && !new_isExpandedDonationShelf
  6590.  
  6591. if (ytdFlexyElm.getAttribute('tyt-tab') === '' && new_isTwoColumns && !new_isTheater && nothingShown && !new_isFullScreen) {
  6592. // e.g. engage panel removed after miniview and change video
  6593. setToActiveTab();
  6594. } else if (new_isExpandedEPanel && querySelectorAllFromAnchor.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) {
  6595. wls.layoutStatus = new_layoutStatus & (~LAYOUT_ENGAGEMENT_PANEL_EXPANDED)
  6596. } else if (new_isExpandedDonationShelf && querySelectorAllFromAnchor.call(ytdFlexyElm, 'ytd-donation-shelf-renderer.ytd-watch-flexy:not([hidden])').length === 0) {
  6597. wls.layoutStatus = new_layoutStatus & (~LAYOUT_DONATION_SHELF_EXPANDED)
  6598. }
  6599.  
  6600. }
  6601.  
  6602.  
  6603. function extractInfoFromLiveChatRenderer(liveChatRenderer) {
  6604.  
  6605. let lcr = liveChatRenderer
  6606.  
  6607. let data_shb = ((lcr || 0).showHideButton || 0).toggleButtonRenderer
  6608.  
  6609. let default_display_state = null, txt_collapse = null, txt_expand = null;
  6610.  
  6611.  
  6612. if (data_shb && data_shb.defaultText && data_shb.toggledText && data_shb.defaultText.runs && data_shb.toggledText.runs) {
  6613.  
  6614. if (data_shb.defaultText.runs.length === 1 && data_shb.toggledText.runs.length === 1) {
  6615.  
  6616. if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_EXPANDED") {
  6617.  
  6618. default_display_state = lcr.initialDisplayState
  6619.  
  6620. txt_collapse = (data_shb.defaultText.runs[0] || 0).text // COLLAPSE the area
  6621.  
  6622. txt_expand = (data_shb.toggledText.runs[0] || 0).text // expand the area
  6623.  
  6624. } else if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_COLLAPSED") {
  6625. default_display_state = lcr.initialDisplayState
  6626.  
  6627. txt_expand = (data_shb.defaultText.runs[0] || 0).text // expand the area
  6628.  
  6629. txt_collapse = (data_shb.toggledText.runs[0] || 0).text // COLLAPSE the area
  6630. }
  6631.  
  6632.  
  6633. if (typeof txt_expand == 'string' && typeof txt_collapse == 'string' && txt_expand.length > 0 && txt_collapse.length > 0) {
  6634.  
  6635. } else {
  6636. txt_expand = null;
  6637. txt_collapse = null;
  6638. }
  6639. }
  6640.  
  6641. }
  6642.  
  6643. return { default_display_state, txt_collapse, txt_expand }
  6644.  
  6645. }
  6646.  
  6647.  
  6648. function newVideoPage(evt_detail) {
  6649.  
  6650. //toggleBtnDC = 1;
  6651.  
  6652. console.log('[tyt] newVideoPage')
  6653.  
  6654. const ytdFlexyElm = es.ytdFlexy;
  6655. if (!ytdFlexyElm) return;
  6656.  
  6657. timeline.reset();
  6658. layoutStatusMutex = new Mutex();
  6659.  
  6660. let liveChatRenderer = null;
  6661. let isReplay = null;
  6662.  
  6663. if (pageType !== 'watch') return; // scriptEnable -> pageType shall be always 'watch'
  6664. resetBuggyLayoutForNewVideoPage();
  6665.  
  6666. try {
  6667. liveChatRenderer = evt_detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  6668. } catch (e) { }
  6669. if (liveChatRenderer) {
  6670. if (liveChatRenderer.isReplay === true) isReplay = true;
  6671. }
  6672.  
  6673. pageFetchedDataVideoId = ((((evt_detail || 0).pageData || 0).playerResponse || 0).videoDetails || 0).videoId || 0;
  6674.  
  6675.  
  6676. const chatBlockR = liveChatRenderer ? (isReplay ? 3 : 1) : 0
  6677. const initialDisplayState = liveChatRenderer ? liveChatRenderer.initialDisplayState : null;
  6678.  
  6679.  
  6680. liveChatRenderer = null; // release memory for GC, just in case
  6681.  
  6682. let f = () => {
  6683.  
  6684. _console.log(932, 1, 1)
  6685. const ytdFlexyElm = es.ytdFlexy;
  6686. if (!scriptEnable || !ytdFlexyElm) return;
  6687.  
  6688. _console.log(932, 1, 2)
  6689. if (pageType !== 'watch') return;
  6690.  
  6691. _console.log(932, 1, 3)
  6692.  
  6693.  
  6694. let displayState = setupChatFrameDisplayState1(chatBlockR, initialDisplayState);
  6695.  
  6696. let { attr_chatblock, attr_chatcollapsed, chatTypeChanged } = displayState;
  6697.  
  6698.  
  6699. if (pageType === 'watch') { // reset info when hidden
  6700.  
  6701. let elm_storeLastPanel = es.storeLastPanel;
  6702.  
  6703. if (!elm_storeLastPanel) storeLastPanel = null;
  6704. else if (!isDOMVisible(elm_storeLastPanel)) {
  6705. storeLastPanel = null;
  6706. ytBtnCloseEngagementPanels();
  6707. }
  6708.  
  6709. }
  6710.  
  6711. if (chatTypeChanged) {
  6712.  
  6713. if (attr_chatblock == 'chat$live') {
  6714.  
  6715. _console.log(932, 4)
  6716.  
  6717. mtf_forceCheckLiveVideo_disable = 2;
  6718.  
  6719. //console.log(3712,1)
  6720.  
  6721. _disableComments();
  6722.  
  6723.  
  6724. } else {
  6725.  
  6726. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"].tab-btn-hidden')
  6727. if (tabBtn) {
  6728. emptyCommentSection();
  6729. _console.log(9360, 74);
  6730. tabBtn.classList.remove("tab-btn-hidden")
  6731. } else {
  6732. setCommentSection(0);
  6733. }
  6734.  
  6735. mtf_forceCheckLiveVideo_disable = 0;
  6736.  
  6737. _console.log(7234, 'comments_section_loaded = 0')
  6738. restoreFetching();
  6739.  
  6740.  
  6741. }
  6742.  
  6743.  
  6744. } else {
  6745.  
  6746. // restore Fetching only
  6747.  
  6748. if (mtf_forceCheckLiveVideo_disable !== 2 && (attr_chatblock === false || attr_chatblock === 'chat$playback')) {
  6749.  
  6750. emptyCommentSection();
  6751. _console.log(9360, 77);
  6752. mtf_forceCheckLiveVideo_disable = 0;
  6753. _console.log(7235, 'comments_section_loaded = 0')
  6754. restoreFetching();
  6755.  
  6756. }
  6757.  
  6758. }
  6759.  
  6760.  
  6761. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  6762. fetchCounts.new.f();
  6763. fetchCounts.fetched = true;
  6764. _console.log(9972, 'fetched = true')
  6765.  
  6766. fetchCommentsFinished();
  6767. }
  6768.  
  6769. }
  6770.  
  6771. f();
  6772.  
  6773. }
  6774.  
  6775. function setVideosTwoColumns(/** @type {number} */ flag, /** @type {boolean} */ bool) {
  6776.  
  6777. //two columns to one column
  6778.  
  6779. /*
  6780. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  6781.  
  6782. is-two-columns ="" => no is-two-columns
  6783. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  6784. no hidden => hidden =""
  6785. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  6786. hidden ="" => no hidden
  6787.  
  6788. */
  6789.  
  6790. let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  6791.  
  6792. let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  6793.  
  6794. let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  6795.  
  6796. let res = {}
  6797. if (flag & 1) {
  6798. res.m1 = document.querySelector(cssSelector1)
  6799. if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false);
  6800. }
  6801.  
  6802. if (flag & 2) {
  6803. res.m2 = document.querySelector(cssSelector2)
  6804. if (res.m2) wAttr(res.m2, 'hidden', bool ? false : '');
  6805. }
  6806.  
  6807. if (flag & 4) {
  6808. res.m3 = document.querySelector(cssSelector3)
  6809. if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false);
  6810. }
  6811.  
  6812. return res
  6813.  
  6814.  
  6815. }
  6816.  
  6817. // ---------------------------------------------------------------------------------------------
  6818.  
  6819. // ---- EVENTS ----
  6820.  
  6821. let ytEventSequence = 0
  6822. let formatDates = null
  6823.  
  6824. function pageBeingFetched(evt, isPageFirstLoaded) {
  6825.  
  6826. let nodeName = (((evt||0).target||0).nodeName||'').toUpperCase()
  6827. if (nodeName !== 'YTD-APP') return;
  6828.  
  6829. let pageFetchedDataLocal = evt.detail;
  6830.  
  6831. let d_page = ((pageFetchedDataLocal||0).pageData||0).page;
  6832. if (!d_page) return;
  6833.  
  6834. pageType = d_page;
  6835.  
  6836. if (pageType !== 'watch') return
  6837.  
  6838. let promiseChatDetails = null
  6839.  
  6840. let isFirstLoad = firstLoadStatus & 8;
  6841.  
  6842. if (isFirstLoad) {
  6843. firstLoadStatus -= 8;
  6844. document.addEventListener('load', iframeLoadHookA, capturePassive)
  6845. ytMicroEventsInit();
  6846. }
  6847. // ytMicroEventsInit set
  6848. variableResets();
  6849.  
  6850. if (isFirstLoad) {
  6851.  
  6852. if (ytEventSequence >= 2) {
  6853. let docElement = document.documentElement
  6854. if (docElement.hasAttribute('tabview-loaded')) {
  6855. throw 'Tabview Youtube Duplicated';
  6856. }
  6857. docElement.setAttribute('tabview-loaded', '')
  6858.  
  6859. Promise.resolve(docElement).then(docElement => {
  6860. if (ytEventSequence >= 2) {
  6861. docElement.dispatchEvent(new CustomEvent('tabview-ce-hack'))
  6862. docElement = null
  6863. }
  6864. })
  6865.  
  6866. }
  6867. }
  6868. // tabview-loaded delay set
  6869.  
  6870. formatDates={}
  6871. try{
  6872. formatDates.publishDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.publishDate
  6873. }catch(e){}
  6874. // 2022-12-30
  6875.  
  6876. try{
  6877. formatDates.uploadDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.uploadDate
  6878. }catch(e){}
  6879. // 2022-12-30
  6880.  
  6881. try{
  6882. formatDates.publishDate2 = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.dateText.simpleText
  6883. }catch(e){}
  6884. // 2022/12/31
  6885.  
  6886. if(typeof formatDates.publishDate2==='string' && formatDates.publishDate2 !== formatDates.publishDate){
  6887. formatDates.publishDate = formatDates.publishDate2
  6888. formatDates.uploadDate = null
  6889. }
  6890.  
  6891. try{
  6892. formatDates.broadcastBeginAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.startTimestamp
  6893. }catch(e){}
  6894. try{
  6895. formatDates.broadcastEndAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.endTimestamp
  6896. }catch(e){}
  6897. try{
  6898. formatDates.isLiveNow = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.isLiveNow
  6899. }catch(e){}
  6900.  
  6901. promiseChatDetails = new Promise(resolve => {
  6902. if (ytEventSequence >= 2) {
  6903. let liveChatRenderer = null;
  6904. try {
  6905. liveChatRenderer = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  6906. } catch (e) { }
  6907. chatroomDetails = liveChatRenderer ? extractInfoFromLiveChatRenderer(liveChatRenderer) : null;
  6908. liveChatRenderer = null; // release memory for GC, just in case
  6909. }
  6910. resolve(0)
  6911. })
  6912.  
  6913. let ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  6914.  
  6915. if (!ytdFlexyElm) return;
  6916.  
  6917.  
  6918. ytdFlexy = mWeakRef(ytdFlexyElm);
  6919.  
  6920. ytdFlexyElm = null;
  6921.  
  6922. Promise.resolve(0).then(() => {
  6923.  
  6924. if (ytEventSequence >= 2 && pageRendered === 0) {
  6925.  
  6926. const ytdFlexyElm = es.ytdFlexy; // shall be always non-null
  6927. if (ytdFlexyElm) {
  6928.  
  6929. let elmPL = document.createElement('tabview-view-ploader');
  6930. pageRendered = 1;
  6931. ytdFlexyElm.appendChild(elmPL);
  6932. // pageRendered keeps at 1 if the video is continuously playing at the background
  6933. // pageRendered would not be resolve but will reset for each change of video
  6934.  
  6935. }
  6936.  
  6937. }
  6938.  
  6939. })
  6940.  
  6941. let renderId = renderIdentifier
  6942. renderDeferred.debounce(() => {
  6943. if (renderId !== renderIdentifier) return
  6944. if (ytEventSequence >= 2) {
  6945. advanceFetch(); // at least one triggering at yt-page-data-fetched
  6946. }
  6947. });
  6948.  
  6949. Promise.race([promiseChatDetails]).then(() => {
  6950.  
  6951. const ytdFlexyElm = es.ytdFlexy;
  6952. if (ytEventSequence >= 2 && ytdFlexyElm) {
  6953. ytdFlexyElm.classList.toggle('tyt-chat-toggleable', !!chatroomDetails);
  6954. }
  6955.  
  6956. }).then(() => {
  6957.  
  6958. if (ytEventSequence >= 2) {
  6959.  
  6960. let tabsDeferredSess = pageSession.session();
  6961. if (!scriptEnable && tabsDeferred.resolved) { }
  6962. else tabsDeferred.debounce(() => {
  6963.  
  6964. if (!tabsDeferredSess.isValid) return;
  6965. tabsDeferredSess = null;
  6966.  
  6967. if (ytEventSequence >= 2 && pageFetchedDataLocal !== null) {
  6968. domInit_comments();
  6969. newVideoPage(pageFetchedDataLocal);
  6970. pageFetchedDataLocal = null;
  6971. }
  6972.  
  6973. });
  6974.  
  6975. }
  6976.  
  6977. })
  6978.  
  6979. }
  6980.  
  6981. let pageSeqMutex = new Mutex()
  6982.  
  6983. function pageSeq1(evt) {
  6984. _navigateLoadDT = 0
  6985. if (ytEventSequence !== 1) {
  6986. ytEventSequence = 1
  6987. pageSeqMutex.lockWith(unlock=>{
  6988. pageBeingInit();
  6989. unlock();
  6990. })
  6991. }
  6992. }
  6993.  
  6994. function pageSeq2(evt) {
  6995. _navigateLoadDT = 0
  6996.  
  6997. if (ytEventSequence !== 1) {
  6998. ytEventSequence = 1
  6999. pageSeqMutex.lockWith(unlock=>{
  7000. pageBeingInit();
  7001. unlock();
  7002. })
  7003. }
  7004. if (ytEventSequence === 1) {
  7005. ytEventSequence = 2
  7006.  
  7007. pageType = null
  7008. pageSeqMutex.lockWith(unlock=>{
  7009. let mIsPageFirstLoaded = _isPageFirstLoaded
  7010. pageType = null
  7011. // mIsPageFirstLoaded && console.time("Tabview Youtube Load")
  7012. pageBeingFetched(evt, mIsPageFirstLoaded)
  7013. // mIsPageFirstLoaded && console.timeEnd("Tabview Youtube Load")
  7014. // ytMicroEventsInit set + tabview-loaded delay set
  7015. new Promise(() => {
  7016. if (ytEventSequence >= 2) {
  7017. document.documentElement.classList.toggle('tabview-normal-player', pageType === 'watch');
  7018. }
  7019. })
  7020. if (pageType !== 'watch') {
  7021. ytdFlexy = null
  7022. chatroomDetails = null
  7023. Promise.resolve(0).then(() => {
  7024. if (ytEventSequence >= 2) {
  7025. variableResets();
  7026. emptyCommentSection();
  7027. _console.log(9360, 75);
  7028. tabsDeferred.reset();
  7029. _pageBeingInit();
  7030. tabsDeferred.resolve(); // for page initialization
  7031. }
  7032. })
  7033. }
  7034.  
  7035. if (_updateTimeAccum > 0) {
  7036. let currentVideo = document.querySelector('#movie_player video[src]')
  7037. let keep_viTime = false
  7038. if (currentVideo && currentVideo.readyState > currentVideo.HAVE_CURRENT_DATA && currentVideo.currentTime > 2.2) {
  7039. // allow miniview browsing
  7040. keep_viTime = true
  7041. }
  7042. if (!keep_viTime) {
  7043. _viTimeNum = 200;
  7044. _updateTimeAccum = 0;
  7045. delete document.head.dataset.viTime;
  7046. }
  7047. }
  7048.  
  7049. unlock();
  7050. })
  7051. }
  7052.  
  7053. }
  7054.  
  7055. function pageSeq3(evt) {
  7056. _navigateLoadDT = 0
  7057. if (ytEventSequence === 2) {
  7058. ytEventSequence = 3
  7059. pageSeqMutex.lockWith(unlock => {
  7060. if (pageType === 'watch') {
  7061. let mIsPageFirstLoaded = _isPageFirstLoaded
  7062. // ytMicroEventsInit set + tabview-loaded delay set
  7063. onNavigationEndAsync(mIsPageFirstLoaded)
  7064. _isPageFirstLoaded = false
  7065. }
  7066. unlock();
  7067. })
  7068. }
  7069. }
  7070.  
  7071.  
  7072. document.addEventListener('yt-navigate-start', pageSeq1, bubblePassive)
  7073. document.addEventListener('yt-navigate-cache', pageSeq1, bubblePassive)
  7074. document.addEventListener('yt-navigate-redirect', pageSeq1, bubblePassive)
  7075. document.addEventListener('yt-page-data-fetched', pageSeq2, bubblePassive)
  7076. document.addEventListener("yt-navigate-finish", pageSeq3, bubblePassive)
  7077. //yt-navigate-redirect
  7078. //yt-page-data-fetched
  7079. //yt-navigate-error
  7080. //yt-navigate-start
  7081. //yt-page-manager-navigate-start
  7082. //yt-navigate
  7083. //yt-navigate-cache
  7084.  
  7085. globalHook('yt-page-data-fetched', generalLog901)
  7086. //globalHook('yt-rendererstamper-finished',generalLog901)
  7087. globalHook('yt-page-data-updated', generalLog901)
  7088. globalHook('yt-player-updated', generalLog901)
  7089. globalHook('yt-watch-comments-ready', generalLog901)
  7090. globalHook('yt-page-type-changed', generalLog901)
  7091. globalHook('data-changed', generalLog901)
  7092. globalHook('yt-navigate-finish', generalLog901)
  7093. globalHook('yt-navigate-redirect', generalLog901)
  7094. globalHook('yt-navigate-error', generalLog901)
  7095. globalHook('yt-navigate-start', generalLog901)
  7096. globalHook('yt-page-manager-navigate-start', generalLog901)
  7097. globalHook('yt-navigate', generalLog901)
  7098. globalHook('yt-navigate-cache', generalLog901)
  7099.  
  7100.  
  7101. // ---------------------------------------------------------------------------------------------
  7102.  
  7103.  
  7104. function manualResizeT(){
  7105.  
  7106. if (!scriptEnable) return;
  7107. if (pageType !== 'watch') return;
  7108. //lastResizeAt = Date.now();
  7109.  
  7110. if((wls.layoutStatus & LAYOUT_FULLSCREEN) === LAYOUT_FULLSCREEN ){
  7111.  
  7112. }else if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === 0) {
  7113. // single col
  7114.  
  7115. setTimeout3(() => {
  7116. singleColumnScrolling(true)
  7117. })
  7118.  
  7119. } else {
  7120. // two cols
  7121.  
  7122. updateFloatingSlider();
  7123.  
  7124. }
  7125.  
  7126.  
  7127. }
  7128. //let lastResizeAt = 0;
  7129. let resizeCount = 0;
  7130. window.addEventListener('resize', function (evt) {
  7131.  
  7132. if (evt.isTrusted === true) {
  7133. //console.log(evt)
  7134. let tcw = ++resizeCount;
  7135. Promise.resolve(0).then(()=>{
  7136. if (tcw !== resizeCount) return;
  7137. setTimeout(() => {
  7138. // avoid duplicate calling during resizing
  7139. if (tcw !== resizeCount) return;
  7140. resizeCount = 0;
  7141. manualResizeT();
  7142. dispatchCommentRowResize();
  7143. }, 160);
  7144. });
  7145. }
  7146.  
  7147. }, bubblePassive)
  7148.  
  7149.  
  7150. document.addEventListener("tyt-chat-popup",(evt)=>{
  7151.  
  7152. let detail = (evt || 0).detail
  7153. if (!detail) return
  7154. const { popuped } = detail
  7155. if (typeof popuped !== 'boolean') return;
  7156.  
  7157. let ytdFlexyElm = es.ytdFlexy
  7158. if (!ytdFlexyElm) return
  7159.  
  7160. ytdFlexyElm.classList.toggle('tyt-chat-popup', popuped)
  7161. if (popuped === true) {
  7162. enableLivePopupCheck = true;
  7163. ytBtnSetTheater()
  7164. } else {
  7165. enableLivePopupCheck = false;
  7166. ytBtnCancelTheater()
  7167. }
  7168.  
  7169. })
  7170.  
  7171. document.addEventListener("tabview-plugin-loaded",()=>{
  7172.  
  7173. scriptletDeferred.resolve();
  7174.  
  7175. if(MINIVIEW_BROWSER_ENABLE){
  7176. document.dispatchEvent(new CustomEvent("tabview-miniview-browser-enable"));
  7177. }
  7178.  
  7179. }, false)
  7180.  
  7181.  
  7182. document.documentElement.setAttribute('plugin-tabview-youtube', `${scriptVersionForExternal}`)
  7183. if(document.documentElement.getAttribute('tabview-unwrapjs')){
  7184. document.dispatchEvent(new CustomEvent("tabview-plugin-loaded"))
  7185. }
  7186.  
  7187.  
  7188. // function nestedObjectFlatten(prefix, obj) {
  7189. // let ret = {};
  7190. // let _prefix = prefix ? `${prefix}.` : '';
  7191. // let isObject = (obj && typeof obj == 'object' && obj.constructor.name == 'Object');
  7192. // let isArray = (obj && typeof obj == 'object' && obj.constructor.name == 'Array');
  7193. // const f = (k, v) => {
  7194. // let isObject = (v && typeof v == 'object' && v.constructor.name == 'Object');
  7195. // let isArray = (v && typeof v == 'object' && v.constructor.name == 'Array');
  7196. // if (isObject || isArray) {
  7197. // let r = nestedObjectFlatten(k, v)
  7198. // for (const w in r) {
  7199. // ret[`${_prefix}${w}`] = r[w];
  7200. // }
  7201. // } else {
  7202. // ret[`${_prefix}${k}`] = v;
  7203. // }
  7204. // }
  7205. // if (isObject) {
  7206. // for (const k in obj) {
  7207. // let v = obj[k];
  7208. // f(k, v);
  7209. // }
  7210. // } else if (isArray) {
  7211. // let idx = 0;
  7212. // for (const v of obj) {
  7213. // let k = `[${idx}]`;
  7214. // f(k, v);
  7215. // idx++;
  7216. // }
  7217. // }
  7218. // return ret;
  7219. // }
  7220.  
  7221. /*
  7222.  
  7223. for(const p of document.querySelectorAll('ytd-watch-flexy *')){ let m = p.data; if(!m)continue; console.log(m)}
  7224.  
  7225. function objec
  7226.  
  7227. */
  7228.  
  7229.  
  7230. //Object.keys($0).filter(key=>!(key in $0.constructor.prototype))
  7231.  
  7232. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML'))
  7233. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML')).filter(k=>$0 instanceof window[k])
  7234.  
  7235.  
  7236. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  7237.  
  7238. /*
  7239. fix bug for comment section - version 1.8.7
  7240. This issue is the bug in browser's rendering
  7241. I guess, this is due to the lines clamp with display:-webkit-box
  7242. use stupid coding to let it re-render when its content become visible
  7243. /*
  7244.  
  7245. ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander {
  7246. color: var(--yt-spec-text-primary);
  7247. display: -webkit-box;
  7248. overflow: hidden;
  7249. max-height: none;
  7250. -webkit-box-orient: vertical;
  7251. -webkit-line-clamp: var(--ytd-expander-max-lines, 4);
  7252. }
  7253.  
  7254. // v1.8.36 imposed a effective solution for fixing this bug
  7255.  
  7256. */
  7257.  
  7258. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  7259.  
  7260.  
  7261. /**
  7262. *
  7263.  
  7264.  
  7265. f.initChildrenObserver=function(){var a=this;this.observer=new MutationObserver(function(){return a.childrenChanged()});
  7266. this.observer.observe(this.$.content,{subtree:!0,childList:!0,attributes:!0});this.childrenChanged()};
  7267. 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()})}))};
  7268.  
  7269.  
  7270. f.onIronResize=function(){this.recomputeOnResize&&this.childrenChanged()};
  7271.  
  7272.  
  7273. onButtonClick_:function(){this.fire("yt-close-upsell-dialog")},
  7274. 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};
  7275. n(xI,L);f=xI.prototype;f.alwaysToggleableChanged=function(){this.alwaysToggleable&&(this.canToggle=!0)};
  7276.  
  7277.  
  7278. f.calculateCanCollapse=function(){this.canToggle=this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  7279. f.detachObserver=function(){this.observer&&this.observer.disconnect()};
  7280.  
  7281. *
  7282. *
  7283. *
  7284. */
  7285.  
  7286.  
  7287. })();
  7288. // console.timeEnd("Tabview Youtube Init Script")
  7289.  
  7290.  
  7291.  
  7292.  
  7293.  
  7294.  
  7295.  
  7296.  
  7297.  
  7298.  
  7299.  
  7300.  
  7301.  
  7302.  
  7303.  
  7304.  
  7305.  
  7306.  
  7307.  
  7308.  
  7309.  
  7310.  
  7311.  
  7312.  
  7313.  
  7314.  
  7315.  
  7316.  
  7317.  
  7318.  
  7319.  
  7320.  
  7321.  
  7322.  
  7323.  
  7324.  
  7325.  
  7326. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  7327.  
  7328. }
  7329.  
  7330.  
  7331. ;!(function $$() {
  7332. 'use strict';
  7333.  
  7334. if(document.documentElement==null) return window.requestAnimationFrame($$)
  7335.  
  7336. const cssTxt = GM_getResourceText("contentCSS");
  7337.  
  7338. function addStyle (styleText) {
  7339. const styleNode = document.createElement('style');
  7340. styleNode.type = 'text/css';
  7341. styleNode.textContent = styleText;
  7342. document.documentElement.appendChild(styleNode);
  7343. return styleNode;
  7344. }
  7345.  
  7346. addStyle (cssTxt);
  7347.  
  7348. main();
  7349.  
  7350.  
  7351. // Your code here...
  7352. })();