Tabview Youtube

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

目前為 2022-11-15 提交的版本,檢視 最新版本

  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. // @name:ja Tabview Youtube
  30. // @name:zh-TW Tabview Youtube
  31. // @name:zh-CN Tabview Youtube
  32. // @namespace http://tampermonkey.net/
  33. // @version 3.1.2
  34. // @license MIT
  35. // @description Make comments and lists into tabs for YouTube Videos
  36. // @description:en Make comments and lists into tabs for YouTube Videos
  37. // @description:ja YouTube動画のコメントやリストなどをタブに作成します
  38. // @description:zh-TW 把Youtube Videos中的評論及影片清單製作成Tabs
  39. // @description:zh-CN 把Youtube Videos中的评论及视频列表制作成Tabs
  40. // @author CY Fung
  41. // @match https://www.youtube.com/*
  42. // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
  43. // @run-at document-start
  44. // @grant GM_getResourceText
  45. // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/6d0a55a2f5cd906137a217b0f9f3493612df4c67/css/style_content.css
  46. // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/e4582972990301143bab5a318f802136c6a5a921/js/injection_script_1.js
  47. // @require https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.1/cash.min.js
  48. // @noframes
  49. // ==/UserScript==
  50.  
  51. /* jshint esversion:8 */
  52.  
  53. function main($){
  54. // MIT License
  55. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74. -(function() {
  75. function inIframe() {
  76. try {
  77. return window.self !== window.top;
  78. } catch (e) {
  79. return true;
  80. }
  81. }
  82.  
  83. if (inIframe()) return;
  84.  
  85. if (!$) return;
  86.  
  87. /**
  88. * SVG resources:
  89. * <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>
  90. */
  91.  
  92. const scriptVersionForExternal = '2022/05/07';
  93.  
  94. const isMyScriptInChromeRuntime = () => typeof GM === 'undefined' && typeof((((window || 0).chrome || 0).runtime || 0).getURL) == 'function'
  95.  
  96. const svgComments = `<path d="M40.068 13.465l-34.138.07A5.94 5.94 0 0 0 0 19.465v21.141a5.94 5.94 0 0 0 5.93 5.929H12v10a1
  97. 1 0 0 0 1.74.673l9.704-10.675 16.626-.068A5.94 5.94 0 0 0 46 40.536V19.395a5.94 5.94 0 0 0-5.932-5.93zM10 23.465h13a1
  98. 1 0 1 1 0 2H10a1 1 0 1 1 0-2zm26 14H10a1 1 0 1 1 0-2h26a1 1 0 1 1 0 2zm0-6H10a1 1 0 1 1 0-2h26a1 1 0 1 1 0
  99. 2zm18.072-28.93l-34.142-.07A5.94 5.94 0 0 0 14 8.395v3.124l26.064-.054c4.377 0 7.936 3.557 7.936 7.93v21.07.071
  100. 2.087l3.26 3.586a1 1 0 0 0 1.74-.673v-10h1.07A5.94 5.94 0 0 0 60 29.607V8.465a5.94 5.94 0 0 0-5.928-5.93z"/>`.trim();
  101.  
  102. const svgVideos = `<path d="M298 33c0-13.255-10.745-24-24-24H24C10.745 9 0 19.745 0 33v232c0 13.255 10.745 24 24
  103. 24h250c13.255 0 24-10.745 24-24V33zM91 39h43v34H91V39zM61 259H30v-34h31v34zm0-186H30V39h31v34zm73
  104. 186H91v-34h43v34zm-11-82.292v-55.417c0-8.25 5.868-11.302 12.77-6.783l40.237 26.272c6.902 4.519 6.958 11.914.056
  105. 16.434l-40.321 26.277c-6.902 4.52-12.742 1.467-12.742-6.783zM207 259h-43v-34h43v34zm0-186h-43V39h43v34zm61
  106. 186h-31v-34h31v34zm0-186h-31V39h31v34z"/>`.trim();
  107.  
  108. const svgInfo = `<path d="M11.812 0C5.289 0 0 5.289 0 11.812s5.289 11.813 11.812 11.813 11.813-5.29 11.813-11.813S18.335
  109. 0 11.812 0zm2.459 18.307c-.608.24-1.092.422-1.455.548s-.783.189-1.262.189c-.736
  110. 0-1.309-.18-1.717-.539s-.611-.814-.611-1.367c0-.215.015-.435.045-.659a8.23 8.23 0 0 1
  111. .147-.759l.761-2.688c.067-.258.125-.503.171-.731a3.24 3.24 0 0 0
  112. .068-.633c0-.342-.071-.582-.212-.717s-.412-.201-.813-.201c-.196
  113. 0-.398.029-.605.09s-.383.12-.529.176l.201-.828c.498-.203.975-.377 1.43-.521s.885-.218 1.29-.218c.731 0 1.295.178
  114. 1.692.53s.594.812.594 1.376c0 .117-.014.323-.041.617a4.13 4.13 0 0 1-.152.811l-.757
  115. 2.68c-.062.215-.117.461-.167.736s-.073.485-.073.626c0 .356.079.599.239.728s.435.194.827.194a2.4 2.4 0 0 0 .626-.097
  116. 3.56 3.56 0 0 0 .506-.17l-.203.827zm-.134-10.878c-.353.328-.778.492-1.275.492s-.924-.164-1.28-.492a1.57 1.57 0
  117. 0 1-.533-1.193c0-.465.18-.865.533-1.196a1.81 1.81 0 0 1 1.28-.497 1.79 1.79 0 0 1 1.275.497c.353.331.53.731.53
  118. 1.196s-.177.865-.53 1.193z"/>`.trim();
  119.  
  120. const svgPlayList = `<path d="M0 64h256v42.667H0zm0 85.333h256V192H0zm0 85.334h170.667v42.667H0zm341.333
  121. 0v-85.334h-42.666v85.334h-85.334v42.666h85.334v85.334h42.666v-85.334h85.334v-42.666z"/>`.trim();
  122.  
  123.  
  124. const DEBUG_LOG = false
  125.  
  126. const LAYOUT_VAILD = 1;
  127.  
  128. const LAYOUT_TWO_COLUMNS = 2;
  129. const LAYOUT_THEATER = 4;
  130. const LAYOUT_FULLSCREEN = 8;
  131. const LAYOUT_CHATROOM = 16;
  132. const LAYOUT_CHATROOM_COLLASPED = 32;
  133. const LAYOUT_TAB_EXPANDED = 64;
  134. const LAYOUT_ENGAGEMENT_PANEL_EXPAND = 128;
  135. const LAYOUT_CHATROOM_EXPANDED = 256;
  136.  
  137. let pageType = null;
  138.  
  139. /*
  140.  
  141. yt-action yt-add-element-to-app yt-autonav-pause-blur yt-autonav-pause-focus
  142. yt-autonav-pause-guide-closed yt-autonav-pause-guide-opened yt-autonav-pause-player
  143. yt-autonav-pause-player-ended yt-autonav-pause-scroll yt-autoplay-on-changed
  144. yt-close-tou-form yt-consent-bump-display-changed yt-focus-searchbox
  145. yt-get-context-provider yt-guide-close yt-guide-hover yt-guide-toggle
  146. yt-history-load yt-history-pop yt-load-invalidation-continuation
  147. yt-load-next-continuation yt-load-reload-continuation yt-load-tou-form
  148. yt-masthead-height-changed yt-navigate yt-navigate-cache yt-navigate-error
  149. yt-navigate-finish yt-navigate-redirect yt-navigate-set-page-offset
  150. yt-navigate-start yt-next-continuation-data-updated yt-open-hotkey-dialog
  151. yt-open-tou-form-loading-state yt-page-data-fetched yt-page-data-updated
  152. yt-page-data-will-update yt-page-manager-navigate-start yt-page-navigate-start
  153. yt-page-type-changed yt-player-attached yt-player-detached yt-player-released
  154. yt-player-requested yt-player-updated yt-popup-canceled yt-popup-closed
  155. yt-popup-opened yt-preconnect-urls yt-register-action yt-report-form-closed
  156. yt-report-form-opened yt-request-panel-mode-change yt-retrieve-location
  157. yt-service-request-completed yt-service-request-error yt-service-request-sent
  158. yt-set-theater-mode-enabled yt-show-survey yt-subscription-changed
  159. yt-swatch-changed yt-theater-mode-allowed yt-unregister-action yt-update-title
  160. yt-update-unseen-notification-count yt-viewport-scanned yt-visibility-refresh
  161.  
  162. */
  163.  
  164.  
  165.  
  166. const nullFunc = function(){}
  167. const _console = new Proxy(console, {
  168. get(target, prop, receiver){
  169. if(!DEBUG_LOG && prop==='log'){
  170. return nullFunc
  171. }
  172. return Reflect.get(...arguments)
  173. }
  174. });
  175.  
  176. const isPassiveArgSupport = (typeof IntersectionObserver==='function');
  177. // https://caniuse.com/?search=observer
  178. // https://caniuse.com/?search=addEventListener%20passive
  179.  
  180. const bubblePassive = isPassiveArgSupport?{capture:false, passive:true}:false;
  181. const capturePassive = isPassiveArgSupport?{capture:true, passive:true}:true;
  182.  
  183.  
  184. _console.log(38489)
  185.  
  186.  
  187. const querySelectorFromAnchor = HTMLElement.prototype.querySelector; // nodeType==1 // since 2022/07/12
  188. const querySelectorAllFromAnchor = HTMLElement.prototype.querySelectorAll; // nodeType==1 // since 2022/07/12
  189. const closestDOM = HTMLElement.prototype.closest;
  190. const elementRemove = HTMLElement.prototype.remove;
  191. const elementInsertBefore = HTMLElement.prototype.insertBefore; // since 2022/07/12
  192. const elementContains = HTMLElement.prototype.contains; // since 2022/07/12
  193.  
  194. const querySelectorFromAnchorFizzy = (root, id, selector)=>{
  195. if(!root) return null;
  196. if(root.id===id && root.matches(selector))return root;
  197. return querySelectorFromAnchor.call(root, selector) || null;
  198. }
  199.  
  200.  
  201. function maxUInt(s, d){
  202. let t = (d>s?d:s);
  203. return t>0?t:0;
  204. }
  205.  
  206. function scriptInjector(script_id, url_chrome, response_id){
  207.  
  208. let res={
  209. script_id: script_id,
  210. inject: function(){
  211.  
  212. let res = this, script_id = this.script_id;
  213.  
  214. if(!document.querySelector(`script#${script_id}`)){
  215. if (res.runtime_url){
  216. addScriptByURL(res.runtime_url).id = script_id;
  217. } else {
  218. addScript(`${res.injection_script}`).id = script_id;
  219. }
  220. }
  221.  
  222. }
  223. }
  224. res.script_id = script_id;
  225. if (isMyScriptInChromeRuntime()){
  226. res.runtime_url = window.chrome.runtime.getURL(url_chrome)
  227. } else {
  228. res.injection_script = GM_getResourceText(response_id);
  229. }
  230.  
  231. return res;
  232.  
  233.  
  234.  
  235. }
  236.  
  237. /*
  238. const script_inject_facp = scriptInjector(
  239. 'userscript-tabview-injection-facp',
  240. 'js/injectionScript_fixAutoComplete.js',
  241. "injectionFixAutoComplete");
  242. */
  243.  
  244. const script_inject_js1 = scriptInjector(
  245. 'userscript-tabview-injection-1',
  246. 'js/injection_script_1.js',
  247. "injectionJS1");
  248.  
  249. /** @type {(o: Object | null) => WeakRef | null} */
  250. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  251.  
  252. /** @type {(wr: Object | null) => Object | null} */
  253. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  254.  
  255.  
  256.  
  257. const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  258. function nonCryptoRandStr(/** @type {number} */ n){
  259. const result = new Array(n);
  260. const baseStr = nonCryptoRandStr_base;
  261. const bLen = baseStr.length;
  262. for ( let i = 0; i < n; i++ ) {
  263. let t = null
  264. do {
  265. t = baseStr.charAt(Math.floor(Math.random() * bLen));
  266. }while(i===0 && 10-t>0)
  267. result[i]=t;
  268. }
  269. return result.join('');
  270. }
  271.  
  272. /**
  273. * Class definition
  274. * @property {string} propName - propriety description
  275. * ...
  276. */
  277. class ObserverRegister{
  278. constructor(/** @type {()=>MutationObserver | IntersectionObserver} */ observerCreator){
  279. let uid = null;
  280. const uidStore = ObserverRegister.uidStore;
  281. do{
  282. uid = nonCryptoRandStr(5);
  283. }while(uidStore[uid])
  284. uidStore[uid] = true;
  285. /**
  286. * uid is the unique string for each observer
  287. * @type {string}
  288. * @public
  289. */
  290. this.uid = uid;
  291. /**
  292. * observerCreator is a function to create the observer
  293. * @type {Function}
  294. * @public
  295. */
  296. this.observerCreator = observerCreator
  297. /**
  298. * observer is the actual observer object
  299. * @type {MutationObserver | IntersectionObserver}
  300. * @public
  301. */
  302. this.observer = null;
  303. }
  304. bindElement(/** @type {HTMLElement} */ elm, ...args){
  305. if(elm.hasAttribute(`o3r-${this.uid}`))return false;
  306. elm.setAttribute(`o3r-${this.uid}`,'')
  307. if(this.observer===null){
  308. this.observer=this.observerCreator();
  309. }
  310. this.observer.observe(elm, ...args)
  311. return true
  312. }
  313. clear(/** @type {boolean} */ flag){
  314. if(this.observer !== null){
  315. //const uidStore = ObserverRegister.uidStore;
  316. if(flag === true){
  317. this.observer.takeRecords();
  318. this.observer.disconnect();
  319. }
  320. this.observer = null;
  321. for(const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`)
  322. //uidStore[this.uid]=false;
  323. //this.uid = null;
  324. }
  325. }
  326. }
  327.  
  328. /**
  329. * 'uidStore' is the static store of strings used.
  330. * @static
  331. */
  332. ObserverRegister.uidStore = {}; //backward compatible with FireFox 55.
  333.  
  334.  
  335. const mtoMutation_body = new ObserverRegister(()=>{
  336. return new MutationObserver(FP.mtoBodyF)
  337. });
  338.  
  339. const mtoFlexyAttr = new ObserverRegister(()=>{
  340. return new MutationObserver(mtf_attrFlexy)
  341. });
  342.  
  343. const mtoVisibility_EngagementPanel = new ObserverRegister(()=>{
  344. return new MutationObserver(FP.mtf_attrEngagementPanel)
  345. });
  346. const sa_epanel = mtoVisibility_EngagementPanel.uid;
  347.  
  348. const mtoVisibility_Playlist = new ObserverRegister(()=>{
  349. return new AttributeMutationObserver({
  350. "hidden": FP.mtf_attrPlaylist
  351. })
  352. })
  353. const sa_playlist = mtoVisibility_Playlist.uid;
  354.  
  355. const mtoVisibility_Comments = new ObserverRegister(()=>{
  356. return new AttributeMutationObserver({
  357. "hidden": FP.mtf_attrComments
  358. })
  359. })
  360. const sa_comments = mtoVisibility_Comments.uid;
  361.  
  362.  
  363. const mtoVisibility_Chatroom = new ObserverRegister(()=>{
  364. return new AttributeMutationObserver({
  365. "collapsed": FP.mtf_attrChatroom
  366. })
  367. })
  368. const sa_chatroom = mtoVisibility_Chatroom.uid;
  369.  
  370.  
  371.  
  372.  
  373.  
  374. class ScriptEF {
  375. constructor() {
  376. this._id = scriptEC;
  377. }
  378. isValid() {
  379. return this._id === scriptEC;
  380. }
  381. }
  382.  
  383. class Timeout {
  384.  
  385. set(f, d, repeatCount) {
  386. if (this.cid > 0) return;
  387. let sEF = new ScriptEF();
  388. if (repeatCount > 0) {
  389.  
  390. let rc = repeatCount;
  391. const g = () => {
  392. this.cid = 0;
  393. if (!sEF.isValid()) return;
  394. let res = f();
  395. if (--rc <= 0) return;
  396. if (res === true) this.cid = timeline.setTimeout(g, d);
  397. }
  398. g();
  399.  
  400. } else {
  401.  
  402. const g = () => {
  403. this.cid = 0;
  404. if (!sEF.isValid()) return;
  405. if (f() === true) this.cid = timeline.setTimeout(g, d);
  406. }
  407. this.cid = timeline.setTimeout(g, d);
  408. }
  409. }
  410.  
  411. clear() {
  412. if (this.cid > 0) timeline.clearTimeout(this.cid);
  413. }
  414.  
  415. isEmpty() {
  416. return !this.cid
  417. }
  418.  
  419.  
  420. }
  421.  
  422. class Mutex {
  423.  
  424. constructor() {
  425. this.p = Promise.resolve()
  426. }
  427.  
  428. lockWith(f) {
  429.  
  430. this.p = this.p.then(() => {
  431. return new Promise(f)
  432. }).catch(console.warn)
  433. }
  434.  
  435. }
  436.  
  437.  
  438.  
  439. function prettyElm(/** @type {Element} */ elm) {
  440. if (!elm || !elm.nodeName) return null;
  441. const eId = elm.id || null;
  442. const eClsName = elm.className || null;
  443. return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  444. }
  445.  
  446. function extractTextContent(/** @type {Node} */ elm) {
  447. 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, '')
  448. }
  449.  
  450. function addScript(/** @type {string} */ scriptText) {
  451. const scriptNode = document.createElement('script');
  452. scriptNode.type = 'text/javascript';
  453. scriptNode.textContent = scriptText;
  454. try {
  455. document.documentElement.appendChild(scriptNode);
  456. } catch (e) {
  457. console.log('addScript Error', e)
  458. }
  459. return scriptNode;
  460. }
  461.  
  462. function addScriptByURL(/** @type {string} */ scriptURL) {
  463. const scriptNode = document.createElement('script');
  464. scriptNode.type = 'text/javascript';
  465. scriptNode.src = scriptURL;
  466. try {
  467. document.documentElement.appendChild(scriptNode);
  468. } catch (e) {
  469. console.log('addScriptByURL Error', e)
  470. }
  471. return scriptNode;
  472. }
  473.  
  474. function addStyle(/** @type {string} */ styleText, /** @type {HTMLElement | Document} */ container) {
  475. const styleNode = document.createElement('style');
  476. //styleNode.type = 'text/css';
  477. styleNode.textContent = styleText;
  478. (container || document.documentElement).appendChild(styleNode);
  479. return styleNode;
  480. }
  481.  
  482.  
  483. const stopIframePropagation = function(/** @type {Event} */ evt){
  484. if(scriptEnable && ((evt||0).target||0).nodeName === 'IFRAME'){
  485. evt.stopImmediatePropagation();
  486. evt.stopPropagation();
  487. }
  488. }
  489. document.addEventListener('mouseover', stopIframePropagation, true)
  490. document.addEventListener('mouseout', stopIframePropagation, true)
  491. document.addEventListener('mousedown', stopIframePropagation, true)
  492. document.addEventListener('mouseup', stopIframePropagation, true)
  493. document.addEventListener('keydown', stopIframePropagation, true)
  494. document.addEventListener('keyup', stopIframePropagation, true)
  495. document.addEventListener('mouseenter', stopIframePropagation, true)
  496. document.addEventListener('mouseleave', stopIframePropagation, true)
  497.  
  498.  
  499.  
  500. function isDOMVisible(/** @type {HTMLElement} */ elem) {
  501. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  502. return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
  503. }
  504.  
  505. function isNonEmptyString(s) {
  506. return typeof s == 'string' && s.length > 0;
  507. }
  508.  
  509.  
  510. async function nativeCall(/** @type {EventTarget} */ dom, /** @type {any[]} */ detail) {
  511. //console.log(1231)
  512. dom.dispatchEvent(new CustomEvent("userscript-call-dom", { detail: detail }))
  513. //console.log(1232)
  514. }
  515.  
  516. async function nativeFunc(/** @type {EventTarget} */ dom, /** @type {string} */ property, /** @type {any} */ args) {
  517. dom.dispatchEvent(new CustomEvent("userscript-call-dom-func", { detail: { property, args } }))
  518. }
  519.  
  520. // async function nativeValue(dom, property, args) {
  521. // dom.dispatchEvent(new CustomEvent("userscript-call-dom-value", { detail: { property, args } }))
  522. // }
  523. // async function nativeFuncStacked(/** @type {string} */ selector, /** @type {string} */ property, /** @type {any} */ args){
  524. // document.dispatchEvent(new CustomEvent("userscript-call-dom-func-stacked", { detail: { selector, property, args } }))
  525. // }
  526. // async function nativeValueStacked(selector, property, args){
  527. // document.dispatchEvent(new CustomEvent("userscript-call-dom-value-stacked", { detail: { selector, property, args } }))
  528. // }
  529. // async function nativeConstStacked(selector, property, args){
  530. // document.dispatchEvent(new CustomEvent("userscript-call-dom-const-stacked", { detail: { selector, property, args } }))
  531. // }
  532.  
  533. function isCommentsK(ytdFlexyElm){
  534. return (ytdFlexyElm.getAttribute('tabview-youtube-comments')||'').indexOf('K')>=0
  535.  
  536. }
  537.  
  538. function akAttr(/** @type {HTMLElement} */ cssElm, /** @type {String} */ attrName, /** @type {boolean} */ isNegative, /** @type {string | any} */ flag) {
  539. // isNegative => incomplete loading
  540.  
  541. let u = parseInt(cssElm.getAttribute(attrName) || 0) || 0;
  542. let ak = Math.abs(u);
  543.  
  544. if (ak > 100 && isNegative && u < 0) {
  545.  
  546. } else if (ak > 100 && !isNegative && u > 0) {
  547.  
  548. } else {
  549. if (ak <= 100) {
  550. ak = 101;
  551. } else {
  552. ak++;
  553. if (ak >= 800) ak = 101;
  554. }
  555. // 101, 102, ... 799, 101
  556. }
  557.  
  558. cssElm.setAttribute(attrName, `${ isNegative ? -ak : ak }${ flag || '' }`)
  559. }
  560.  
  561.  
  562.  
  563. let timeout_resize_for_layout_change = new Timeout();
  564.  
  565. function dispatchWindowResize(){
  566. // for youtube to detect layout resize for adjusting Player tools
  567. return window.dispatchEvent(new Event('resize'));
  568. }
  569.  
  570.  
  571.  
  572. function layoutStatusChanged(/** @type {number} */ old_layoutStatus, /** @type {number} */ new_layoutStatus) {
  573.  
  574.  
  575. if (old_layoutStatus === new_layoutStatus) return;
  576.  
  577. const cssElm = kRef(ytdFlexy);
  578.  
  579. if (!cssElm) return;
  580.  
  581. const BF_TWOCOL_N_THEATER = LAYOUT_TWO_COLUMNS|LAYOUT_THEATER
  582.  
  583. let new_isExpandedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_EXPANDED)
  584.  
  585. let new_isTabExpanded = !!(new_layoutStatus & LAYOUT_TAB_EXPANDED);
  586. let new_isFullScreen = !!(new_layoutStatus & LAYOUT_FULLSCREEN);
  587. let new_isExpandEPanel = !!(new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  588.  
  589.  
  590.  
  591. function showTabOrChat() {
  592.  
  593. layoutStatusMutex.lockWith(unlock => {
  594.  
  595. if (lstTab.lastPanel == '#chatroom') {
  596.  
  597. if (new_isTabExpanded) switchTabActivity(null)
  598. if (!new_isExpandedChat) ytBtnExpandChat();
  599.  
  600. } else if (lstTab.lastPanel && lstTab.lastPanel.indexOf('#engagement-panel-') == 0) {
  601.  
  602. if (new_isTabExpanded) switchTabActivity(null)
  603. if (!new_isExpandEPanel) ytBtnOpenEngagementPanel(lstTab.lastPanel);
  604.  
  605. } else {
  606.  
  607. if (new_isExpandedChat) ytBtnCollapseChat()
  608. if (!new_isTabExpanded) {setToActiveTab();}
  609.  
  610. }
  611.  
  612. timeline.setTimeout(unlock, 40);
  613.  
  614. })
  615. }
  616.  
  617. function hideTabAndChat() {
  618.  
  619. layoutStatusMutex.lockWith(unlock => {
  620.  
  621. if (new_isTabExpanded) switchTabActivity(null)
  622. if (new_isExpandedChat) ytBtnCollapseChat()
  623. if (new_isExpandEPanel) ytBtnCloseEngagementPanels();
  624.  
  625.  
  626. timeline.setTimeout(unlock, 40);
  627.  
  628. })
  629.  
  630. }
  631.  
  632. const statusCollaspedFalse = !!(new_layoutStatus & (LAYOUT_TAB_EXPANDED|LAYOUT_ENGAGEMENT_PANEL_EXPAND|LAYOUT_CHATROOM_EXPANDED))
  633. const statusCollaspedTrue = !statusCollaspedFalse
  634.  
  635. let changes = (old_layoutStatus & LAYOUT_VAILD) ? old_layoutStatus ^ new_layoutStatus : 0;
  636.  
  637. let chat_collasped_changed = !!(changes & LAYOUT_CHATROOM_COLLASPED)
  638. let chat_expanded_changed = !!(changes & LAYOUT_CHATROOM_EXPANDED)
  639. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  640. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  641. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  642. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  643. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  644.  
  645. let BF_LayoutCh_Panel = (changes & (LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM_EXPANDED|LAYOUT_ENGAGEMENT_PANEL_EXPAND))
  646. let tab_change = BF_LayoutCh_Panel;
  647. let isChatOrTabExpandTriggering = !!((new_layoutStatus) & BF_LayoutCh_Panel);
  648. let isChatOrTabCollaspeTriggering = !!((~new_layoutStatus) & BF_LayoutCh_Panel);
  649.  
  650.  
  651.  
  652. let moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandEPanel) > 1
  653.  
  654. let requestVideoResize = false;
  655.  
  656. // two column; not theater; tab collapse; chat expand; ep expand
  657. const IF_01a = LAYOUT_TWO_COLUMNS|LAYOUT_THEATER|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|LAYOUT_CHATROOM_COLLASPED|LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  658. const IF_01b = LAYOUT_TWO_COLUMNS|0|0|LAYOUT_CHATROOM|0|LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  659.  
  660. // two column; not theater;
  661. const IF_02a = BF_TWOCOL_N_THEATER;
  662. const IF_02b = LAYOUT_TWO_COLUMNS;
  663.  
  664. // two column; not theater; tab expand; chat expand;
  665. const IF_03a = LAYOUT_TWO_COLUMNS|LAYOUT_THEATER|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|LAYOUT_CHATROOM_COLLASPED;
  666. const IF_03b = LAYOUT_TWO_COLUMNS|0|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|0;
  667.  
  668. // two column; tab expand; chat expand;
  669. const IF_06a = LAYOUT_TWO_COLUMNS|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|LAYOUT_CHATROOM_COLLASPED;
  670. const IF_06b = LAYOUT_TWO_COLUMNS|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|0;
  671.  
  672. // two column; theater;
  673. const IF_04a = BF_TWOCOL_N_THEATER;
  674. const IF_04b = BF_TWOCOL_N_THEATER;
  675.  
  676. // not fullscreen; two column; not theater; not tab expand; not EP expand; not expand chat
  677. const IF_05a = LAYOUT_FULLSCREEN|LAYOUT_TWO_COLUMNS|LAYOUT_THEATER|LAYOUT_TAB_EXPANDED|LAYOUT_ENGAGEMENT_PANEL_EXPAND|LAYOUT_CHATROOM_EXPANDED;
  678. const IF_05b = 0|LAYOUT_TWO_COLUMNS|0|0|0|0;
  679.  
  680.  
  681.  
  682. if(new_isFullScreen){
  683.  
  684.  
  685. if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_06a) === IF_06b && statusCollaspedFalse && !column_mode_changed) {
  686.  
  687. // two column; tab expand; chat expand;
  688. switchTabActivity(null);
  689.  
  690.  
  691. }
  692. if( !!(tab_change & LAYOUT_CHATROOM_EXPANDED) && new_isExpandedChat ){
  693. //tab_change = LAYOUT_CHATROOM_EXPANDED
  694. //tab_change = LAYOUT_CHATROOM_EXPANDED|LAYOUT_TAB_EXPANDED
  695. timeline.setTimeout(() => {
  696. let scrollElement = document.querySelector('ytd-app[scrolling]')
  697. if(!scrollElement) return;
  698. // single column view; click button; scroll to tab content area 100%
  699. let chatFrame = document.querySelector('ytd-live-chat-frame#chat');
  700. if (chatFrame && isChatExpand()) {
  701. _console.log(7290,1)
  702. chatFrame.scrollIntoView(true);
  703. }
  704. }, 60)
  705.  
  706. }
  707.  
  708.  
  709.  
  710. if (!!(tab_change & LAYOUT_ENGAGEMENT_PANEL_EXPAND) && new_isExpandEPanel) {
  711. timeline.setTimeout(() => {
  712. let scrollElement = document.querySelector('ytd-app[scrolling]')
  713. if(!scrollElement) return;
  714. // single column view; click button; scroll to tab content area 100%
  715. let epPanel = document.querySelector('ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]');
  716. if (epPanel ) {
  717. _console.log(7290,2)
  718.  
  719. let pi=50;
  720. let cid = setInterval(()=>{
  721. if(--pi) epPanel.scrollIntoView(true); else clearInterval(cid)
  722. },17)
  723. //
  724. }
  725. }, 60)
  726.  
  727. }
  728.  
  729.  
  730.  
  731. }else if (fullscreen_mode_changed) {
  732.  
  733.  
  734.  
  735. if( !new_isFullScreen && statusCollaspedTrue && isWideScreenWithTwoColumns() && !isTheater()){
  736. showTabOrChat();
  737. requestVideoResize = true;
  738. } else if( !new_isFullScreen && statusCollaspedFalse && isWideScreenWithTwoColumns() && isTheater()){
  739. ytBtnCancelTheater();
  740. requestVideoResize = true;
  741. }
  742.  
  743.  
  744.  
  745. } else if((new_layoutStatus & IF_01a) === IF_01b && !column_mode_changed && (tab_change==LAYOUT_CHATROOM_EXPANDED || tab_change==LAYOUT_ENGAGEMENT_PANEL_EXPAND) ){
  746.  
  747. // two column; not theater; tab collapse; chat expand; ep expand
  748.  
  749.  
  750.  
  751. if(epanel_expanded_changed){
  752. layoutStatusMutex.lockWith(unlock => {
  753. ytBtnCollapseChat();
  754. setTimeout(unlock,13)
  755. })
  756. }else if(chat_collasped_changed){
  757. layoutStatusMutex.lockWith(unlock => {
  758. ytBtnCloseEngagementPanels();
  759. setTimeout(unlock,13)
  760. })
  761.  
  762. }
  763.  
  764. } else if (!tab_change && column_mode_changed && (new_layoutStatus & IF_02a) === IF_02b && moreThanOneShown) {
  765.  
  766. // two column; not theater;
  767. // moreThanOneShown
  768.  
  769. showTabOrChat();
  770. requestVideoResize = true;
  771.  
  772. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_03a) === IF_03b && statusCollaspedFalse && !column_mode_changed) {
  773.  
  774. // two column; not theater; tab expand; chat expand;
  775.  
  776. switchTabActivity(null);
  777. requestVideoResize = true;
  778.  
  779. } else if (isChatOrTabExpandTriggering && (new_layoutStatus & IF_04a) === IF_04b && statusCollaspedFalse && (changes & BF_TWOCOL_N_THEATER) === 0 ) {
  780.  
  781. ytBtnCancelTheater();
  782. requestVideoResize = true;
  783.  
  784. } else if ((new_layoutStatus & IF_04a) === IF_04b && statusCollaspedFalse) {
  785.  
  786. hideTabAndChat();
  787. requestVideoResize = true;
  788.  
  789. } else if (isChatOrTabCollaspeTriggering && (new_layoutStatus & IF_02a) === IF_02b && statusCollaspedTrue && !column_mode_changed) {
  790.  
  791. if(tab_change==LAYOUT_ENGAGEMENT_PANEL_EXPAND){
  792.  
  793. lstTab.lastPanel = null;
  794.  
  795. if(new_isFullScreen){
  796.  
  797. }else{
  798. showTabOrChat();
  799. }
  800. }else if(tab_change==LAYOUT_CHATROOM_EXPANDED){
  801.  
  802. lstTab.lastPanel = null;
  803.  
  804. if(new_isFullScreen){
  805.  
  806. }else{
  807. showTabOrChat();
  808. }
  809. }else{
  810.  
  811. if(new_isFullScreen){
  812.  
  813. }else{
  814.  
  815. ytBtnSetTheater();
  816.  
  817. }
  818.  
  819. }
  820.  
  821. requestVideoResize = true;
  822.  
  823. } else if (!tab_change && !!(changes & BF_TWOCOL_N_THEATER) && (new_layoutStatus & IF_02a) === IF_02b && statusCollaspedTrue) {
  824.  
  825. showTabOrChat();
  826. requestVideoResize = true;
  827.  
  828. } else if ( (new_layoutStatus & IF_05a) === IF_05b ) {
  829. // bug fix for restoring from mini player
  830.  
  831. layoutStatusMutex.lockWith(unlock => {
  832. setToActiveTab();
  833. timeline.setTimeout(unlock, 40);
  834. })
  835.  
  836. requestVideoResize = true;
  837.  
  838. } else if (tab_expanded_changed) {
  839.  
  840. requestVideoResize = true;
  841.  
  842. }
  843.  
  844.  
  845.  
  846.  
  847.  
  848. if (requestVideoResize) {
  849.  
  850. timeout_resize_for_layout_change.clear();
  851. timeout_resize_for_layout_change.set(() => {
  852. dispatchWindowResize();
  853. }, 92)
  854.  
  855. }
  856.  
  857.  
  858.  
  859.  
  860. }
  861.  
  862. function fixLayoutStatus(x){
  863.  
  864.  
  865. const new_isExpandedChat = !(x & LAYOUT_CHATROOM_COLLASPED) && (x & LAYOUT_CHATROOM)
  866.  
  867. return new_isExpandedChat ? (x | LAYOUT_CHATROOM_EXPANDED) : (x & ~LAYOUT_CHATROOM_EXPANDED);
  868.  
  869. }
  870.  
  871.  
  872. const wls = new Proxy({
  873. /** @type {number | null} */
  874. layoutStatus:undefined
  875. }, {
  876. get: function(target, prop) {
  877. return target[prop];
  878. },
  879. set: function(target, prop, value) {
  880. if(prop=='layoutStatus'){
  881. if (value === 0) {
  882. target[prop] = value;
  883. return;
  884. }else if(target[prop]===value){
  885. return;
  886. }else{
  887. if (!target.layoutStatus_pending) {
  888. target.layoutStatus_pending = true;
  889. const old_layoutStatus = target[prop];
  890. target[prop] = value;
  891. layoutStatusMutex.lockWith(unlock => {
  892. target.layoutStatus_pending = false;
  893. layoutStatusChanged((old_layoutStatus), (target[prop]));
  894. timeline.setTimeout(unlock, 40)
  895. })
  896. return;
  897. }
  898. }
  899. }
  900. target[prop] = value;
  901. },
  902. has: function(target, prop) {
  903. return (prop in target);
  904. }
  905. });
  906.  
  907.  
  908.  
  909.  
  910.  
  911.  
  912. const svgElm = (w, h, vw, vh, p) => `<svg width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`
  913.  
  914. let settings = {
  915. defaultTab: "#tab-videos"
  916. };
  917.  
  918.  
  919.  
  920. function isVideoPlaying(video) {
  921. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  922. }
  923.  
  924. function wAttr(elm, attr, kv) {
  925. if (!elm || kv === null) {} else if (kv === true) { elm.setAttribute(attr, '') } else if (kv === false) { elm.removeAttribute(attr) } else if (typeof kv == 'string') { elm.setAttribute(attr, kv) }
  926. }
  927.  
  928. function hideTabBtn(tabBtn) {
  929. //console.log('hideTabBtn', tabBtn)
  930. let isActiveBefore = tabBtn.classList.contains('active');
  931. tabBtn.classList.add("tab-btn-hidden");
  932. if (isActiveBefore) {
  933. setToActiveTab();
  934. }
  935. }
  936.  
  937. function hasAttribute(obj, key) {
  938. return obj && obj.hasAttribute(key);
  939. }
  940.  
  941. function isTheater() {
  942. const cssElm = kRef(ytdFlexy);
  943. return (cssElm && cssElm.hasAttribute('theater'))
  944. }
  945.  
  946. function isFullScreen() {
  947. const cssElm = kRef(ytdFlexy);
  948. return (cssElm && cssElm.hasAttribute('fullscreen'))
  949. }
  950.  
  951. function isChatExpand() {
  952. const cssElm = kRef(ytdFlexy);
  953. return cssElm && cssElm.hasAttribute('userscript-chatblock') && !cssElm.hasAttribute('userscript-chat-collapsed')
  954. }
  955.  
  956. function isWideScreenWithTwoColumns() {
  957. const cssElm = kRef(ytdFlexy);
  958. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  959. }
  960.  
  961. function isAnyActiveTab() {
  962. return $('#right-tabs .tab-btn.active').length > 0
  963. }
  964.  
  965. function isEngagementPanelExpanded() { //note: not checking the visual elements
  966. const cssElm = kRef(ytdFlexy);
  967. return (cssElm && +cssElm.getAttribute('userscript-engagement-panel') > 0)
  968. }
  969.  
  970. function engagement_panels_() {
  971.  
  972. let res = [];
  973. let shownRes = [];
  974.  
  975. let v = 0,
  976. k = 1,
  977. count = 0;
  978. for (const ePanel of document.querySelectorAll(
  979. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  980. )) {
  981.  
  982. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  983.  
  984. switch (visibility) {
  985. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED':
  986. v |= k;
  987. count++;
  988. shownRes.push(ePanel)
  989. res.push({ ePanel, k, visible: true });
  990. break;
  991. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN':
  992. res.push({ ePanel, k, visible: false });
  993. break;
  994. default:
  995. res.push({ ePanel, k, visible: false });
  996. }
  997.  
  998. k = k << 1;
  999.  
  1000. }
  1001. return { list: res, value: v, count: count, shownRes };
  1002. }
  1003.  
  1004.  
  1005. function ytBtnOpenEngagementPanel(/** @type {number | string} */ panel_id) {
  1006.  
  1007. if (typeof panel_id == 'string') {
  1008. panel_id = panel_id.replace('#engagement-panel-', '');
  1009. panel_id = parseInt(panel_id);
  1010. }
  1011. if (panel_id >= 0) {} else return false;
  1012.  
  1013. let panels = engagement_panels_();
  1014.  
  1015. for (const { ePanel, k, visible } of panels.list) {
  1016. if ((panel_id & k) === k) {
  1017. if (!visible) ePanel.setAttribute('visibility', "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED");
  1018. } else {
  1019. if (visible) ytBtnCloseEngagementPanel(ePanel);
  1020. }
  1021. }
  1022.  
  1023. }
  1024.  
  1025. function ytBtnCloseEngagementPanel(/** @type {HTMLElement} */ s) {
  1026. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  1027. let button = querySelectorFromAnchor.call(s,'ytd-watch-flexy ytd-engagement-panel-title-header-renderer #header > #visibility-button');
  1028.  
  1029. if (button){
  1030. button =
  1031. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1032. querySelectorFromAnchor.call(button, 'ytd-button-renderer');
  1033. if(button) button.click();
  1034. }
  1035.  
  1036. }
  1037.  
  1038. function ytBtnCloseEngagementPanels() {
  1039. if (isEngagementPanelExpanded()) {
  1040. for (const s of document.querySelectorAll(
  1041. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  1042. )) {
  1043. if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  1044. }
  1045. }
  1046. }
  1047.  
  1048. function ytBtnSetTheater() {
  1049. if (!isTheater()) {
  1050. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  1051. if (sizeBtn) sizeBtn.click();
  1052. }
  1053. }
  1054.  
  1055. function ytBtnCancelTheater() {
  1056. if (isTheater()) {
  1057. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  1058. if (sizeBtn) sizeBtn.click();
  1059. }
  1060. }
  1061.  
  1062. function ytBtnExpandChat() {
  1063. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button')
  1064. if (button){
  1065. button =
  1066. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1067. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  1068. if(button) button.click();
  1069. }
  1070. }
  1071.  
  1072. function ytBtnCollapseChat() {
  1073. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button')
  1074. if (button){
  1075. button =
  1076. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1077. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  1078. if(button) button.click();
  1079. }
  1080. }
  1081.  
  1082.  
  1083. const Q = {}
  1084.  
  1085.  
  1086.  
  1087. function chatFrameContentDocument() {
  1088. // non-null if iframe exist && contentDocument && readyState = complete
  1089. /** @type {HTMLIFrameElement | null} */
  1090. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  1091. if (!iframe) return null; //iframe must be there
  1092. /** @type {Document | null} */
  1093. let cDoc = null;
  1094. try {
  1095. cDoc = iframe.contentDocument;
  1096. } catch (e) {}
  1097. if (!cDoc) return null;
  1098. if (cDoc.readyState != 'complete') return null; //we must wait for its completion
  1099.  
  1100. return cDoc;
  1101.  
  1102. }
  1103.  
  1104. function chatFrameElement(/** @type {string} */ cssSelector) {
  1105. let cDoc = chatFrameContentDocument();
  1106. if (!cDoc) return null;
  1107. /** @type {HTMLElement | null} */
  1108. let elm = null;
  1109. try {
  1110. elm = cDoc.querySelector(cssSelector)
  1111. } catch (e) {
  1112. console.log('iframe error', e)
  1113. }
  1114. return elm;
  1115. }
  1116.  
  1117.  
  1118.  
  1119.  
  1120. function fixTabs() {
  1121.  
  1122.  
  1123. if (!scriptEnable) return;
  1124.  
  1125.  
  1126. let queryElement = document.querySelector('*:not(#tab-videos) > #related:not([non-placeholder-videos]) > ytd-watch-next-secondary-results-renderer')
  1127.  
  1128. let isRelocated = !!queryElement;
  1129.  
  1130.  
  1131.  
  1132. if (isRelocated) {
  1133.  
  1134. let relocatedRelated = closestDOM.call(queryElement, '#related'); // NOT NULL
  1135.  
  1136. let right_tabs = document.querySelector('#right-tabs');
  1137. let tab_videos = querySelectorFromAnchor.call(right_tabs,"#tab-videos");
  1138.  
  1139. if (!right_tabs || !tab_videos) return;
  1140.  
  1141. for (const s of querySelectorAllFromAnchor.call(relocatedRelated,'#related')) {
  1142. s.setAttribute('non-placeholder-videos', '')
  1143. }
  1144.  
  1145. let target_container = document.querySelector('ytd-watch-flexy:not([is-two-columns_]) #primary-inner, ytd-watch-flexy[is-two-columns_] #secondary-inner')
  1146.  
  1147. if (target_container) target_container.append(right_tabs) // last-child
  1148.  
  1149.  
  1150. let videos_related = relocatedRelated; // NOT NULL
  1151. $('[placeholder-videos]').removeAttr('placeholder-videos');
  1152. $('[placeholder-for-youtube-play-next-queue]').removeAttr('placeholder-for-youtube-play-next-queue');
  1153.  
  1154. tab_videos.appendChild(videos_related);
  1155. let videos_results_renderer = querySelectorFromAnchor.call(relocatedRelated,"ytd-watch-next-secondary-results-renderer");
  1156. if (videos_results_renderer) videos_results_renderer.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal);
  1157. videos_related.setAttribute('placeholder-for-youtube-play-next-queue', '')
  1158. videos_related.setAttribute('placeholder-videos', '')
  1159.  
  1160. $('[placeholder-videos]').on("scroll", windowScroll);
  1161.  
  1162.  
  1163.  
  1164.  
  1165. }
  1166.  
  1167.  
  1168.  
  1169. /** @type {HTMLElement | null} */
  1170. let chatroom = null;
  1171. if (chatroom = document.querySelector('*:not([data-positioner="before|#chat"]) + ytd-live-chat-frame#chat, ytd-live-chat-frame#chat:first-child')) {
  1172.  
  1173. let positioner = document.querySelector('tabview-youtube-positioner[data-positioner="before|#chat"]');
  1174. if (positioner) positioner.remove();
  1175.  
  1176.  
  1177. if (document.querySelector('.YouTubeLiveFilledUpView')) {
  1178. // no relocation
  1179. } else {
  1180.  
  1181. $(chatroom).insertBefore('#right-tabs')
  1182.  
  1183. }
  1184.  
  1185.  
  1186. $(positioner ? positioner : document.createElement('tabview-youtube-positioner')).attr('data-positioner', 'before|#chat').insertBefore(chatroom)
  1187.  
  1188.  
  1189.  
  1190. }
  1191.  
  1192.  
  1193. }
  1194.  
  1195. function handlerAutoCompleteExist() {
  1196.  
  1197.  
  1198. /** @type {HTMLElement} */
  1199. let autoComplete = this;
  1200.  
  1201. autoComplete.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  1202.  
  1203. let domId = autoComplete.getAttribute('data-autocomplete-input-id')
  1204. let searchBox = autoComplete.ownerDocument.querySelector(`[data-autocomplete-results-id="${domId}"]`)
  1205.  
  1206. if (!domId || !searchBox) return;
  1207.  
  1208. let positioner = searchBox.nextSibling;
  1209. if (positioner && positioner.nodeName.toLowerCase() == "autocomplete-positioner") {} else if (positioner && positioner.nodeName.toLowerCase() != "autocomplete-positioner") {
  1210. $(positioner = document.createElement("autocomplete-positioner")).insertAfter(searchBox);
  1211. } else {
  1212. $(positioner = document.createElement("autocomplete-positioner")).prependTo(searchBox.parentNode);
  1213. }
  1214. $(autoComplete).prependTo(positioner);
  1215.  
  1216. positioner.style.setProperty('--sb-margin-bottom', getComputedStyle(searchBox).marginBottom)
  1217. positioner.style.setProperty('--height', searchBox.offsetHeight + 'px')
  1218.  
  1219. }
  1220.  
  1221. function mtf_fixAutoCompletePosition(/** @type {HTMLElement} */ elmAutoComplete) {
  1222.  
  1223.  
  1224. elmAutoComplete.setAttribute('autocomplete-disable-updatesc', '')
  1225. elmAutoComplete.addEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  1226.  
  1227. let tf = ()=>{
  1228. if(!document.documentElement.hasAttribute('tabview-injection-js-1-ready')) return setTimeout(tf, 300);
  1229. document.dispatchEvent(new CustomEvent('tabview-fix-autocomplete'))
  1230. }
  1231. tf();
  1232. //script_inject_facp.inject();
  1233.  
  1234. }
  1235.  
  1236. function mtf_AfterFixTabs() {
  1237.  
  1238.  
  1239. /** @type {HTMLElement | null} */
  1240. let ytdFlexyElm = kRef(ytdFlexy);
  1241. if (!scriptEnable || !ytdFlexyElm) return;
  1242.  
  1243. /** @type {HTMLElement | null} */
  1244. const rootElement = ytdFlexyElm;
  1245.  
  1246.  
  1247.  
  1248. const autocomplete = querySelectorFromAnchor.call(rootElement,'[placeholder-for-youtube-play-next-queue] input#suggestions-search + autocomplete-positioner > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])')
  1249.  
  1250. if (autocomplete) {
  1251.  
  1252. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  1253.  
  1254.  
  1255. if (searchBox) {
  1256.  
  1257.  
  1258. autocomplete.parentNode.setAttribute('position-fixed-by-tabview-youtube', '');
  1259. autocomplete.setAttribute('position-fixed-by-tabview-youtube', '');
  1260. autocomplete.setAttribute('userscript-scrollbar-render', '')
  1261.  
  1262. if (!searchBox.hasAttribute('is-set-click-to-toggle')) {
  1263. searchBox.setAttribute('is-set-click-to-toggle', '')
  1264. searchBox.addEventListener('click', function() {
  1265.  
  1266.  
  1267. setTimeout(() => {
  1268. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  1269.  
  1270. if (!autocomplete) return;
  1271.  
  1272. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0;
  1273.  
  1274. if (isNotEmpty) {
  1275.  
  1276. let elmVisible = isDOMVisible(autocomplete)
  1277.  
  1278. if (elmVisible) $(autocomplete).hide();
  1279. else $(autocomplete).show();
  1280.  
  1281. }
  1282.  
  1283. }, 20);
  1284.  
  1285. })
  1286.  
  1287. let timeoutOnce_searchbox_keyup = new Timeout();
  1288. searchBox.addEventListener('keyup', function() {
  1289.  
  1290. timeoutOnce_searchbox_keyup.set(() => {
  1291.  
  1292. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  1293.  
  1294. if (!autocomplete) return;
  1295.  
  1296.  
  1297. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0
  1298.  
  1299. if (isNotEmpty) {
  1300.  
  1301. let elmVisible = isDOMVisible(autocomplete)
  1302.  
  1303. if (!elmVisible) $(autocomplete).show();
  1304.  
  1305. }
  1306.  
  1307. }, 20);
  1308.  
  1309. })
  1310.  
  1311. }
  1312.  
  1313.  
  1314.  
  1315. }
  1316.  
  1317. }
  1318.  
  1319.  
  1320.  
  1321.  
  1322. let currentLastVideo = querySelectorFromAnchor.call(rootElement,'[placeholder-videos] #items ytd-compact-video-renderer:last-of-type')
  1323. let prevLastVideo = kRef(_cachedLastVideo);
  1324.  
  1325. if (prevLastVideo !== currentLastVideo && currentLastVideo) {
  1326. _cachedLastVideo = mWeakRef(currentLastVideo);
  1327. }
  1328.  
  1329. if (prevLastVideo !== currentLastVideo && currentLastVideo && prevLastVideo) {
  1330.  
  1331. let isPrevRemoved = !prevLastVideo.parentNode
  1332.  
  1333.  
  1334. function getVideoListHash() {
  1335.  
  1336. let res = [...document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')].map(renderer => {
  1337. return querySelectorFromAnchor.call(renderer,'a[href*="watch"][href*="v="]').getAttribute('href')
  1338.  
  1339. }).join('|')
  1340. // /watch?v=XXXXX|/watch?v=XXXXXX|/watch?v=XXXXXX
  1341.  
  1342. // alternative - DOM.data.videoId
  1343. // let elms = document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')
  1344. // let res = [...elms].map(elm=>elm.data.videoId||'').join('|') ;
  1345.  
  1346. if (res.indexOf('||') >= 0) {
  1347. res = '';
  1348. }
  1349.  
  1350. return res ? res : null;
  1351. }
  1352.  
  1353. if (isPrevRemoved) {
  1354.  
  1355. // this is the replacement of videos instead of addition
  1356.  
  1357. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  1358.  
  1359. let currentPlayListHash = getVideoListHash() || null;
  1360.  
  1361. if (!currentPlayListHash) {
  1362.  
  1363. } else if (!videoListBeforeSearch && searchBox) {
  1364.  
  1365. videoListBeforeSearch = currentPlayListHash;
  1366. if (videoListBeforeSearch) {
  1367. //console.log('fromSearch', videoListBeforeSearch)
  1368.  
  1369. requestAnimationFrame(function() {
  1370.  
  1371. let renderer = document.querySelector('[placeholder-videos] ytd-watch-next-secondary-results-renderer');
  1372. if (searchBox && searchBox.parentNode) searchBox.blur();
  1373.  
  1374. if (renderer) {
  1375. let scrollParent = renderer.parentNode;
  1376. if (scrollParent.scrollHeight > scrollParent.offsetHeight) {
  1377. let targetTop = renderer.offsetTop;
  1378. if (searchBox && searchBox.parentNode == scrollParent) targetTop -= searchBox.offsetHeight
  1379. scrollParent.scrollTop = targetTop - scrollParent.firstChild.offsetTop;
  1380. }
  1381. }
  1382.  
  1383. });
  1384.  
  1385. }
  1386.  
  1387. } else if (videoListBeforeSearch) {
  1388.  
  1389. if (currentPlayListHash != videoListBeforeSearch) {
  1390.  
  1391. videoListBeforeSearch = null;
  1392. //console.log('fromSearch', videoListBeforeSearch)
  1393.  
  1394.  
  1395. }
  1396.  
  1397. }
  1398.  
  1399.  
  1400. }
  1401.  
  1402.  
  1403. }
  1404.  
  1405.  
  1406.  
  1407.  
  1408. }
  1409.  
  1410. function base_ChatExist() {
  1411.  
  1412. let ytdFlexyElm = kRef(ytdFlexy);
  1413. if (!scriptEnable || !ytdFlexyElm) return null;
  1414.  
  1415. // no mutation triggering if the changes are inside the iframe
  1416.  
  1417. // 1) Detection of #continuations inside iframe
  1418. // iframe ownerDocument is accessible due to same origin
  1419. // if the chatroom is collasped, no determination of live chat or replay (as no #continuations and somehow a blank iframe doc)
  1420.  
  1421. // 2) Detection of meta tag
  1422. // This is fastest but not reliable. It is somehow a bug that the navigation might not update the meta tag content
  1423.  
  1424. // 3) Detection of HTMLElement inside video player for live video
  1425.  
  1426. // (1)+(3) = solution
  1427.  
  1428. let attr_chatblock = null
  1429. let attr_chatcollapsed = null;
  1430.  
  1431. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  1432. let elmCont = null;
  1433. if (elmChat) {
  1434. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  1435.  
  1436.  
  1437. let s = 0;
  1438. if (elmCont) {
  1439. //not found if it is collasped.
  1440. s |= querySelectorFromAnchor.call(elmCont,'yt-timed-continuation') ? 1 : 0;
  1441. s |= querySelectorFromAnchor.call(elmCont,'yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0;
  1442. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  1443. if (s == 1) {
  1444. attr_chatblock = 'chat-live';
  1445. }else if (s == 2) attr_chatblock = 'chat-playback';
  1446.  
  1447. if (s == 1) $("span#tab3-txt-loader").text('');
  1448.  
  1449. } else if (!ytdFlexyElm.hasAttribute('userscript-chatblock')) {
  1450. // live chat frame but type not known
  1451. attr_chatblock = '';
  1452.  
  1453. }
  1454. //keep unknown as original
  1455. let isCollapsed = !!elmChat.hasAttribute('collapsed');
  1456. attr_chatcollapsed = isCollapsed;
  1457.  
  1458. } else {
  1459. attr_chatblock = false;
  1460. attr_chatcollapsed = false;
  1461.  
  1462. }
  1463.  
  1464. return { attr_chatblock, attr_chatcollapsed }
  1465.  
  1466. }
  1467.  
  1468.  
  1469.  
  1470.  
  1471.  
  1472.  
  1473. let t_heated_BodyScroll = 0;
  1474.  
  1475. function windowScroll() {
  1476. let ct = Date.now();
  1477. if (ct - t_heated_BodyScroll < 6) return; // avoid duplicate calling
  1478. t_heated_BodyScroll = ct;
  1479. window.dispatchEvent(new Event("scroll")); // dispatch Scroll Event to Window for content display
  1480. }
  1481.  
  1482. /* items - > special case (2022/11/09) */
  1483.  
  1484.  
  1485. // continuous check for element relocation
  1486. function mtf_append_comments() {
  1487. /** @type {HTMLElement | null} */
  1488. let ytdFlexyElm = kRef(ytdFlexy);
  1489. if (!scriptEnable || !ytdFlexyElm) return;
  1490.  
  1491. /** @type {HTMLElement | null} */
  1492. const rootElement = ytdFlexyElm;
  1493.  
  1494. let comments = querySelectorFromAnchorFizzy(rootElement,'comments', '#primary ytd-watch-metadata ~ ytd-comments#comments');
  1495. if (comments) {
  1496. _console.log(3202)
  1497. $(comments).appendTo('#tab-comments').attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  1498. }
  1499. }
  1500.  
  1501. // continuous check for element relocation
  1502. function mtf_liveChatBtnF() {
  1503. /** @type {HTMLElement | null} */
  1504. let ytdFlexyElm = kRef(ytdFlexy);
  1505. if (!scriptEnable || !ytdFlexyElm) return;
  1506.  
  1507. /** @type {HTMLElement | null} */
  1508. const rootElement = ytdFlexyElm;
  1509.  
  1510. let button = querySelectorFromAnchor.call(rootElement,'ytd-live-chat-frame#chat > .ytd-live-chat-frame#show-hide-button:nth-child(n+2)');
  1511. if (button){
  1512. let parentNode = closestDOM.call(button, 'ytd-live-chat-frame#chat');
  1513. if (!parentNode){
  1514. console.log('parentNode failed')
  1515. } else if( HTMLElement.prototype.prepend ){
  1516. // using prepend
  1517. HTMLElement.prototype.prepend.call(parentNode, button)
  1518.  
  1519. } else{
  1520. // using insertBefore
  1521. try{
  1522. elementInsertBefore.call(parentNode, button, parentNode.firstChild);
  1523. }catch(e){
  1524. console.log('element insert failed in old browser CE')
  1525. }
  1526. }
  1527. }
  1528. //if (button) button.parentNode.insertBefore(button, button.parentNode.firstChild)
  1529. }
  1530.  
  1531.  
  1532.  
  1533. // continuous check for element relocation
  1534. // fired at begining & window resize, etc
  1535. function mtf_append_playlist(playlist) {
  1536.  
  1537. /** @type {HTMLElement | null} */
  1538. let ytdFlexyElm = kRef(ytdFlexy);
  1539. if (!scriptEnable || !ytdFlexyElm || !playlist) return;
  1540.  
  1541. let ple1 = querySelectorFromAnchor.call(playlist, "*:not(#ytd-userscript-playlist) > ytd-playlist-panel-renderer#playlist:not(.ytd-miniplayer) #items.ytd-playlist-panel-renderer:not(:empty)");
  1542.  
  1543. if (ple1) {
  1544.  
  1545. let ct = Date.now();
  1546.  
  1547. let truePlaylist = closestDOM.call(ple1, 'ytd-playlist-panel-renderer#playlist');
  1548. if(!truePlaylist || truePlaylist.nodeType!==1) truePlaylist = null;
  1549. else {
  1550.  
  1551. let tab_list = document.querySelector("#tab-list");
  1552.  
  1553. if(!tab_list) return;
  1554.  
  1555. truePlaylist.setAttribute('tabview-true-playlist', ct)
  1556.  
  1557.  
  1558. for (const s of document.querySelectorAll(`ytd-playlist-panel-renderer#playlist:not([tabview-true-playlist="${ct}"])`)){
  1559. s.removeAttribute('tabview-true-playlist')
  1560. }
  1561. let $wrapper = getWrapper('ytd-userscript-playlist')
  1562. $wrapper.append(truePlaylist).appendTo(tab_list);
  1563. truePlaylist.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  1564. setDisplayedPlaylist(); // relocation after re-layout
  1565. requestAnimationFrame(() => {
  1566. let ytdFlexyElm = kRef(ytdFlexy);
  1567. if (!scriptEnable || !ytdFlexyElm) return;
  1568. if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tabview-selection') + '').indexOf('#tab-') === 0 && /https\:\/\/www\.youtube\.com\/watch.*[\?\&]list=[\w\-\_]+/.test(location.href)) {
  1569. if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  1570. }
  1571. })
  1572.  
  1573. }
  1574.  
  1575.  
  1576.  
  1577. }
  1578. }
  1579.  
  1580.  
  1581. // content fix - info & playlist
  1582. // fired at begining, and keep for in case any change
  1583. function mtf_fix_details() {
  1584.  
  1585. if (!scriptEnable) return;
  1586.  
  1587.  
  1588. 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])')
  1589.  
  1590. if(contentToggleBtn){
  1591.  
  1592. (()=> {
  1593. const domElement = contentToggleBtn;
  1594. contentToggleBtn = null;
  1595. // if(!domElement.parentElement) return; // not working in pseudo custom element - parentNode = documentFragment
  1596. const expander = closestDOM.call(domElement, 'ytd-watch-flexy #tab-info ytd-expander')
  1597.  
  1598. if(!expander || expander.nodeType!==1) return; // checking whether it is still on the page
  1599. if(expander.style.getPropertyValue('--ytd-expander-collapsed-height')){
  1600. expander.style.setProperty('--ytd-expander-collapsed-height','')
  1601. }
  1602. nativeCall(expander, [
  1603. {'property':'canToggleJobId','value':1}, // false disable calculateCanCollapse in childrenChanged
  1604. {'property':'alwaysToggleable','value':false}, // this is checked in childrenChanged
  1605. {'property':'recomputeOnResize','value':false}, // no need to check toggleable
  1606. {'property':'isToggled','value':true}, // show full content
  1607. {'property':'canToggle','value':false}, // hide show more or less btn
  1608. {'property':'collapsedHeight','value':999999} // disable collapsed height check
  1609. ])
  1610.  
  1611. })();
  1612. }
  1613.  
  1614. let strcturedInfo=document.querySelector('ytd-watch-flexy #tab-info ytd-structured-description-content-renderer.style-scope.ytd-video-secondary-info-renderer[hidden]')
  1615. if(strcturedInfo){
  1616.  
  1617. (()=>{
  1618.  
  1619. strcturedInfo.removeAttribute('hidden');
  1620. })();
  1621. }
  1622.  
  1623. /*
  1624. let inlineInfoExpander = document.querySelector('ytd-watch-flexy #description.ytd-watch-metadata ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata yt-formatted-string[split-lines].ytd-text-inline-expander');
  1625. let vsInfoExpander = document.querySelector('#tab-info ytd-expander.ytd-video-secondary-info-renderer #description.ytd-video-secondary-info-renderer yt-formatted-string[split-lines].ytd-video-secondary-info-renderer')
  1626. console.log(23234, inlineInfoExpander, vsInfoExpander, inlineInfoExpander.textContent, vsInfoExpander.textContent)
  1627. if(inlineInfoExpander && vsInfoExpander && inlineInfoExpander.textContent===vsInfoExpander.textContent){
  1628.  
  1629. inlineInfoExpander.classList.add('tabview-hidden-info')
  1630.  
  1631. }*/
  1632.  
  1633.  
  1634.  
  1635.  
  1636. // just in case the playlist is collapsed
  1637. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  1638. if(playlist && playlist.matches('[collapsed], [collapsible]')) {
  1639. (()=> {
  1640. const domElement = playlist;
  1641. playlist = null;
  1642. // if(!domElement.parentElement || domElement.nodeType!==1) return; // not working in pseudo custom element - parentNode = documentFragment
  1643. const tablist = closestDOM.call(domElement, 'ytd-watch-flexy #tab-list')
  1644.  
  1645. if(!tablist || tablist.nodeType!==1) return; // checking whether it is still on the page
  1646.  
  1647. if (domElement.hasAttribute('collapsed')) wAttr(domElement, 'collapsed', false);
  1648. if (domElement.hasAttribute('collapsible')) wAttr(domElement, 'collapsible', false);
  1649. })();
  1650. }
  1651.  
  1652.  
  1653.  
  1654. }
  1655.  
  1656.  
  1657.  
  1658. let mTime = Date.now()-152000000;
  1659. //let loadedComments = [];
  1660.  
  1661. const innerCommentsFuncs=[
  1662. // comments
  1663. function(){
  1664. _console.log(2907,1)
  1665.  
  1666. let span = document.querySelector("span#tab3-txt-loader")
  1667. let r = '0';
  1668. let txt = this.elm.textContent
  1669. if (typeof txt == 'string') {
  1670. let m = txt.match(/[\d\,\s]+/)
  1671. if (m) {
  1672. r = m[0].trim()
  1673. }
  1674. }
  1675. if (span){
  1676. let tab_btn = closestDOM.call(span,'.tab-btn[userscript-tab-content="#tab-comments"]')
  1677. if(tab_btn)tab_btn.setAttribute('loaded-comment','normal')
  1678. span.textContent = r;
  1679. }
  1680. setCommentSection(1);
  1681. },
  1682. // message
  1683. function(){
  1684. _console.log(2907,2)
  1685. let span = document.querySelector("span#tab3-txt-loader")
  1686. if (span){
  1687. let tab_btn = closestDOM.call(span,'.tab-btn[userscript-tab-content="#tab-comments"]')
  1688. if(tab_btn)tab_btn.setAttribute('loaded-comment','message')
  1689. span.textContent ='\u200B';
  1690. }
  1691. setCommentSection(1);
  1692. }
  1693. ]
  1694.  
  1695. /** @type {WeakMap<HTMLElement>} */
  1696. let loadedCommentsDT = new WeakMap();
  1697.  
  1698. let cmTime = 0;
  1699.  
  1700.  
  1701.  
  1702.  
  1703. function _innerCommentsLoader() {
  1704.  
  1705. /** @type {HTMLElement | null} */
  1706. let ytdFlexyElm = kRef(ytdFlexy);
  1707. if (!scriptEnable || !ytdFlexyElm) return;
  1708.  
  1709. _console.log(3434, pageType)
  1710. if (pageType!=='watch') return;
  1711.  
  1712.  
  1713.  
  1714. //console.log(823100,rootElement)
  1715.  
  1716. /** @type {Array<HTMLElement>} */
  1717. 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')]
  1718.  
  1719.  
  1720. let eTime = +`${Date.now()-mTime}00`;
  1721.  
  1722. let res = new Array(qmElms.length);
  1723. res.newFound = false;
  1724.  
  1725.  
  1726. let ci = 0;
  1727. let latest = -1;
  1728.  
  1729. let retrival = cmTime;
  1730. cmTime = eTime;
  1731. for(const qmElm of qmElms){
  1732.  
  1733. let mgz = 0
  1734. if (qmElm.id === 'count') {
  1735. //#count.ytd-comments-header-renderer
  1736. mgz = 1;
  1737. } else if ((qmElm.textContent || '').trim()) {
  1738. //ytd-message-renderer.ytd-item-section-renderer
  1739. mgz = 2;
  1740. // it is possible to get the message before the header generation.
  1741. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  1742. // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  1743. }
  1744.  
  1745. if(mgz>0){
  1746.  
  1747.  
  1748. let lastUpdate = loadedCommentsDT.get(qmElm)||0;
  1749. let diff = retrival-lastUpdate
  1750. _console.log(2907, diff)
  1751. let isNew = (diff>4 || diff<-4);
  1752. if(!isNew){
  1753. loadedCommentsDT.set(qmElm,eTime);
  1754. }else{
  1755. loadedCommentsDT.set(qmElm,eTime+1);
  1756. res.newFound = true;
  1757. latest = ci;
  1758. }
  1759. res[ci]={
  1760. status: mgz,
  1761. text: qmElm.textContent,
  1762. elm: qmElm,
  1763. isNew: isNew,
  1764. isLatest: false, //set afterwards
  1765. f: innerCommentsFuncs[mgz-1]
  1766. }
  1767.  
  1768. ci++;
  1769. }
  1770.  
  1771.  
  1772. }
  1773. if(res.length>ci) res.length=ci;
  1774. if(latest>=0){
  1775. res[latest].isLatest = true;
  1776. }
  1777.  
  1778.  
  1779.  
  1780.  
  1781.  
  1782. _console.log(2908, res)
  1783.  
  1784. return res;
  1785.  
  1786.  
  1787. }
  1788.  
  1789. // function _innerCommentsLoader() {
  1790.  
  1791. // /** @type {HTMLElement | null} */
  1792. // let ytdFlexyElm = kRef(ytdFlexy);
  1793. // if (!scriptEnable || !ytdFlexyElm) return;
  1794.  
  1795. // if (deferredVarYTDHidden) return;
  1796.  
  1797.  
  1798.  
  1799. // //console.log(823100,rootElement)
  1800.  
  1801. // /** @type {Array<HTMLElement>} */
  1802. // 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')]
  1803.  
  1804.  
  1805. // let di = 0;
  1806. // let dz = false;
  1807. // /** @type {WeakSet<HTMLElement>} */
  1808. // let dS = new WeakSet()
  1809. // for (const loadedComment of loadedComments) {
  1810. // let loadedCommentElm = loadedComment ? kRef(loadedComment) : null;
  1811.  
  1812. // if (!loadedCommentElm || !qmElms.includes(loadedCommentElm)) {
  1813. // loadedComments[di] = null;
  1814. // dz = true;
  1815. // } else {
  1816. // dS.add(loadedCommentElm)
  1817. // }
  1818. // di++;
  1819. // }
  1820. // if (dz) {
  1821. // loadedComments = loadedComments.filter(e => !!e)
  1822. // }
  1823.  
  1824. // let res = new Array(qmElms.length);
  1825. // res.newFound = false;
  1826.  
  1827. // let ci = 0;
  1828. // for (const qmElm of qmElms) {
  1829.  
  1830. // let mgz = 0
  1831. // if (qmElm.id === 'count') {
  1832. // //#count.ytd-comments-header-renderer
  1833. // mgz = 1;
  1834. // } else if ((qmElm.textContent || '').trim()) {
  1835. // //ytd-message-renderer.ytd-item-section-renderer
  1836. // mgz = 2;
  1837. // // it is possible to get the message before the header generation.
  1838. // // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  1839. // // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  1840. // }
  1841.  
  1842. // if (mgz > 0) {
  1843. // let isNew = !dS.has(qmElm)
  1844. // if (isNew) {
  1845. // loadedComments.push(mWeakRef(qmElm))
  1846. // res.newFound = true;
  1847. // }
  1848.  
  1849. // res[ci] = {
  1850. // status: mgz,
  1851. // text: qmElm.textContent,
  1852. // elm: qmElm,
  1853. // isNew: isNew,
  1854. // f: innerCommentsFuncs[mgz-1]
  1855. // }
  1856.  
  1857. // }
  1858. // ci++;
  1859.  
  1860. // }
  1861.  
  1862. // let lastElm = kRef(loadedComments[loadedComments.length - 1])
  1863.  
  1864. // for (const entry of res) {
  1865. // entry.isLatest = entry.elm === lastElm;
  1866. // }
  1867.  
  1868. // dS = null
  1869.  
  1870.  
  1871.  
  1872. // _console.log(2908, res)
  1873.  
  1874. // return res;
  1875.  
  1876.  
  1877. // }
  1878.  
  1879.  
  1880. function restoreFetching(){
  1881.  
  1882.  
  1883. if(mtf_forceCheckLiveVideo_disable===2) return;
  1884.  
  1885.  
  1886. let ytdFlexyElm = kRef(ytdFlexy);
  1887. if(!ytdFlexyElm) return;
  1888.  
  1889. _console.log(2901)
  1890.  
  1891. if(isCommentsK(ytdFlexyElm))return;
  1892.  
  1893. _console.log(2902)
  1894.  
  1895. let visibleComments = querySelectorFromAnchor.call(ytdFlexyElm, 'ytd-comments#comments:not([hidden])')
  1896. if(!visibleComments) return;
  1897.  
  1898. _console.log(2903)
  1899.  
  1900. fetchPendings.length= 0 ;
  1901. fetched=false;
  1902.  
  1903. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'K');
  1904.  
  1905. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  1906. let span = querySelectorFromAnchor.call(tabBtn,'span#tab3-txt-loader');
  1907. tabBtn.removeAttribute('loaded-comment')
  1908. span.innerHTML='';
  1909.  
  1910. if(tabBtn){
  1911. tabBtn.classList.remove("tab-btn-hidden")
  1912. }
  1913. _console.log(2905)
  1914.  
  1915.  
  1916. }
  1917.  
  1918. let fetched = false;
  1919. let pendingFetch = null;
  1920. const comments_ulfx = (innerCommentsLoaderRet)=> {
  1921.  
  1922. if (!scriptEnable || !ytdFlexy || !kRef(ytdFlexy)) return;
  1923.  
  1924. if (pageType!=='watch') return;
  1925.  
  1926. _console.log(980, 1)
  1927.  
  1928.  
  1929. let newFound = innerCommentsLoaderRet.newFound;
  1930.  
  1931. let b = innerCommentsLoaderRet
  1932.  
  1933. _console.log(980, 2, !newFound)
  1934.  
  1935. if (!newFound) {
  1936. if (b.length === 1) {
  1937. _console.log(980, 3, 1)
  1938. let entry = b[0]
  1939. pendingFetch = entry;
  1940. }
  1941. return;
  1942. }
  1943.  
  1944. pendingFetch=null;
  1945.  
  1946. if (b.length === 1) {
  1947.  
  1948. _console.log(980, 3, 3)
  1949. let entry = b[0]
  1950. entry.f();
  1951. fetched = true;
  1952.  
  1953. } else if (b.length > 1) {
  1954.  
  1955. _console.log(980, 3, 5)
  1956. let yy = null;
  1957. for (const entry of b) {
  1958. if (entry.isLatest === true && entry.isNew) {
  1959. yy = entry;
  1960. break;
  1961. }
  1962. }
  1963.  
  1964. _console.log(980, 3, 6, yy)
  1965. if (yy) {
  1966. yy.f();
  1967. fetched = true;
  1968. }
  1969.  
  1970. }
  1971. _console.log(2907, innerCommentsLoaderRet)
  1972.  
  1973. };
  1974.  
  1975.  
  1976. let mtc_nr_comments=0;
  1977. const cssOnceFunc_comments = (comments)=>{
  1978. // once per {ytd-comments#comments} detection
  1979.  
  1980. let ytdFlexyElm = kRef(ytdFlexy);
  1981. if (!scriptEnable || !ytdFlexyElm) return;
  1982.  
  1983. if (!comments) return;
  1984. _console.log(3901)
  1985.  
  1986. if(mtoVisibility_Comments.bindElement(comments)){
  1987. mtoVisibility_Comments.observer.check(9);
  1988. }
  1989.  
  1990. };
  1991.  
  1992. const cssOnceFunc_teaserInfo = (teaserInfo)=>{
  1993. //obsolete?
  1994.  
  1995. // for Teaser UI
  1996. // once per {#description-and-actions.style-scope.ytd-watch-metadata > #description > ytd-text-inline-expander} detection
  1997.  
  1998. let ytdFlexyElm = kRef(ytdFlexy);
  1999. if (!scriptEnable || !ytdFlexyElm) return ;
  2000. let addedInfo = document.querySelector('#tab-info ytd-expander[tabview-info-expander]');
  2001.  
  2002. if(!addedInfo) return;
  2003.  
  2004. if(!document.documentElement.hasAttribute('tabview-injection-js-1-ready')) return;
  2005.  
  2006. _console.log(3903)
  2007.  
  2008. teaserInfo.setAttribute('tabview-removed-duplicate','')
  2009.  
  2010.  
  2011. teaserInfo.dispatchEvent(new CustomEvent('tabview-no-duplicate-info'))
  2012. //console.log(56)
  2013.  
  2014.  
  2015.  
  2016. }
  2017.  
  2018. function setOnlyOneEPanel(ePanel){
  2019.  
  2020. layoutStatusMutex.lockWith(unlock => {
  2021.  
  2022. let cPanels = engagement_panels_();
  2023. for (const entry of cPanels.list) {
  2024. if (entry.ePanel != ePanel && entry.visible) ytBtnCloseEngagementPanel(entry.ePanel);
  2025. }
  2026. setTimeout(unlock, 30)
  2027.  
  2028. })
  2029.  
  2030. }
  2031.  
  2032. function immHidden(){
  2033. //if(t_preheat_ImmHidden>Date.now()) return setTimeout(immHidden,400); // avoid timeline reset due to change of video
  2034. let comments = document.querySelector('ytd-comments#comments')
  2035.  
  2036. if(!comments.hasAttribute('hidden')) return; // visible comments content
  2037.  
  2038.  
  2039. if(pageType==='watch' && Q.comments_section_loaded===1){
  2040. emptyCommentSection();
  2041.  
  2042. }
  2043.  
  2044. }
  2045.  
  2046. const FP = {
  2047.  
  2048. mtoBodyF: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  2049. //subtree DOM mutation checker - {body} \single \subtree
  2050.  
  2051. if (!scriptEnable) return;
  2052. if (pageType!=='watch') return;
  2053.  
  2054. for (const mutation of mutations) {
  2055. for (const addedNode of mutation.addedNodes){
  2056. if (addedNode.nodeType === 1) {
  2057. if (addedNode.nodeName == "DIV" && addedNode.matches('.autocomplete-suggestion:not([autocomplete-disable-updatesc])') ) {
  2058. mtf_fixAutoCompletePosition(addedNode)
  2059. }else if(addedNode.nodeName == "DIV" && (addedNode.id==='lyricscontainer' || addedNode.id==='showlyricsbutton')){
  2060.  
  2061. goYoutubeGeniusLyrics();
  2062.  
  2063. }
  2064. }
  2065. }
  2066. }
  2067.  
  2068. },
  2069.  
  2070. mtf_attrPlaylist: (attrName, newValue) => {
  2071. //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single
  2072. //::attr ~ hidden
  2073. //console.log(1210)
  2074.  
  2075. _console.log(21311)
  2076. if (!scriptEnable) return;
  2077. if (pageType!=='watch') return;
  2078. let cssElm = kRef(ytdFlexy);
  2079. if (!cssElm) return;
  2080.  
  2081. _console.log(21312)
  2082.  
  2083. let playlist = document.querySelector('ytd-playlist-panel-renderer#playlist[tabview-true-playlist]')
  2084. let isAnyPlaylistExist = playlist && !playlist.hasAttribute('hidden');
  2085. const tabBtn = document.querySelector('[userscript-tab-content="#tab-list"]');
  2086. //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden'))
  2087. if (tabBtn) {
  2088. //console.log('attr playlist changed')
  2089. let isPlaylistTabHidden = tabBtn.classList.contains('tab-btn-hidden')
  2090. if (isPlaylistTabHidden && isAnyPlaylistExist) {
  2091. //console.log('attr playlist changed - no hide')
  2092. tabBtn.classList.remove("tab-btn-hidden");
  2093. } else if (!isPlaylistTabHidden && !isAnyPlaylistExist) {
  2094. //console.log('attr playlist changed - add hide')
  2095. hideTabBtn(tabBtn);
  2096. }
  2097. }
  2098. /* visible layout for triggering hidden removal */
  2099. akAttr(cssElm, 'tabview-youtube-playlist', !isAnyPlaylistExist);
  2100. },
  2101. mtf_attrComments: (attrName, newValue) => {
  2102. //attr mutation checker - {ytd-comments#comments} \single
  2103. //::attr ~ hidden
  2104.  
  2105. // *** consider this can happen immediately after pop state. timeout / interval might clear out.
  2106.  
  2107. if (pageType!=='watch') return;
  2108.  
  2109. let comments = document.querySelector('ytd-comments#comments')
  2110. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  2111. if (!comments || !tabBtn) return;
  2112. let isCommentHidden = comments.hasAttribute('hidden')
  2113. //console.log('attr comments changed')
  2114.  
  2115.  
  2116. let ytdFlexyElm = kRef(ytdFlexy);
  2117. if (!scriptEnable || !ytdFlexyElm) return;
  2118.  
  2119. if( mtf_forceCheckLiveVideo_disable === 2 ){
  2120.  
  2121. }else if (!isCommentHidden) {
  2122.  
  2123.  
  2124. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'K');
  2125. emptyCommentSection();
  2126. tabBtn.classList.remove("tab-btn-hidden") //if contains
  2127.  
  2128.  
  2129. } else if (isCommentHidden) {
  2130.  
  2131. akAttr(ytdFlexyElm, 'tabview-youtube-comments', true, 'K');
  2132. immHidden();
  2133.  
  2134. }
  2135.  
  2136.  
  2137. },
  2138.  
  2139.  
  2140. mtf_attrChatroom: (attrName, newValue) => {
  2141. //attr mutation checker - {ytd-live-chat-frame#chat} \single
  2142. //::attr ~ collapsed
  2143.  
  2144. let ytdFlexyElm = kRef(ytdFlexy);
  2145. if (!scriptEnable || !ytdFlexyElm) return;
  2146. if (pageType!=='watch') return;
  2147.  
  2148. layoutStatusMutex.lockWith(unlock => {
  2149.  
  2150. const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
  2151. const cssElm = kRef(ytdFlexy)
  2152.  
  2153. if (!chatBlock || !cssElm) {
  2154. unlock();
  2155. return;
  2156. }
  2157.  
  2158. if (pageType!=='watch') {
  2159. unlock();
  2160. return;
  2161. }
  2162.  
  2163. if (!cssElm.hasAttribute('userscript-chatblock')) wAttr(cssElm, 'userscript-chatblock', true);
  2164. _console.log(332,ytdFlexyElm.hasAttribute('userscript-chatblock'))
  2165. let isCollapsed = !!chatBlock.hasAttribute('collapsed');
  2166. wAttr(cssElm, 'userscript-chat-collapsed', isCollapsed);
  2167.  
  2168. if (cssElm.hasAttribute('userscript-chatblock') && !isCollapsed) lstTab.lastPanel = '#chatroom';
  2169.  
  2170. if (!isCollapsed && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  2171. switchTabActivity(null);
  2172. timeline.setTimeout(unlock, 40);
  2173. } else {
  2174. unlock();
  2175. }
  2176.  
  2177. if (isCollapsed) {
  2178. chatBlock.removeAttribute('yt-userscript-iframe-loaded');
  2179. }
  2180.  
  2181. })
  2182.  
  2183.  
  2184.  
  2185.  
  2186. },
  2187.  
  2188. mtf_attrEngagementPanel: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  2189. //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple
  2190. //::attr ~ visibility
  2191.  
  2192. let ytdFlexyElm = kRef(ytdFlexy);
  2193. if (!scriptEnable || !ytdFlexyElm) return;
  2194.  
  2195. //multiple instance
  2196. if (mutations) {
  2197. for (const mutation of mutations) {
  2198. let ePanel = mutation.target;
  2199. if (ePanel.getAttribute('visibility') == 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED') {
  2200. setOnlyOneEPanel(ePanel);
  2201. break;
  2202. }
  2203. }
  2204. }
  2205. if (pageType!=='watch') return;
  2206.  
  2207. layoutStatusMutex.lockWith(unlock => {
  2208.  
  2209. const ePanel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer')
  2210. const cssElm = kRef(ytdFlexy)
  2211.  
  2212. if (!ePanel || !cssElm) {
  2213. unlock();
  2214. return;
  2215. }
  2216. let previousValue = +cssElm.getAttribute('userscript-engagement-panel') || 0;
  2217.  
  2218. let { shownRes, value, count } = engagement_panels_();
  2219. let nextValue = value;
  2220. let nextCount = count;
  2221.  
  2222.  
  2223. if (nextCount == 0 && cssElm.hasAttribute('userscript-engagement-panel')) {
  2224. storeLastPanel=null;
  2225. wAttr(cssElm, 'userscript-engagement-panel', false);
  2226. unlock();
  2227. } else {
  2228.  
  2229. if ((nextCount > 1) || (cssElm.hasAttribute('userscript-engagement-panel') && previousValue === nextValue)) {
  2230. unlock();
  2231. return;
  2232. }
  2233.  
  2234. cssElm.setAttribute('userscript-engagement-panel', nextValue);
  2235.  
  2236. let b = false;
  2237. if (previousValue != nextValue && nextValue > 0) {
  2238. lstTab.lastPanel = `#engagement-panel-${nextValue}`;
  2239. b = true;
  2240. storeLastPanel = mWeakRef( shownRes[0])
  2241. //console.log(9999, shownRes[0])
  2242. }
  2243.  
  2244. if (b && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  2245. switchTabActivity(null);
  2246. timeline.setTimeout(unlock, 40);
  2247. } else {
  2248. unlock();
  2249. }
  2250. }
  2251.  
  2252. })
  2253.  
  2254.  
  2255.  
  2256.  
  2257. },
  2258.  
  2259. mtf_initalAttr_chatroom: () => {
  2260. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  2261. let ytdFlexyElm = kRef(ytdFlexy);
  2262. if (!scriptEnable || !ytdFlexyElm) return true;
  2263.  
  2264. const rootElement = ytdFlexyElm;
  2265.  
  2266.  
  2267. if (!mgChatFrame.inPage()) {
  2268.  
  2269. mtoVisibility_Chatroom.clear(true);
  2270.  
  2271. let chatroom = querySelectorFromAnchor.call(rootElement,`ytd-live-chat-frame#chat:not([${sa_chatroom}]`)
  2272. if (chatroom) {
  2273. mgChatFrame.setVar(chatroom);
  2274.  
  2275. //console.log(124,chatroom)
  2276. if(mtoVisibility_Chatroom.bindElement(chatroom)){
  2277. mtoVisibility_Chatroom.observer.check(9)
  2278. }
  2279.  
  2280. chatroom = null
  2281. }
  2282. }
  2283. return true;
  2284.  
  2285. },
  2286.  
  2287.  
  2288.  
  2289.  
  2290.  
  2291. }
  2292.  
  2293.  
  2294.  
  2295.  
  2296.  
  2297. /** @type {WeakRef | null} */
  2298. let displayedPlaylist = null;
  2299. /** @type {WeakRef | null} */
  2300. let scrollingVideosList = null;
  2301.  
  2302. let scriptEnable = false;
  2303. let scriptEC = 0;
  2304. let lstTab = null;
  2305. function lstTabInit(){
  2306. lstTab =
  2307. {
  2308. lastTab: null,
  2309. lastPanel: null,
  2310. last: null
  2311. };
  2312. }
  2313.  
  2314. let _cachedLastVideo = null;
  2315. let videoListBeforeSearch = null;
  2316.  
  2317. /** @type {WeakRef | null} */
  2318. let ytdFlexy = null;
  2319.  
  2320. function pluginUnhook() {
  2321. _pluginUnhook();
  2322. emptyCommentSection();
  2323. }
  2324.  
  2325. function _pluginUnhook() {
  2326.  
  2327. //console.log(8001)
  2328.  
  2329. videoListBeforeSearch = null;
  2330. _cachedLastVideo = null;
  2331. lstTabInit()
  2332. displayedPlaylist = null;
  2333. scrollingVideosList = null;
  2334. scriptEnable = false;
  2335. scriptEC++;
  2336. if (scriptEC > 788888888) scriptEC = 188888888;
  2337. ytdFlexy = null;
  2338. wls.layoutStatus = 0;
  2339.  
  2340. //console.log('unc01')
  2341.  
  2342. mtoVisibility_EngagementPanel.clear(true)
  2343. mtoVisibility_Playlist.clear(true)
  2344. mtoVisibility_Comments.clear(true)
  2345.  
  2346. mgChatFrame.kVar = null;
  2347. mtoVisibility_Chatroom.clear(true)
  2348. mtoFlexyAttr.clear(true)
  2349.  
  2350.  
  2351. for (const elem of document.querySelectorAll(
  2352. ['ytd-expander[tabview-info-expander]'].join(', ')
  2353. )) {
  2354. elem.removeAttribute('tabview-info-expander');
  2355. }
  2356.  
  2357. mtoMutation_body.clear(true)
  2358. Q.mtf_chatBlockQ = null;
  2359.  
  2360.  
  2361.  
  2362. }
  2363.  
  2364.  
  2365.  
  2366. let pageLang = 'en';
  2367. const langWords ={
  2368. 'en':{
  2369. //'share':'Share',
  2370. 'info':'Info',
  2371. 'videos':'Videos',
  2372. 'playlist':'Playlist'
  2373. },
  2374. 'jp':{
  2375. //'share':'共有',
  2376. 'info':'情報',
  2377. 'videos':'動画',
  2378. 'playlist':'再生リスト'
  2379. },
  2380. 'tw':{
  2381. //'share':'分享',
  2382. 'info':'資訊',
  2383. 'videos':'影片',
  2384. 'playlist':'播放清單'
  2385. },
  2386. 'cn':{
  2387. //'share':'分享',
  2388. 'info':'资讯',
  2389. 'videos':'视频',
  2390. 'playlist':'播放列表'
  2391. },
  2392. 'du':{
  2393. //'share':'Teilen',
  2394. 'info':'Info',
  2395. 'videos':'Videos',
  2396. 'playlist':'Playlist'
  2397. },
  2398. 'fr':{
  2399. //'share':'Partager',
  2400. 'info':'Info',
  2401. 'videos':'Vidéos',
  2402. 'playlist':'Playlist'
  2403. },
  2404. 'kr':{
  2405. //'share':'공유',
  2406. 'info':'정보',
  2407. 'videos':'동영상',
  2408. 'playlist':'재생목록'
  2409. }
  2410. };
  2411.  
  2412. function getWord(tag){
  2413. return langWords[pageLang][tag]||langWords['en'][tag]||'';
  2414. }
  2415.  
  2416.  
  2417. function getTabsHTML() {
  2418.  
  2419. const sTabBtnVideos = `${svgElm(16,16,298,298,svgVideos)}<span>${getWord('videos')}</span>`;
  2420. const sTabBtnInfo = `${svgElm(16,16,23.625,23.625,svgInfo)}<span>${getWord('info')}</span>`;
  2421. const sTabBtnPlayList = `${svgElm(16,16,426.667,426.667,svgPlayList)}<span>${getWord('playlist')}</span>`;
  2422.  
  2423. let str1 = `
  2424. <paper-ripple class="style-scope yt-icon-button">
  2425. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  2426. <div id="waves" class="style-scope paper-ripple"></div>
  2427. </paper-ripple>
  2428. `;
  2429.  
  2430. let str_fbtns = `
  2431. <div class="font-size-right">
  2432. <div class="font-size-btn font-size-plus">
  2433. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  2434. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  2435. <path d="M12 25h26"/><path d="M25 12v26"/>
  2436. </svg>
  2437. </div><div class="font-size-btn font-size-minus">
  2438. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  2439. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  2440. <path d="M12 25h26"/>
  2441. </svg>
  2442. </div>
  2443. </div>
  2444. `.replace(/[\r\n]+/g,'');
  2445.  
  2446. const str_tabs = [
  2447. `<a id="tab-btn1" data-name="info" userscript-tab-content="#tab-info" class="tab-btn">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
  2448. `<a id="tab-btn3" userscript-tab-content="#tab-comments" data-name="comments" class="tab-btn">${svgElm(16,16,60,60,svgComments)}<span id="tab3-txt-loader"></span>${str1}${str_fbtns}</a>`,
  2449. `<a id="tab-btn4" userscript-tab-content="#tab-videos" data-name="videos" class="tab-btn">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
  2450. `<a id="tab-btn5" userscript-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`
  2451. ].join('');
  2452.  
  2453. let addHTML = `
  2454. <div id="right-tabs">
  2455. <header>
  2456. <div id="material-tabs">
  2457. ${str_tabs}
  2458. </div>
  2459. </header>
  2460. <div class="tab-content">
  2461. <div id="tab-info" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2462. <div id="tab-comments" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2463. <div id="tab-videos" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2464. <div id="tab-list" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2465. </div>
  2466. </div>
  2467. `;
  2468.  
  2469. return addHTML;
  2470.  
  2471. }
  2472.  
  2473. function getLang(){
  2474.  
  2475. let lang = 'en';
  2476. let htmlLang = ((document||0).documentElement||0).lang||'';
  2477. switch (htmlLang) {
  2478. case 'en':
  2479. case 'en-GB':
  2480. lang = 'en';
  2481. break;
  2482. case 'de':
  2483. case 'de-DE':
  2484. lang = 'du';
  2485. break;
  2486. case 'fr':
  2487. case 'fr-CA':
  2488. case 'fr-FR':
  2489. lang = 'fr';
  2490. break;
  2491. case 'zh-Hant':
  2492. case 'zh-Hant-HK':
  2493. case 'zh-Hant-TW':
  2494. lang = 'tw';
  2495. break;
  2496. case 'zh-Hans':
  2497. case 'zh-Hans-CN':
  2498. lang = 'cn';
  2499. break;
  2500. case 'ja':
  2501. case 'ja-JP':
  2502. lang = 'jp';
  2503. break;
  2504. case 'ko':
  2505. case 'ko-KR':
  2506. lang = 'kr';
  2507. break;
  2508. default:
  2509. lang = 'en';
  2510. }
  2511. if(langWords[lang]) pageLang = lang; else pageLang = 'en';
  2512.  
  2513. }
  2514.  
  2515. function checkEvtTarget(evt, nodeNames){
  2516. return nodeNames.includes((((evt||0).target||0).nodeName||0));
  2517. }
  2518.  
  2519.  
  2520.  
  2521. let cid_nav_end = 0;
  2522. let rc_nav_end = 0;
  2523.  
  2524. const symbol_noRepeat = Symbol();
  2525.  
  2526. function globalHook(eventType, func){
  2527.  
  2528. document.addEventListener(eventType,function(evt){
  2529. if(evt[symbol_noRepeat])return;
  2530. evt[symbol_noRepeat]=true;
  2531.  
  2532. new Promise(()=>{
  2533.  
  2534. func(evt);
  2535.  
  2536. })
  2537.  
  2538. },capturePassive)
  2539.  
  2540. }
  2541.  
  2542.  
  2543. let initializerResolve = null;
  2544.  
  2545. let initializerLock = new Promise(resolve=>{initializerResolve=resolve});
  2546.  
  2547. const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER','YTD-MENU-RENDERER']
  2548.  
  2549.  
  2550.  
  2551.  
  2552. function pageCheck(){
  2553. // yt-rendererstamper-finished
  2554. // yt-player-updated
  2555. // yt-page-data-updated
  2556. // yt-watch-comments-ready
  2557. // [is-two-columns_] attr changed
  2558. let m_teaser_info = document.querySelector('#description-and-actions.style-scope.ytd-watch-metadata > #description ytd-text-inline-expander:not([tabview-removed-duplicate])')
  2559. if(m_teaser_info) cssOnceFunc_teaserInfo(m_teaser_info)
  2560. FP.mtf_initalAttr_chatroom();
  2561. mtf_append_comments();
  2562. mtf_liveChatBtnF();
  2563. fixTabs();
  2564. mtf_AfterFixTabs();
  2565.  
  2566. }
  2567.  
  2568. globalHook('yt-rendererstamper-finished',(evt)=>{
  2569.  
  2570. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2571.  
  2572. _console.log(evt.target.nodeName,904, evt.type, evt.detail);
  2573. (async ()=>{
  2574.  
  2575. await initializerLock;
  2576.  
  2577. const nodeName = evt.target.nodeName.toUpperCase();
  2578. const node = evt.target
  2579. _console.log(evt.target.nodeName,905, evt.type)
  2580.  
  2581. if(!S_GENERAL_RENDERERS.includes(nodeName)){
  2582.  
  2583. mtc_nr_comments = Date.now();
  2584.  
  2585. switch(nodeName){
  2586. case 'YTD-ENGAGEMENT-PANEL-SECTION-LIST-RENDERER':
  2587. // ignore; trigger by page-updated
  2588. break;
  2589. default:
  2590. _console.log(782,1, nodeName)
  2591. pageCheck();
  2592. }
  2593. }
  2594.  
  2595. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  2596. //console.log(2344, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2597. //console.log(555, document.querySelectorAll('#meta-contents ytd-expander').length)
  2598. if(nodeName === 'YTD-MENU-RENDERER'){
  2599. //console.log('YTD-MENU-RENDERER !! ', evt.target)
  2600. }
  2601.  
  2602. if(nodeName === 'YTD-PLAYLIST-PANEL-RENDERER'){
  2603. //("yt-playlist-data-updated");
  2604. //yt-playlist-reloading
  2605. mtf_append_playlist(node);
  2606. let m_playlist = document.querySelector(`ytd-playlist-panel-renderer#playlist[tabview-true-playlist]:not([o3r-${sa_playlist}])`)
  2607.  
  2608. // once per {ytd-playlist-panel-renderer#playlist} detection
  2609.  
  2610. _console.log(3902, !!m_playlist)
  2611.  
  2612. let ytdFlexyElm = kRef(ytdFlexy);
  2613. if (!scriptEnable || !ytdFlexyElm){}
  2614. else if (m_playlist){
  2615.  
  2616. if(mtoVisibility_Playlist.bindElement(m_playlist)){
  2617. mtoVisibility_Playlist.observer.check(9); //delay check required for browser bug - hidden changed not triggered
  2618. }
  2619. m_playlist = null;
  2620.  
  2621. }
  2622. FP.mtf_attrPlaylist();
  2623.  
  2624. }else if (nodeName ==='YTD-WATCH-METADATA'){
  2625.  
  2626. }else if (nodeName ==='YTD-COMMENTS-HEADER-RENDERER'){
  2627. _console.log(3205,1)
  2628.  
  2629.  
  2630. let innerCommentsLoaderRet = _innerCommentsLoader();
  2631. comments_ulfx(innerCommentsLoaderRet);
  2632. _console.log(3205,3)
  2633.  
  2634. if(!fetched && pendingFetch){
  2635. requestAnimationFrame(()=>{
  2636.  
  2637. pendingFetch.f();
  2638. fetched= true;
  2639. fetchCommentsFinished();
  2640. fetched=false;pendingFetch=null;
  2641. })
  2642. }
  2643.  
  2644. if(fetched){
  2645. fetchCommentsFinished();
  2646. fetched=false;pendingFetch=null;
  2647. }
  2648.  
  2649.  
  2650.  
  2651.  
  2652. }else if(nodeName==='YTD-ENGAGEMENT-PANEL-SECTION-LIST-RENDERER'){
  2653.  
  2654.  
  2655. let ytdFlexyElm = kRef(ytdFlexy);
  2656. if (scriptEnable && ytdFlexyElm) {
  2657.  
  2658.  
  2659. let match = node.matches(`ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([o3r-${sa_epanel}])`);
  2660. if(match){
  2661.  
  2662.  
  2663. mtoVisibility_EngagementPanel.bindElement(node, {
  2664. attributes: true,
  2665. attributeFilter: ['visibility'],
  2666. attributeOldValue: true
  2667. })
  2668.  
  2669.  
  2670. }
  2671.  
  2672.  
  2673.  
  2674. }
  2675.  
  2676. }
  2677.  
  2678.  
  2679.  
  2680. })()
  2681.  
  2682. })
  2683.  
  2684. globalHook('yt-player-updated',(evt)=>{
  2685.  
  2686.  
  2687. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2688.  
  2689. mtc_nr_comments = Date.now();
  2690.  
  2691. _console.log(evt.target.nodeName,904, evt.type);
  2692. _innerCommentsLoader();
  2693. //restoreFetching(); //yt-player-updated might happen after comments ready
  2694. if(!fetched){
  2695. window.dispatchEvent(new Event("scroll"));
  2696. }
  2697. pageCheck();
  2698.  
  2699. (async ()=>{
  2700. await initializerLock;
  2701. let nodeName = evt.target.nodeName.toUpperCase()
  2702. _console.log(nodeName,905, evt.type)
  2703.  
  2704. _console.log(2344,evt.type, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2705.  
  2706.  
  2707. if(nodeName === 'YTD-PLAYER'){
  2708.  
  2709. _console.log(2554, nodeName, evt.type, evt.detail )
  2710.  
  2711. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  2712. if(m_comments) cssOnceFunc_comments(m_comments);
  2713. FP.mtf_initalAttr_chatroom();
  2714.  
  2715. }
  2716.  
  2717. })();
  2718.  
  2719.  
  2720.  
  2721. })
  2722.  
  2723.  
  2724. globalHook('yt-page-data-updated',(evt)=>{
  2725.  
  2726. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2727.  
  2728. _console.log(evt.target.nodeName,904, evt.type);
  2729.  
  2730.  
  2731. mtc_nr_comments = Date.now();
  2732.  
  2733.  
  2734. FP.mtf_attrEngagementPanel();
  2735. _innerCommentsLoader();
  2736. if(!fetched){
  2737. window.dispatchEvent(new Event("scroll"));
  2738. }
  2739.  
  2740. pageCheck();
  2741.  
  2742. (async ()=>{
  2743.  
  2744. await initializerLock;
  2745. _console.log(evt.target.nodeName,905, evt.type)
  2746.  
  2747. _console.log(2774, !!document.querySelector('ytd-live-chat-frame#chat'))
  2748.  
  2749. _console.log(2344,evt.type, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2750. _console.log(544, document.querySelectorAll('#meta-contents'))
  2751. _console.log(546, document.querySelectorAll('#meta-contents ytd-expander'))
  2752. let expander = document.querySelector('#meta-contents ytd-expander:not([tabview-info-expander])')
  2753. if(expander){
  2754.  
  2755. // once per $$native-info-description$$ {#meta-contents ytd-expander} detection
  2756. // append the detailed meta contents to the tab-info
  2757.  
  2758. let ytdFlexyElm = kRef(ytdFlexy);
  2759. if (!scriptEnable || !ytdFlexyElm) return ;
  2760.  
  2761. if (!expander) return ;
  2762. expander.setAttribute('tabview-info-expander','')
  2763. $(expander).appendTo("#tab-info").attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  2764.  
  2765.  
  2766.  
  2767. }
  2768. mtf_fix_details();
  2769. })();
  2770.  
  2771.  
  2772.  
  2773. })
  2774.  
  2775.  
  2776. globalHook('yt-page-data-fetched',(evt)=>{
  2777.  
  2778. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2779.  
  2780. mtc_nr_comments = Date.now();
  2781.  
  2782. let nodeName = evt.target.nodeName.toUpperCase()
  2783.  
  2784. _console.log(nodeName,904, evt.type);
  2785.  
  2786. let d_page = ((evt.detail||0).pageData||0).page;
  2787.  
  2788. pageType = d_page;
  2789. if(d_page=='watch'){
  2790. document.documentElement.classList.toggle('tabview-normal-player', true)
  2791. }else{
  2792. document.documentElement.classList.toggle('tabview-normal-player', false)
  2793.  
  2794. }
  2795.  
  2796. dispatchWindowResize(); // player control positioning
  2797.  
  2798. (async ()=>{
  2799. await initializerLock;
  2800. _console.log(nodeName,905, evt.type);
  2801.  
  2802. _console.log(2344,evt.type, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2803.  
  2804. if(nodeName==='YTD-APP'){
  2805. _console.log(2554, nodeName, evt.type, evt.detail )
  2806. newVideoPage(evt.detail);
  2807. }
  2808.  
  2809. })();
  2810.  
  2811.  
  2812. })
  2813.  
  2814. globalHook('yt-watch-comments-ready',(evt)=>{
  2815. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2816.  
  2817. mtc_nr_comments = Date.now();
  2818.  
  2819. let nodeName = evt.target.nodeName.toUpperCase()
  2820. _console.log(nodeName,904, evt.type, evt.detail);
  2821.  
  2822. //fetchCommentsFinished();
  2823. window.dispatchEvent(new Event("scroll"));
  2824.  
  2825. pageCheck();
  2826.  
  2827. (async ()=>{
  2828. await initializerLock;
  2829. _console.log(nodeName,905, evt.type);
  2830.  
  2831. if(nodeName==='YTD-WATCH-FLEXY'){
  2832. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  2833. if(m_comments) cssOnceFunc_comments(m_comments);
  2834.  
  2835.  
  2836.  
  2837. if(mtf_forceCheckLiveVideo_disable===2){
  2838.  
  2839. }else{
  2840.  
  2841.  
  2842. _console.log(3713, fetched)
  2843.  
  2844. if(document.querySelector(`ytd-comments#comments`).hasAttribute('hidden')){
  2845.  
  2846. //unavailable apart from live chat
  2847.  
  2848. _disableComments();
  2849. _console.log(3713, 3)
  2850.  
  2851.  
  2852. }else{
  2853. let innerCommentsLoaderRet = _innerCommentsLoader();
  2854. comments_ulfx(innerCommentsLoaderRet);
  2855. _console.log(3713, 5)
  2856.  
  2857.  
  2858.  
  2859. if(fetched|| pendingFetch ){
  2860. _console.log(3713, 6)
  2861. // if there is only message, no rendering event occur after yt-watch-comments-ready
  2862. timeline.setTimeout(()=>{
  2863. if(!fetched && pendingFetch){
  2864. pendingFetch.f();
  2865. fetched= true;
  2866. }
  2867. if(fetched){
  2868. fetchCommentsFinished();
  2869. fetched=false;pendingFetch=null;
  2870. }
  2871. },800)
  2872. }
  2873.  
  2874. }
  2875. _console.log(3714, fetched, !!pendingFetch)
  2876.  
  2877.  
  2878.  
  2879. }
  2880.  
  2881.  
  2882. }
  2883.  
  2884. })();
  2885.  
  2886.  
  2887. })
  2888.  
  2889. function onNavigationEnd(evt) {
  2890. console.log('yt-navigate-finish')
  2891.  
  2892.  
  2893.  
  2894. globalHook('yt-navigate',(evt)=>{
  2895.  
  2896. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2897. _console.log(evt.target.nodeName, evt.type)
  2898.  
  2899.  
  2900.  
  2901. })
  2902.  
  2903.  
  2904. globalHook('ytd-playlist-lockup-now-playing-active',(evt)=>{
  2905.  
  2906. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2907. _console.log(evt.target.nodeName, evt.type)
  2908.  
  2909.  
  2910. })
  2911.  
  2912. globalHook('yt-service-request-completed',(evt)=>{
  2913.  
  2914. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2915. _console.log(evt.target.nodeName, evt.type)
  2916.  
  2917.  
  2918. })
  2919. globalHook('yt-commerce-action-done',(evt)=>{
  2920.  
  2921. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2922. _console.log(evt.target.nodeName, evt.type)
  2923.  
  2924.  
  2925. })
  2926. globalHook('yt-navigate',(evt)=>{
  2927.  
  2928. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2929. _console.log(evt.target.nodeName, evt.type)
  2930.  
  2931.  
  2932. })
  2933.  
  2934.  
  2935. globalHook('yt-execute-service-endpoint',(evt)=>{
  2936.  
  2937. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2938. _console.log(evt.target.nodeName, evt.type)
  2939.  
  2940. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  2941.  
  2942.  
  2943. })
  2944.  
  2945.  
  2946.  
  2947. globalHook('yt-request-panel-mode-change',(evt)=>{
  2948.  
  2949. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2950. _console.log(evt.target.nodeName, evt.type)
  2951.  
  2952.  
  2953. })
  2954.  
  2955. window.addEventListener("message", (evt) => {
  2956. if(evt.origin === location.origin && evt.data.tabview){
  2957.  
  2958. let data = evt.data.tabview;
  2959.  
  2960. if(data.eventType=== "yt-page-type-changed"){
  2961. let detail = data.eventDetail
  2962. let {newPageType, oldPageType} = detail;
  2963. if(newPageType && oldPageType ){
  2964.  
  2965. let bool = false;
  2966. if(newPageType == 'ytd-watch-flexy'){
  2967. bool = true;
  2968.  
  2969. pageType = 'watch';
  2970. }else if(newPageType=='ytd-browse'){
  2971. pageType = 'browse';
  2972. }
  2973.  
  2974. //if(newPageType=='ytd-browse'){
  2975.  
  2976. //}
  2977. document.documentElement.classList.toggle('tabview-normal-player', bool)
  2978. dispatchWindowResize(); // player control positioning
  2979. }
  2980. }
  2981.  
  2982. console.log(37192,evt)
  2983.  
  2984. }
  2985. }, bubblePassive);
  2986. /*
  2987. document.addEventListener('yt-page-type-changed',(evt)=>{
  2988.  
  2989. console.log(3553,evt,evt.detail,evt.target.data)
  2990. },false);
  2991. */
  2992. globalHook('yt-visibility-refresh',(evt)=>{
  2993.  
  2994. if(!evt || !evt.target /*|| evt.target.nodeType !== 1*/) return;
  2995. _console.log(evt.target.nodeName||'', evt.type)
  2996.  
  2997. _console.log(2784, evt.type, kRef(ytdFlexy).hasAttribute('hidden'),evt.detail)
  2998. _console.log(evt.detail)
  2999.  
  3000.  
  3001. })
  3002.  
  3003.  
  3004. //document.addEventListener('')
  3005.  
  3006. globalHook('yt-request-panel-mode-change',(evt)=>{
  3007.  
  3008. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3009. _console.log(evt.target.nodeName, evt.type)
  3010.  
  3011. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3012.  
  3013.  
  3014. })
  3015. globalHook('yt-navigate-finish',(evt)=>{
  3016.  
  3017. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3018. _console.log(evt.target.nodeName, evt.type)
  3019.  
  3020. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3021.  
  3022. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  3023. if(m_comments) cssOnceFunc_comments(m_comments);
  3024.  
  3025.  
  3026. })
  3027.  
  3028. globalHook('app-reset-layout',(evt)=>{
  3029.  
  3030. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3031. _console.log(evt.target.nodeName, evt.type)
  3032.  
  3033. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3034.  
  3035.  
  3036. })
  3037. globalHook('yt-guide-close',(evt)=>{
  3038.  
  3039. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3040. _console.log(evt.target.nodeName, evt.type)
  3041.  
  3042. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3043.  
  3044.  
  3045. })
  3046. globalHook('yt-page-data-will-change',(evt)=>{
  3047.  
  3048. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3049. _console.log(evt.target.nodeName, evt.type)
  3050.  
  3051.  
  3052. })
  3053.  
  3054. globalHook('yt-retrieve-location',(evt)=>{
  3055.  
  3056. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3057. _console.log(evt.target.nodeName, evt.type)
  3058.  
  3059.  
  3060. })
  3061.  
  3062.  
  3063.  
  3064. globalHook('yt-refit',(evt)=>{
  3065. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3066. _console.log(evt.target.nodeName, evt.type)
  3067. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3068. })
  3069. globalHook('addon-attached',(evt)=>{
  3070. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3071. _console.log(evt.target.nodeName, evt.type)
  3072. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3073. })
  3074. globalHook('yt-live-chat-context-menu-opened',(evt)=>{
  3075. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3076. _console.log(evt.target.nodeName, evt.type)
  3077. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3078. })
  3079.  
  3080. globalHook('yt-live-chat-context-menu-closed',(evt)=>{
  3081. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3082. _console.log(evt.target.nodeName, evt.type)
  3083. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3084. })
  3085.  
  3086. globalHook('yt-commentbox-resize',(evt)=>{
  3087. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3088. _console.log(evt.target.nodeName, evt.type)
  3089. })
  3090.  
  3091. globalHook('yt-rich-grid-layout-refreshed',(evt)=>{
  3092. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3093. _console.log(2327, evt.target.nodeName, evt.type)
  3094. })
  3095.  
  3096.  
  3097. globalHook('data-changed',(evt)=>{
  3098.  
  3099. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3100. let nodeName = evt.target.nodeName.toUpperCase()
  3101. _console.log(nodeName, evt.type)
  3102.  
  3103. if(nodeName==='YTD-ITEM-SECTION-RENDERER' || nodeName==='YTD-COMMENTS'){
  3104.  
  3105. _console.log(344)
  3106.  
  3107.  
  3108. }
  3109.  
  3110.  
  3111. })
  3112.  
  3113. globalHook('animationend',(evt)=>{
  3114.  
  3115. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3116. _console.log(evt.target.nodeName, evt.type)
  3117.  
  3118. })
  3119.  
  3120. globalHook('yt-dismissible-item-dismissed',(evt)=>{
  3121.  
  3122. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3123. _console.log(evt.target.nodeName, evt.type)
  3124.  
  3125.  
  3126. })
  3127. globalHook('yt-dismissible-item-undismissed',function(evt){
  3128.  
  3129. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3130. _console.log(evt.target.nodeName, evt.type)
  3131.  
  3132.  
  3133. })
  3134.  
  3135. globalHook('yt-load-next-continuation',function(evt){
  3136.  
  3137. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3138. _console.log(evt.target.nodeName, evt.type)
  3139.  
  3140.  
  3141. })
  3142.  
  3143. globalHook('yt-load-reload-continuation',function(evt){
  3144.  
  3145. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3146. _console.log(evt.target.nodeName, evt.type)
  3147.  
  3148.  
  3149. })
  3150.  
  3151. globalHook('yt-toggle-button',function(evt){
  3152.  
  3153. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3154. _console.log(evt.target.nodeName, evt.type)
  3155.  
  3156.  
  3157. })
  3158.  
  3159. document.dispatchEvent(new CustomEvent('tabview-v-change')) //possible duplicated
  3160. document.documentElement.setAttribute('youtube-ready','')
  3161. script_inject_js1.inject();
  3162. document.documentElement.dispatchEvent(new CustomEvent('tabview-ce-hack'))
  3163.  
  3164. //document.dispatchEvent(new CustomEvent('tabview-youtube-comments-check'))
  3165.  
  3166. forceConfig();
  3167. /*
  3168. console.log(window.ytcfg)
  3169. try{
  3170. window.ytcfg.set({
  3171. "EXPERIMENT_FLAGS": {"kevlar_watch_metadata_refresh":false}})
  3172. }catch(e){}
  3173. // ytcfg.set({EXPERIMENT_FLAGS:{"kevlar_watch_metadata_refresh":true}})
  3174. */
  3175.  
  3176.  
  3177. let mRet =
  3178. (/^https?\:\/\/(\w+\.)*youtube\.com\/watch\?(\w+\=[^\/\?\&]+\&|\w+\=?\&)*v=[\w\-\_]+/.test(window.location.href) ? 1 : 0) +
  3179. (document.querySelector('ytd-watch-flexy[tabview-selection]') ? 2 : 0);
  3180.  
  3181. if (mRet === 1) {
  3182.  
  3183.  
  3184. document.addEventListener('load',function(evt){
  3185.  
  3186. if( checkEvtTarget(evt, ['IFRAME']) && evt.target.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')){
  3187.  
  3188. let iframe = evt.target;
  3189. new Promise(resolve=>{
  3190.  
  3191. let k = 270
  3192. let cid = setInterval(()=>{
  3193. if(k--<1) {
  3194. clearInterval(cid);
  3195. resolve(false);
  3196. }
  3197.  
  3198. let cDoc = iframe.contentDocument;
  3199. if (!cDoc) return null;
  3200. if (cDoc.readyState != 'complete') return;
  3201. if (!cDoc.querySelector('body')) {
  3202. clearInterval(cid);
  3203. resolve(false);
  3204. }
  3205.  
  3206. if(!cDoc.querySelector('yt-live-chat-app')) return;
  3207.  
  3208. clearInterval(cid);
  3209.  
  3210. if(!document.contains(iframe)) return resolve(false);
  3211.  
  3212. resolve(cDoc);
  3213.  
  3214.  
  3215. },17)
  3216.  
  3217. }).then((res)=>{
  3218.  
  3219. if(res){
  3220. callFind(res)
  3221. }
  3222.  
  3223. })
  3224.  
  3225.  
  3226.  
  3227. }
  3228. }, capturePassive)
  3229.  
  3230. pluginUnhook(); // in case not triggered by popstate - say mini playing
  3231.  
  3232. let timeout = 4; // max. 4 animation frames
  3233.  
  3234. let tf = () => {
  3235.  
  3236. if (--timeout > 0 && !document.querySelector('#player video')) return requestAnimationFrame(tf);
  3237.  
  3238. let ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  3239.  
  3240. if(!ytdFlexyElm) return;
  3241.  
  3242.  
  3243. scriptEnable = true;
  3244. scriptEC++;
  3245.  
  3246.  
  3247. ytdFlexy = mWeakRef(ytdFlexyElm)
  3248.  
  3249. let timeoutR_findRelated = new Timeout();
  3250. timeoutR_findRelated.set(function() {
  3251. let ytdFlexyElm = kRef(ytdFlexy);
  3252. if(!ytdFlexyElm) return true;
  3253. let related = querySelectorFromAnchor.call(ytdFlexyElm,"#related");
  3254. if (!related) return true;
  3255. foundRelated(related);
  3256. }, 100, 10)
  3257.  
  3258. function foundRelated(related) {
  3259. let promise = Promise.resolve();
  3260. if (!document.querySelector("#right-tabs")) {
  3261. promise = promise.then(() => {
  3262. getLang();
  3263. $(getTabsHTML()).insertBefore(related).attr('data-dom-created-by-tabview-youtube', scriptVersionForExternal);
  3264. })
  3265. }
  3266. promise.then(runAfterTabAppended)
  3267. }
  3268.  
  3269. }
  3270.  
  3271. tf();
  3272.  
  3273. } else if (mRet === 3) {
  3274.  
  3275. if(!scriptEnable) return;
  3276.  
  3277. let elmComments = document.querySelector('ytd-comments#comments')
  3278.  
  3279. if (elmComments && querySelectorFromAnchor.call(elmComments,'ytd-item-section-renderer#sections.style-scope.ytd-comments')){
  3280. nativeFunc(elmComments, "loadComments")
  3281. }
  3282.  
  3283. } else if (mRet === 0) {
  3284.  
  3285. pluginUnhook(); // in case not triggered by popstate - say mini playing
  3286.  
  3287. }
  3288.  
  3289.  
  3290.  
  3291.  
  3292. if(scriptEnable) {
  3293.  
  3294.  
  3295. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  3296. if(m_comments) cssOnceFunc_comments(m_comments);
  3297.  
  3298. if(cid_nav_end>0) {
  3299. clearInterval(cid_nav_end)
  3300. cid_nav_end = 0;
  3301. }
  3302.  
  3303. rc_nav_end = 0;
  3304. cid_nav_end=setInterval(()=>{
  3305.  
  3306. if(rc_nav_end<1000) rc_nav_end++;
  3307.  
  3308. // regular check for different issues with UI display status
  3309. // until the initialization of the page finished + 4700ms
  3310.  
  3311. if(rc_nav_end<20){ // <=15s
  3312.  
  3313. let playlist = document.querySelector('ytd-playlist-panel-renderer#playlist[tabview-true-playlist]')
  3314. if(playlist){
  3315. playlist.dispatchEvent(new CustomEvent("userscript-fix-playlist-display", { detail: {} }))
  3316. }
  3317. let chatroomBtn = document.querySelector('ytd-live-chat-frame#chat #show-hide-button.ytd-live-chat-frame')
  3318. if(chatroomBtn){
  3319. // in case text is different from the visual state
  3320. chatroomBtn.dispatchEvent(new CustomEvent("userscript-fix-chatroombtn-text", { detail: {} }))
  3321. }
  3322.  
  3323. }
  3324.  
  3325. let clearTimer = false;
  3326. if(rc_nav_end<80){ // <=63s
  3327. let cTime = Date.now();
  3328.  
  3329. if(cTime - mtc_nr_comments<1270) return;
  3330. if(!mtc_nr_comments || cTime - mtc_nr_comments>2870){
  3331. clearTimer = true; // last check
  3332. }
  3333.  
  3334.  
  3335. }else{
  3336. clearTimer = true;
  3337. }
  3338. if(clearTimer && cid_nav_end>0) {
  3339. clearInterval(cid_nav_end)
  3340. cid_nav_end = 0;
  3341. }
  3342.  
  3343. },800)
  3344.  
  3345.  
  3346. initializerResolve();
  3347.  
  3348. }
  3349.  
  3350. }
  3351.  
  3352.  
  3353. function setToActiveTab(defaultTab) {
  3354. if (isTheater() && isWideScreenWithTwoColumns()) return;
  3355. const jElm = document.querySelector(`a[userscript-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  3356. document.querySelector(`a[userscript-tab-content="${(defaultTab||settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  3357. document.querySelector("a[userscript-tab-content]:not(.tab-btn-hidden)") ||
  3358. null;
  3359. switchTabActivity(jElm);
  3360. return !!jElm;
  3361. }
  3362.  
  3363. function getWrapper(wrapperId) {
  3364. let $wrapper = $(`#${wrapperId}`);
  3365. if (!$wrapper[0]) $wrapper = $(`<div id="${wrapperId}"></div>`)
  3366. return $wrapper.first();
  3367. }
  3368.  
  3369.  
  3370. function runAfterTabAppended() {
  3371.  
  3372. document.documentElement.setAttribute('plugin-tabview-youtube', '')
  3373.  
  3374. const ytdFlexyElm = kRef(ytdFlexy)
  3375. if(!ytdFlexyElm) return;
  3376. if (!ytdFlexyElm.hasAttribute('tabview-selection')) ytdFlexyElm.setAttribute('tabview-selection', '')
  3377.  
  3378. // append the next videos
  3379. // it exists as "related" is already here
  3380. fixTabs();
  3381.  
  3382. setToActiveTab(); // just switch to the default tab
  3383. prepareTabBtn();
  3384.  
  3385.  
  3386. mtoFlexyAttr.clear(true)
  3387. mtf_checkFlexy()
  3388.  
  3389.  
  3390. document.querySelector("#right-tabs .tab-content").addEventListener('scroll', windowScroll, true);
  3391.  
  3392.  
  3393. // for automcomplete plugin or other userscript plugins
  3394. // document.body for Firefox >= 60
  3395. mtoMutation_body.bindElement(document.querySelector('body'), {
  3396. childList: true,
  3397. subtree: false,
  3398. attributes: false
  3399. })
  3400.  
  3401.  
  3402.  
  3403.  
  3404.  
  3405.  
  3406. }
  3407.  
  3408.  
  3409. function fetchCommentsFinished() {
  3410. let ytdFlexyElm = kRef(ytdFlexy);
  3411. if (!scriptEnable || !ytdFlexyElm) return;
  3412. if(mtf_forceCheckLiveVideo_disable===2) return;
  3413. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'LS')
  3414. _console.log(2909,1)
  3415. }
  3416.  
  3417. function setCommentSection( /** @type {number} */ value) {
  3418.  
  3419. Q.comments_section_loaded = value;
  3420.  
  3421. }
  3422.  
  3423.  
  3424. function emptyCommentSection(){
  3425.  
  3426. let tab_btn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]')
  3427.  
  3428.  
  3429. if(tab_btn){
  3430.  
  3431. let span = querySelectorFromAnchor.call(tab_btn,'span#tab3-txt-loader');
  3432.  
  3433. tab_btn.removeAttribute('loaded-comment')
  3434. if(span){
  3435. span.textContent='';
  3436. }
  3437. }
  3438.  
  3439.  
  3440. setCommentSection(0);
  3441.  
  3442.  
  3443. }
  3444.  
  3445.  
  3446.  
  3447. function _disableComments() {
  3448.  
  3449. _console.log(2909,1)
  3450. if (!scriptEnable) return;
  3451. let cssElm = kRef(ytdFlexy);
  3452. if (!cssElm) return;
  3453.  
  3454. _console.log(2909,2)
  3455.  
  3456. let comments = document.querySelector('ytd-comments#comments')
  3457. if(mtf_forceCheckLiveVideo_disable===2){
  3458. // earlier than DOM change
  3459. }else{
  3460. if(comments && !comments.hasAttribute('hidden')) return; // visible comments content)
  3461. }
  3462. if(Q.comments_section_loaded===2) return; //already disabled
  3463.  
  3464.  
  3465. _console.log(2909,3)
  3466.  
  3467. _console.log(2909,4)
  3468. setCommentSection(2);
  3469.  
  3470. _console.log(2909,5)
  3471.  
  3472. let tabBtn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]');
  3473. if(tabBtn) {
  3474. let span = querySelectorFromAnchor.call(tabBtn,'span#tab3-txt-loader');
  3475. tabBtn.removeAttribute('loaded-comment')
  3476. if (!tabBtn.classList.contains('tab-btn-hidden')) {
  3477. //console.log('hide', comments, comments && comments.hasAttribute('hidden'))
  3478. hideTabBtn(tabBtn)
  3479. }
  3480. if(span){
  3481. span.textContent='';
  3482. }
  3483. }
  3484.  
  3485. akAttr(cssElm, 'tabview-youtube-comments', true, 'D');
  3486. _console.log(2909,10)
  3487.  
  3488.  
  3489. }
  3490.  
  3491.  
  3492.  
  3493.  
  3494. let layoutStatusMutex = new Mutex();
  3495.  
  3496. function forceDisplayChatReplay() {
  3497. let items = chatFrameElement('yt-live-chat-item-list-renderer #items');
  3498. if (items && items.childElementCount !== 0) return;
  3499.  
  3500. let ytd_player = document.querySelector('ytd-player#ytd-player');
  3501. if (!ytd_player) return;
  3502. let videoElm = querySelectorFromAnchor.call(ytd_player,'video');
  3503. if (!videoElm) return;
  3504.  
  3505. let video = videoElm;
  3506. if (videoElm && video.currentTime > 0 && !video.ended && video.readyState > video.HAVE_CURRENT_DATA) {
  3507. let chat = document.querySelector('ytd-live-chat-frame#chat');
  3508. if (chat) {
  3509. nativeFunc(chat, "postToContentWindow", [{ "yt-player-video-progress": videoElm.currentTime }])
  3510. }
  3511. }
  3512.  
  3513. }
  3514.  
  3515.  
  3516.  
  3517. function addIframeStyle(cDoc){
  3518.  
  3519.  
  3520. if(cDoc.querySelector('#userscript-tabview-chatroom-css')) return false;
  3521.  
  3522. addStyle(`
  3523.  
  3524.  
  3525. body #input-panel.yt-live-chat-renderer::after {
  3526. background: transparent;
  3527. }
  3528. .style-scope.yt-live-chat-item-list-renderer{
  3529. box-sizing: border-box;
  3530. }
  3531. yt-live-chat-text-message-renderer:nth-last-child(-n+30):hover #menu.yt-live-chat-text-message-renderer{
  3532. transition-delay: 87ms;
  3533. }
  3534. yt-live-chat-text-message-renderer #menu.yt-live-chat-text-message-renderer{
  3535. transition-delay: 1ms;
  3536. }
  3537. #item.style-scope.yt-live-chat-item-list-renderer, #item-scroller.style-scope.yt-live-chat-item-list-renderer{
  3538. transition-delay: 42ms;
  3539. }
  3540. yt-live-chat-item-list-renderer img[alt] {
  3541. pointer-events: auto;
  3542. }
  3543. body yt-live-chat-item-list-renderer img[alt] ~ tp-yt-paper-tooltip, body yt-live-chat-item-list-renderer #image ~ tp-yt-paper-tooltip {
  3544. --paper-tooltip-delay-in: 120ms !important;
  3545. white-space: nowrap;
  3546. }
  3547. #items.style-scope.yt-live-chat-item-list-renderer > yt-live-chat-text-message-renderer.yt-live-chat-item-list-renderer{
  3548. --tabview-chat-message-display: block;
  3549. --tabview-chat-message-mt: 2px;
  3550. --tabview-chat-message-mb: 4px;
  3551. }
  3552. #message.yt-live-chat-text-message-renderer {
  3553. display: var(--tabview-chat-message-display);
  3554. margin-top: var(--tabview-chat-message-mt);
  3555. margin-bottom: var(--tabview-chat-message-mb);
  3556. }
  3557. [collapsed] #message.yt-live-chat-text-message-renderer{
  3558. --tabview-chat-message-display: 'VOID';
  3559. --tabview-chat-message-mt: 'VOID';
  3560. --tabview-chat-message-mb: 'VOID';
  3561. }
  3562. @supports (contain: layout paint style){
  3563. body yt-live-chat-app{
  3564. contain: size layout paint style;
  3565. content-visibility: auto;
  3566. transform: translate3d(0,0,0);
  3567. overflow: hidden;
  3568. }
  3569. #items.style-scope.yt-live-chat-item-list-renderer,
  3570. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  3571. contain: layout paint style;
  3572. }
  3573. #item-scroller.style-scope.yt-live-chat-item-list-renderer{
  3574. contain: size style;
  3575. }
  3576. #contents.style-scope.yt-live-chat-item-list-renderer,
  3577. #chat.style-scope.yt-live-chat-renderer,
  3578. img.style-scope.yt-img-shadow[width][height]{
  3579. contain: size layout paint style;
  3580. }
  3581. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label],
  3582. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label] > #container {
  3583. contain: layout paint style;
  3584. }
  3585. yt-img-shadow#author-photo.style-scope{
  3586. contain: layout paint style;
  3587. content-visibility: auto;
  3588. contain-intrinsic-size: 24px 24px;
  3589. }
  3590. yt-live-chat-text-message-renderer:not([author-is-owner]) #author-photo.style-scope.yt-live-chat-text-message-renderer, yt-live-chat-text-message-renderer:not([author-is-owner]) yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer{
  3591. pointer-events: none;
  3592. }
  3593. 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 {
  3594. cursor: default;
  3595. }
  3596. yt-live-chat-text-message-renderer:not([author-is-owner]) span#message.style-scope.yt-live-chat-text-message-renderer,
  3597. yt-live-chat-paid-message-renderer #message.yt-live-chat-paid-message-renderer,
  3598. yt-live-chat-text-message-renderer:not([author-is-owner]) #timestamp.style-scope.yt-live-chat-text-message-renderer,
  3599. yt-live-chat-membership-item-renderer #header-content.style-scope.yt-live-chat-membership-item-renderer,
  3600. yt-live-chat-membership-item-renderer #timestamp.style-scope.yt-live-chat-membership-item-renderer,
  3601. yt-live-chat-paid-message-renderer #header-content.yt-live-chat-paid-message-renderer,
  3602. yt-live-chat-paid-message-renderer #timestamp.style-scope.yt-live-chat-paid-message-renderer,
  3603. yt-live-chat-paid-sticker-renderer #content.style-scope.yt-live-chat-paid-sticker-renderer,
  3604. yt-live-chat-paid-sticker-renderer #timestamp.style-scope.yt-live-chat-paid-sticker-renderer {
  3605. cursor: default;
  3606. pointer-events: none;
  3607. }
  3608. yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  3609. yt-live-chat-membership-item-renderer.style-scope.yt-live-chat-item-list-renderer,
  3610. yt-live-chat-paid-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  3611. yt-live-chat-banner-manager.style-scope.yt-live-chat-item-list-renderer {
  3612. contain: layout style;
  3613. }
  3614. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  3615. contain: layout paint style;
  3616. }
  3617. }
  3618. #chat-messages tp-yt-iron-dropdown#dropdown.style-scope.tp-yt-paper-menu-button{
  3619. margin-right: var(--ytd-margin-12x);
  3620. }
  3621. `, cDoc.documentElement).id='userscript-tabview-chatroom-css'
  3622.  
  3623. return true;
  3624.  
  3625. }
  3626.  
  3627. let _last_iframe = null;
  3628.  
  3629. function callFind(cDoc){
  3630.  
  3631. _last_iframe = cDoc;
  3632. if(!cDoc)return;
  3633.  
  3634. if(addIframeStyle(cDoc)===false)return;
  3635.  
  3636. let frc= 0;
  3637. let cid = 0;
  3638. let fullReady = ()=>{
  3639. if(!cDoc.documentElement.hasAttribute('style') && ++frc<900) return;
  3640. clearInterval(cid);
  3641. if (!scriptEnable || !isChatExpand()) return;
  3642.  
  3643. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  3644.  
  3645. if(!document.contains(iframe)) return;
  3646.  
  3647. if (cDoc.querySelector('yt-live-chat-renderer #continuations')) {
  3648. $(document.querySelector('ytd-live-chat-frame#chat')).attr('yt-userscript-iframe-loaded', '')
  3649. }
  3650.  
  3651. forceDisplayChatReplay();
  3652. iframe.dispatchEvent(new CustomEvent("tabview-chatroom-ready"))
  3653.  
  3654. }
  3655. cid = setInterval(fullReady,10)
  3656. fullReady();
  3657.  
  3658.  
  3659.  
  3660. }
  3661.  
  3662. function flexyAttr_toggleFlag(mFlag, b, flag) {
  3663. return b ? (mFlag | flag) : (mFlag & ~flag) ;
  3664. }
  3665.  
  3666. function flexAttr_toLayoutStatus(nls, attributeName) {
  3667.  
  3668. let attrElm, b, v;
  3669. switch (attributeName) {
  3670. case 'theater':
  3671. b = isTheater();
  3672. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_THEATER);
  3673. break;
  3674. case 'userscript-chat-collapsed':
  3675. case 'userscript-chatblock':
  3676. attrElm = kRef(ytdFlexy);
  3677. if (hasAttribute(attrElm, 'userscript-chat-collapsed')) {
  3678. nls = flexyAttr_toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLASPED);
  3679. } else {
  3680. nls = flexyAttr_toggleFlag(nls, hasAttribute(attrElm, 'userscript-chatblock'), LAYOUT_CHATROOM);
  3681. nls = flexyAttr_toggleFlag(nls, false, LAYOUT_CHATROOM_COLLASPED);
  3682. }
  3683. break;
  3684. case 'is-two-columns_':
  3685. b = isWideScreenWithTwoColumns();
  3686. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TWO_COLUMNS);
  3687. break;
  3688.  
  3689. case 'tabview-selection':
  3690. attrElm = kRef(ytdFlexy);
  3691. b = isNonEmptyString(attrElm.getAttribute('tabview-selection'));
  3692. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  3693. break;
  3694.  
  3695. case 'fullscreen':
  3696. attrElm = kRef(ytdFlexy);
  3697. b = attrElm.hasAttribute('fullscreen');
  3698. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  3699. break;
  3700.  
  3701. case 'userscript-engagement-panel':
  3702. attrElm = kRef(ytdFlexy);
  3703. v = attrElm.getAttribute('userscript-engagement-panel');
  3704. b = (+v > 0)
  3705. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  3706. break;
  3707.  
  3708. }
  3709.  
  3710. return nls;
  3711.  
  3712.  
  3713. }
  3714.  
  3715. let mtf_attrFlexy = (mutations, observer) => {
  3716.  
  3717. //attr mutation checker - $$ytdFlexyElm$$ {ytd-watch-flexy} \single
  3718. //::attr
  3719. // ~ 'userscript-chat-collapsed', 'userscript-chatblock', 'theater', 'is-two-columns_',
  3720. // ~ 'tabview-selection', 'fullscreen', 'userscript-engagement-panel',
  3721. // ~ 'hidden'
  3722.  
  3723. //console.log(15330, scriptEnable, kRef(ytdFlexy), mutations)
  3724.  
  3725. if (!scriptEnable) return;
  3726.  
  3727. const cssElm = kRef(ytdFlexy)
  3728. if (!cssElm) return;
  3729.  
  3730. if (!mutations) return;
  3731.  
  3732.  
  3733.  
  3734. const old_layoutStatus = wls.layoutStatus
  3735. if (old_layoutStatus === 0) return;
  3736. let new_layoutStatus = old_layoutStatus;
  3737.  
  3738. let checkedChat = false;
  3739.  
  3740. for (const mutation of mutations) {
  3741. new_layoutStatus = flexAttr_toLayoutStatus(new_layoutStatus, mutation.attributeName);
  3742. if (mutation.attributeName == 'userscript-chat-collapsed' || mutation.attributeName == 'userscript-chatblock') {
  3743.  
  3744. if(!checkedChat){
  3745. checkedChat = true; // avoid double call
  3746.  
  3747. if (cssElm.getAttribute('userscript-chatblock') === 'chat-live') {
  3748. // assigned new attribute - "chat-live" => disable comments section
  3749. _disableComments();
  3750. }
  3751.  
  3752. if (!cssElm.hasAttribute('userscript-chatblock')) {
  3753. // might or might not collapsed before
  3754. timeline.setTimeout(() => {
  3755. if (!scriptEnable) return;
  3756. //delayed call => check with the "no active focus" condition with chatroom status
  3757. if (!isAnyActiveTab() && !isChatExpand() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()) {
  3758. setToActiveTab();
  3759. }
  3760. }, 240);
  3761. }
  3762. }
  3763.  
  3764. }else if(mutation.attributeName == 'userscript-engagement-panel'){
  3765. // assume any other active component such as tab content and chatroom
  3766. if (+(cssElm.getAttribute('userscript-engagement-panel')||0)===0 && +mutation.oldValue>0) {
  3767. timeline.setTimeout(() => {
  3768. if (!scriptEnable) return;
  3769. //delayed call => check with the "no active focus" condition with engagement panel status
  3770. if (!isAnyActiveTab() && !isEngagementPanelExpanded() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen() && !isChatExpand()) {
  3771. setToActiveTab();
  3772. }
  3773. }, 240);
  3774. }
  3775. }
  3776. }
  3777.  
  3778. new_layoutStatus = fixLayoutStatus(new_layoutStatus);
  3779.  
  3780.  
  3781. if (new_layoutStatus !== old_layoutStatus) {
  3782. wls.layoutStatus = new_layoutStatus
  3783.  
  3784. // resize => is-two-columns_
  3785. if (((new_layoutStatus ^ old_layoutStatus) & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  3786. requestAnimationFrame(() => {
  3787. pageCheck();
  3788. singleColumnScrolling(true); //initalize sticky
  3789. })
  3790. }
  3791.  
  3792. }
  3793.  
  3794. }
  3795.  
  3796. const mtf_checkFlexy = () => {
  3797. // once per $$native-ytd-watch-flexy$$ {ytd-watch-flexy} detection
  3798.  
  3799. let ytdFlexyElm = kRef(ytdFlexy);
  3800. if (!scriptEnable || !ytdFlexyElm) return true;
  3801.  
  3802.  
  3803. wls.layoutStatus = 0;
  3804.  
  3805. let isFlexyHidden = (ytdFlexyElm.hasAttribute('hidden'));
  3806.  
  3807. if (!isFlexyHidden) {
  3808. let rChatExist = base_ChatExist();
  3809. if (rChatExist) {
  3810. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  3811. if (attr_chatblock === null) {
  3812. //remove attribute if it is unknown
  3813. attr_chatblock = false;
  3814. attr_chatcollapsed = false;
  3815. }
  3816. wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock)
  3817. _console.log(322,ytdFlexyElm.hasAttribute('userscript-chatblock'))
  3818. wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed)
  3819. }
  3820. }
  3821.  
  3822. let rTabSelection = [...querySelectorAllFromAnchor.call(ytdFlexyElm,'.tab-btn[userscript-tab-content]')]
  3823. .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') }));
  3824.  
  3825. if(rTabSelection.length === 0){
  3826. wAttr(ytdFlexyElm, 'tabview-selection', false);
  3827. }else{
  3828. rTabSelection=rTabSelection.filter(entry => entry.hidden !== true); // all available tabs
  3829. if (rTabSelection.length === 0) wAttr(ytdFlexyElm, 'tabview-selection', '');
  3830. }
  3831. rTabSelection = null;
  3832. let rEP = engagement_panels_();
  3833. if (rEP && rEP.count > 0) wAttr(ytdFlexyElm, 'userscript-engagement-panel', false);
  3834. //else wAttr(ytdFlexyElm, 'userscript-engagement-panel', rEP.value + ""); // can be 0
  3835. else if(rEP.value>0) wAttr(ytdFlexyElm, 'userscript-engagement-panel', rEP.value + ""); // can be 0
  3836.  
  3837. let ls = LAYOUT_VAILD;
  3838. ls = flexAttr_toLayoutStatus(ls, 'theater')
  3839. ls = flexAttr_toLayoutStatus(ls, 'userscript-chat-collapsed')
  3840. ls = flexAttr_toLayoutStatus(ls, 'userscript-chatblock')
  3841. ls = flexAttr_toLayoutStatus(ls, 'is-two-columns_')
  3842. ls = flexAttr_toLayoutStatus(ls, 'tabview-selection')
  3843. ls = flexAttr_toLayoutStatus(ls, 'fullscreen')
  3844. ls = flexAttr_toLayoutStatus(ls, 'userscript-engagement-panel')
  3845.  
  3846. fixLayoutStatus(ls)
  3847.  
  3848.  
  3849. wls.layoutStatus = ls
  3850.  
  3851. mtoFlexyAttr.bindElement(ytdFlexyElm,{
  3852. attributes: true,
  3853. attributeFilter: ['userscript-chat-collapsed', 'userscript-chatblock', 'theater', 'is-two-columns_', 'tabview-selection', 'fullscreen', 'userscript-engagement-panel', 'hidden'],
  3854. attributeOldValue: true
  3855. })
  3856.  
  3857.  
  3858.  
  3859. let columns = document.querySelector('ytd-page-manager#page-manager #columns')
  3860. if (columns) {
  3861. wAttr(columns, 'userscript-scrollbar-render', true);
  3862. }
  3863.  
  3864. return false;
  3865. }
  3866.  
  3867. function checkVisibleEngagementPanel(){
  3868. if(storeLastPanel){
  3869.  
  3870. let elm_storeLastPanel = kRef(storeLastPanel);
  3871.  
  3872. if(elm_storeLastPanel && !isDOMVisible(elm_storeLastPanel) ){
  3873. storeLastPanel=null;
  3874. ytBtnCloseEngagementPanels();
  3875. }
  3876.  
  3877. }
  3878.  
  3879. }
  3880.  
  3881.  
  3882.  
  3883.  
  3884. let switchTabActivity_lastTab = null
  3885.  
  3886. function setDisplayedPlaylist() {
  3887. //override the default youtube coding event prevention
  3888. let cssElm = kRef(ytdFlexy);
  3889. if (!scriptEnable || !cssElm) return;
  3890. displayedPlaylist = mWeakRef(document.querySelector('ytd-watch-flexy #tab-list:not(.tab-content-hidden) ytd-playlist-panel-renderer[tabview-true-playlist]') || null);
  3891. }
  3892.  
  3893.  
  3894.  
  3895. function switchTabActivity(activeLink) {
  3896. if (!scriptEnable) return;
  3897.  
  3898. const ytdFlexyElm = kRef(ytdFlexy);
  3899.  
  3900. if (!ytdFlexyElm) return;
  3901.  
  3902. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  3903.  
  3904. //if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null;
  3905.  
  3906.  
  3907. function runAtEnd() {
  3908.  
  3909. //console.log(12312)
  3910.  
  3911. if (activeLink) {
  3912. lstTab.lastTab = activeLink.getAttribute('userscript-tab-content')
  3913. lstTab.lastPanel = null;
  3914. }
  3915.  
  3916. displayedPlaylist = null;
  3917. scrollingVideosList = null;
  3918.  
  3919. if (activeLink && lstTab.lastTab == '#tab-list') {
  3920. setDisplayedPlaylist();
  3921. } else if (activeLink && lstTab.lastTab == '#tab-videos') {
  3922. scrollingVideosList = mWeakRef(document.querySelector('ytd-watch-flexy #tab-videos:not(.tab-content-hidden) [placeholder-videos]'));
  3923. }
  3924.  
  3925.  
  3926. ytdFlexyElm.setAttribute('tabview-selection', activeLink ? lstTab.lastTab : '')
  3927.  
  3928. if (activeLink && lstTab.lastTab == '#tab-comments' && (ytdFlexyElm.getAttribute('tabview-youtube-comments') || '').lastIndexOf('S') >= 0) {
  3929.  
  3930. if(mtf_forceCheckLiveVideo_disable===2) {}
  3931. else{
  3932. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'L');
  3933. _console.log(2909,3)
  3934.  
  3935. }
  3936.  
  3937. }
  3938.  
  3939.  
  3940.  
  3941. }
  3942.  
  3943. const links = document.querySelectorAll('#material-tabs a[userscript-tab-content]');
  3944.  
  3945. //console.log(701, activeLink)
  3946.  
  3947. for (const link of links) {
  3948. let content = document.querySelector(link.getAttribute('userscript-tab-content'));
  3949. if (link && content) {
  3950. if (link !== activeLink) {
  3951. link.classList.remove("active");
  3952. content.classList.add("tab-content-hidden");
  3953. } else {
  3954. //console.log(3343)
  3955. link.classList.add("active");
  3956. content.classList.remove("tab-content-hidden");
  3957. //timeline.setTimeout(()=>content.focus(),400);
  3958.  
  3959. }
  3960. }
  3961. }
  3962.  
  3963. runAtEnd();
  3964.  
  3965.  
  3966. }
  3967.  
  3968.  
  3969. const STORE_VERSION = 1;
  3970. const STORE_key = 'userscript-tabview-settings';
  3971. function getStore(){
  3972. let s = localStorage[STORE_key];
  3973. function resetStore(){
  3974. let ret = {
  3975. version: 1,
  3976. };
  3977. localStorage[STORE_key]=JSON.stringify(ret);
  3978. return ret;
  3979. }
  3980. if(!s) return resetStore();
  3981. let obj = null;
  3982. try{
  3983. obj = JSON.parse(s);
  3984. }catch(e){}
  3985. return obj && obj.version === STORE_VERSION ? obj : resetStore();
  3986. }
  3987.  
  3988. function setStore(obj){
  3989. if(!obj || typeof obj !=='object') return false;
  3990. if(obj.version !== STORE_VERSION) return false;
  3991. localStorage[STORE_key]=JSON.stringify(obj);
  3992. return true;
  3993. }
  3994.  
  3995. let tabsUiScript_setclick = false;
  3996.  
  3997. function prepareTabBtn() {
  3998.  
  3999. const materialTab = document.querySelector("#material-tabs")
  4000. if (!materialTab) return;
  4001.  
  4002. let noActiveTab = !!document.querySelector('ytd-watch-flexy[userscript-chatblock]:not([userscript-chat-collapsed])')
  4003.  
  4004. const activeLink = querySelectorFromAnchor.call(materialTab,'a[userscript-tab-content].active:not(.tab-btn-hidden)')
  4005. if (activeLink) switchTabActivity(noActiveTab ? null : activeLink)
  4006.  
  4007. if (!tabsUiScript_setclick) {
  4008. tabsUiScript_setclick = true;
  4009. $(materialTab).on("click", "a", function(evt) {
  4010.  
  4011. //console.log(8510)
  4012. let ytdFlexyElm = kRef(ytdFlexy);
  4013. if (!scriptEnable || !ytdFlexyElm) return null;
  4014.  
  4015. if (!this.hasAttribute('userscript-tab-content')) return;
  4016.  
  4017. if (evt.target.matches('.font-size-btn')) return;
  4018.  
  4019.  
  4020. evt.preventDefault();
  4021.  
  4022. //console.log(8511)
  4023.  
  4024. //console.log(8513)
  4025. new Promise(requestAnimationFrame).then(() => {
  4026.  
  4027.  
  4028. //console.log(8514)
  4029. layoutStatusMutex.lockWith(unlock => {
  4030.  
  4031. //console.log(8515)
  4032. switchTabActivity_lastTab = this.getAttribute('userscript-tab-content');
  4033.  
  4034. let isActiveAndVisible = this.classList.contains('tab-btn') && this.classList.contains('active') && !this.classList.contains('tab-btn-hidden')
  4035.  
  4036. if( isFullScreen() ){
  4037.  
  4038. console.log(isActiveAndVisible, this)
  4039. if(isActiveAndVisible){
  4040. timeline.setTimeout(unlock, 80);
  4041. switchTabActivity(null);
  4042. }else{
  4043.  
  4044. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  4045. ytBtnCollapseChat();
  4046. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  4047. ytBtnCloseEngagementPanels();
  4048. }
  4049.  
  4050.  
  4051. timeline.setTimeout(() => {
  4052. let scrollElement = document.querySelector('ytd-app[scrolling]')
  4053. if(!scrollElement) return;
  4054. // single column view; click button; scroll to tab content area 100%
  4055. let rightTabs = document.querySelector('#right-tabs');
  4056. let pTop = rightTabs.getBoundingClientRect().top - scrollElement.getBoundingClientRect().top
  4057. if (rightTabs && pTop > 0 && this.classList.contains('active')) {
  4058. rightTabs.scrollIntoView(true);
  4059. }
  4060. }, 60)
  4061.  
  4062. timeline.setTimeout(unlock, 80);
  4063. switchTabActivity(this)
  4064. }
  4065.  
  4066. } else if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) {
  4067. //optional
  4068. timeline.setTimeout(unlock, 80);
  4069. switchTabActivity(null);
  4070. ytBtnSetTheater();
  4071. } else if (isActiveAndVisible) {
  4072. timeline.setTimeout(unlock, 80);
  4073. switchTabActivity(null);
  4074. } else {
  4075.  
  4076. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  4077. ytBtnCollapseChat();
  4078. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  4079. ytBtnCloseEngagementPanels();
  4080. } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) {
  4081. ytBtnCancelTheater();
  4082. }
  4083.  
  4084. timeline.setTimeout(() => {
  4085. // single column view; click button; scroll to tab content area 100%
  4086. let rightTabs = document.querySelector('#right-tabs');
  4087. if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && this.classList.contains('active')) {
  4088. let tabButtonBar = document.querySelector('#material-tabs');
  4089. let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0;
  4090. window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight);
  4091. }
  4092. }, 60)
  4093. // _console.log(8519)
  4094.  
  4095. timeline.setTimeout(unlock, 80)
  4096. switchTabActivity(this)
  4097.  
  4098.  
  4099. }
  4100.  
  4101.  
  4102. })
  4103.  
  4104. })
  4105.  
  4106.  
  4107.  
  4108.  
  4109. });
  4110.  
  4111. function updateCSS_fontsize(){
  4112.  
  4113. let store = getStore();
  4114. let ytdFlexyElm = kRef(ytdFlexy);
  4115. if(ytdFlexyElm){
  4116. if(store['font-size-#tab-info']) ytdFlexyElm.style.setProperty('--ut2257-info', store['font-size-#tab-info'])
  4117. if(store['font-size-#tab-comments']) ytdFlexyElm.style.setProperty('--ut2257-comments', store['font-size-#tab-comments'])
  4118. if(store['font-size-#tab-videos']) ytdFlexyElm.style.setProperty('--ut2257-videos', store['font-size-#tab-videos'])
  4119. if(store['font-size-#tab-list']) ytdFlexyElm.style.setProperty('--ut2257-list', store['font-size-#tab-list'])
  4120. }
  4121.  
  4122. }
  4123.  
  4124. $(materialTab).on("click", ".font-size-btn", function(evt){
  4125.  
  4126. evt.preventDefault();
  4127. evt.stopPropagation();
  4128. evt.stopImmediatePropagation();
  4129.  
  4130. let value = evt.target.matches('.font-size-plus')?1: evt.target.matches('.font-size-minus')?-1 :0;
  4131.  
  4132. let active_tab_content = closestDOM.call(evt.target,'[userscript-tab-content]').getAttribute('userscript-tab-content');
  4133.  
  4134. let store = getStore();
  4135. let settingKey = `font-size-${active_tab_content}`
  4136. if(!store[settingKey]) store[settingKey] = 1.0;
  4137. if(value<0) store[settingKey] -= 0.05;
  4138. else if(value>0) store[settingKey] += 0.05;
  4139. if(store[settingKey]<0.1) store[settingKey] = 0.1;
  4140. else if(store[settingKey]>10) store[settingKey] = 10.0;
  4141. setStore(store);
  4142.  
  4143.  
  4144. updateCSS_fontsize();
  4145.  
  4146.  
  4147. //console.log(this.textContent)
  4148.  
  4149.  
  4150. });
  4151.  
  4152. updateCSS_fontsize();
  4153.  
  4154.  
  4155.  
  4156. }
  4157.  
  4158. }
  4159.  
  4160.  
  4161. // ---------------------------------------------------------------------------------------------
  4162. document.addEventListener("yt-navigate-finish", onNavigationEnd, bubblePassive)
  4163. //yt-navigate-redirect
  4164. //"yt-page-data-fetched"
  4165. //yt-navigate-error
  4166. //yt-navigate-start
  4167. //yt-page-manager-navigate-start
  4168. //"yt-navigate"
  4169. //"yt-navigate-cache
  4170.  
  4171. document.addEventListener("yt-navigate-cache",()=>{
  4172. console.log('yt-navigate-cache')
  4173. },bubblePassive)
  4174. document.addEventListener("yt-navigate-redirect",()=>{
  4175. console.log('yt-navigate-redirect')
  4176.  
  4177.  
  4178.  
  4179. script_inject_js1.inject();
  4180.  
  4181. },bubblePassive)
  4182.  
  4183. function onReady(){
  4184. //might be earlier than yt-navigation-finish
  4185. console.log('html-onReady')
  4186. if(location.pathname=='/watch') script_inject_js1.inject();
  4187. }
  4188.  
  4189. if(document.readyState!=='loading'){
  4190. onReady();
  4191. }else{
  4192. document.addEventListener('DOMContentLoaded', onReady, bubblePassive)
  4193. }
  4194.  
  4195. function forceConfig(){
  4196. }
  4197.  
  4198. document.addEventListener("yt-navigate-start",()=>{
  4199. console.log('yt-navigate-start') // not always trigger before navigate-end
  4200.  
  4201. script_inject_js1.inject();
  4202. forceConfig();
  4203. },bubblePassive)
  4204.  
  4205. document.addEventListener("yt-page-manager-navigate-start",()=>{
  4206. console.log('yt-page-manager-navigate-start')
  4207. forceConfig();
  4208. },bubblePassive)
  4209.  
  4210.  
  4211.  
  4212. // ---------------------------------------------------------------------------------------------
  4213.  
  4214. let scrolling_lastD = 0;
  4215.  
  4216. const singleColumnScrolling = function(/** @type {boolean} */ scrolling_lastF) {
  4217. if (!scriptEnable || pageType!=='watch') return;
  4218.  
  4219. let pageY = 0;
  4220.  
  4221. if(scrolling_lastD === 0){
  4222. if( (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS){
  4223. return;
  4224. }
  4225. pageY = scrollY;
  4226. if (pageY < 10 && !scrolling_lastF) return;
  4227. }else{
  4228. pageY = scrollY;
  4229. }
  4230.  
  4231. let targetElm, header, navElm;
  4232.  
  4233. _console.log(7891,'scrolling')
  4234.  
  4235. Promise.resolve().then(() => {
  4236.  
  4237. targetElm = document.querySelector("#right-tabs");
  4238. if (!targetElm) return;
  4239. header = querySelectorFromAnchor.call(targetElm,"header");
  4240. if (!header) return;
  4241. navElm = document.querySelector('#masthead-container, #masthead')
  4242. if (!navElm) return;
  4243. let navHeight = navElm ? navElm.offsetHeight : 0
  4244.  
  4245. let elmY = targetElm.offsetTop
  4246.  
  4247. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  4248.  
  4249. let xyStatus = 0
  4250. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  4251. // 1
  4252. xyStatus = 1
  4253. }
  4254.  
  4255. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  4256.  
  4257. //2
  4258. xyStatus = 2
  4259.  
  4260. }
  4261.  
  4262. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  4263. // 3
  4264.  
  4265. xyStatus = 3
  4266.  
  4267.  
  4268. }
  4269.  
  4270. return xyStatus;
  4271.  
  4272. }).then((xyStatus) => {
  4273.  
  4274. if ((xyStatus == 2 || xyStatus == 3) && (scrolling_lastD === 0 || scrolling_lastF)) {
  4275. scrolling_lastD = 1;
  4276. let {
  4277. offsetHeight
  4278. } = header
  4279. let {
  4280. offsetWidth
  4281. } = targetElm
  4282.  
  4283. targetElm.style.setProperty("--userscript-sticky-width", offsetWidth + 'px')
  4284. targetElm.style.setProperty("--userscript-sticky", offsetHeight + 'px')
  4285.  
  4286. wAttr(targetElm, 'userscript-sticky', true);
  4287.  
  4288. } else if ((xyStatus == 1) && (scrolling_lastD === 1 || scrolling_lastF)) {
  4289. scrolling_lastD = 0;
  4290.  
  4291. wAttr(targetElm, 'userscript-sticky', false);
  4292. }
  4293.  
  4294.  
  4295. targetElm = null;
  4296. header = null;
  4297. navElm = null;
  4298.  
  4299. });
  4300.  
  4301. };
  4302.  
  4303. window.addEventListener("scroll", function() {
  4304. singleColumnScrolling(false)
  4305. }, bubblePassive)
  4306.  
  4307. //let lastResizeAt = 0;
  4308. window.addEventListener('resize', function() {
  4309.  
  4310. if (!scriptEnable) return;
  4311. if (pageType!=='watch') return;
  4312. //lastResizeAt = Date.now();
  4313.  
  4314. if((wls.layoutStatus & LAYOUT_TWO_COLUMNS) !== LAYOUT_TWO_COLUMNS){
  4315.  
  4316. requestAnimationFrame(() => {
  4317. singleColumnScrolling(true)
  4318. })
  4319.  
  4320. }
  4321.  
  4322.  
  4323. }, bubblePassive)
  4324.  
  4325.  
  4326.  
  4327. function resetBuggyLayoutForNewVideoPage() {
  4328.  
  4329. let ytdFlexyElm = kRef(ytdFlexy);
  4330. if (!ytdFlexyElm) return;
  4331.  
  4332. //(flexy is visible and watch video page)
  4333.  
  4334. scriptEnable = true;
  4335.  
  4336. //mtf_forceCheckLiveVideo_disable = 0;
  4337. _console.log(27056)
  4338.  
  4339.  
  4340. let new_layoutStatus = wls.layoutStatus
  4341.  
  4342. new_layoutStatus & (LAYOUT_CHATROOM_COLLASPED | LAYOUT_CHATROOM)
  4343.  
  4344. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  4345. const new_isCollaspedChat = (new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  4346.  
  4347. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  4348. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  4349. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  4350. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  4351. const new_isExpandEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  4352.  
  4353. if (ytdFlexyElm.getAttribute('tabview-selection') === '' && new_isTwoColumns && !new_isTheater && !new_isTabExpanded && !new_isFullScreen && !new_isExpandEPanel && !new_isExpandedChat) {
  4354. // e.g. engage panel removed after miniview and change video
  4355. setToActiveTab();
  4356. } else if (new_isExpandEPanel && querySelectorAllFromAnchor.call(ytdFlexyElm, 'ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]').length === 0) {
  4357. wls.layoutStatus = new_layoutStatus & (~LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  4358. }
  4359.  
  4360.  
  4361.  
  4362.  
  4363. }
  4364.  
  4365. function newVideoPage(evt_detail) {
  4366.  
  4367. console.log('newVideoPage')
  4368. pendingFetch = null
  4369. fetched = false;
  4370.  
  4371. document.dispatchEvent(new CustomEvent('tabview-v-change')) //possible duplicated
  4372.  
  4373.  
  4374.  
  4375. //console.log('newVideoPage-', 150, location.href)
  4376.  
  4377. let ytdFlexyElm = kRef(ytdFlexy);
  4378. if (!ytdFlexyElm) return;
  4379.  
  4380.  
  4381. timeline.reset();
  4382. layoutStatusMutex = new Mutex();
  4383.  
  4384. //console.log('newVideoPage-', 350, location.href)
  4385.  
  4386.  
  4387. if (pageType === 'watch') {
  4388. resetBuggyLayoutForNewVideoPage();
  4389.  
  4390. }
  4391.  
  4392.  
  4393.  
  4394.  
  4395.  
  4396.  
  4397. pendingFetch = null;
  4398. _innerCommentsLoader();
  4399. if (!fetched) {
  4400. window.dispatchEvent(new Event("scroll"));
  4401. }
  4402.  
  4403. let liveChatRenderer = null;
  4404. let isReplay = null;
  4405. try {
  4406.  
  4407. liveChatRenderer = evt_detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  4408. } catch (e) { }
  4409. if (liveChatRenderer) {
  4410.  
  4411. if (liveChatRenderer.isReplay === true) isReplay = true;
  4412. }
  4413.  
  4414. const chatBlockR = liveChatRenderer ? (isReplay ? 3 : 1) : 0
  4415. const initialDisplayState = liveChatRenderer ? liveChatRenderer.initialDisplayState : null;
  4416.  
  4417.  
  4418.  
  4419.  
  4420. let f = () => {
  4421.  
  4422. _console.log(932, 1, 1)
  4423. let ytdFlexyElm = kRef(ytdFlexy);
  4424. if (!scriptEnable || !ytdFlexyElm) return;
  4425.  
  4426. _console.log(932, 1, 2)
  4427. if (pageType !== 'watch') return;
  4428.  
  4429. _console.log(932, 1, 3)
  4430.  
  4431. if (Q.mtf_chatBlockQ !== chatBlockR) {
  4432. Q.mtf_chatBlockQ = chatBlockR
  4433.  
  4434. let [attr_chatblock, attr_chatcollapsed] = (chatBlockR & 1) ? [
  4435. chatBlockR === 1 ? 'chat-live' : chatBlockR === 3 ? 'chat-playback' : false,
  4436. initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED' ? true : false
  4437. ] : [false, false];
  4438.  
  4439. _console.log(932, 2, attr_chatblock, attr_chatcollapsed)
  4440.  
  4441. //LIVE_CHAT_DISPLAY_STATE_COLLAPSED
  4442. //LIVE_CHAT_DISPLAY_STATE_EXPANDED
  4443. wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock)
  4444. wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed)
  4445.  
  4446. _console.log(932, 3, ytdFlexyElm.hasAttribute('userscript-chatblock'))
  4447.  
  4448.  
  4449.  
  4450.  
  4451. if (pageType === 'watch') { // reset info when hidden
  4452. checkVisibleEngagementPanel();
  4453. }
  4454.  
  4455. if (attr_chatblock == 'chat-live') {
  4456.  
  4457. _console.log(932, 4)
  4458.  
  4459. mtf_forceCheckLiveVideo_disable = 2;
  4460.  
  4461. _disableComments();
  4462.  
  4463.  
  4464. } else {
  4465.  
  4466. _console.log(932, 5)
  4467. mtf_forceCheckLiveVideo_disable = 0;
  4468. setCommentSection(0);
  4469. restoreFetching();
  4470.  
  4471. _console.log(932, 6, mtf_forceCheckLiveVideo_disable)
  4472.  
  4473. if (mtf_forceCheckLiveVideo_disable !== 2) {
  4474.  
  4475. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"].tab-btn-hidden')
  4476. if (tabBtn) {
  4477. emptyCommentSection();
  4478. tabBtn.classList.remove("tab-btn-hidden")
  4479. }
  4480.  
  4481. }
  4482.  
  4483.  
  4484.  
  4485. }
  4486.  
  4487.  
  4488. } else {
  4489.  
  4490. // restore Fetching only
  4491.  
  4492.  
  4493. let [attr_chatblock, attr_chatcollapsed] = (chatBlockR & 1) ? [
  4494. chatBlockR === 1 ? 'chat-live' : chatBlockR === 3 ? 'chat-playback' : false,
  4495. initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED' ? true : false
  4496. ] : [false, false];
  4497.  
  4498. if (mtf_forceCheckLiveVideo_disable !== 2 && (attr_chatblock === false || attr_chatblock === 'chat-playback')) {
  4499.  
  4500.  
  4501. setCommentSection(0);
  4502. restoreFetching();
  4503.  
  4504. }
  4505.  
  4506.  
  4507.  
  4508. }
  4509.  
  4510. }
  4511.  
  4512. f();
  4513.  
  4514.  
  4515.  
  4516.  
  4517.  
  4518. }
  4519.  
  4520.  
  4521. document.addEventListener('wheel', function(evt) {
  4522.  
  4523. if (!scriptEnable) return;
  4524. const displayedPlaylist_element = kRef(displayedPlaylist);
  4525. if (displayedPlaylist_element && elementContains.call(displayedPlaylist_element, evt.target)) {
  4526. evt.stopPropagation();
  4527. evt.stopImmediatePropagation();
  4528. }
  4529. }, capturePassive);
  4530.  
  4531.  
  4532. function setVideosTwoColumns(/** @type {number} */ flag, /** @type {boolean} */ bool) {
  4533.  
  4534. //two columns to one column
  4535.  
  4536. /*
  4537. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  4538.  
  4539. is-two-columns ="" => no is-two-columns
  4540. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  4541. no hidden => hidden =""
  4542. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  4543. hidden ="" => no hidden
  4544.  
  4545. */
  4546.  
  4547. let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  4548.  
  4549. let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  4550.  
  4551. let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  4552.  
  4553. let res = {}
  4554. if (flag & 1) {
  4555. res.m1 = document.querySelector(cssSelector1)
  4556. if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false);
  4557. }
  4558.  
  4559. if (flag & 2) {
  4560. res.m2 = document.querySelector(cssSelector2)
  4561. if (res.m2) wAttr(res.m2, 'hidden', bool ? false : '');
  4562. }
  4563.  
  4564. if (flag & 4) {
  4565. res.m3 = document.querySelector(cssSelector3)
  4566. if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false);
  4567. }
  4568.  
  4569.  
  4570. return res
  4571.  
  4572.  
  4573.  
  4574.  
  4575. }
  4576.  
  4577. let lastScrollFetch = 0;
  4578. // function isScrolledToEnd(){
  4579. // return (window.innerHeight + window.pageYOffset) >= document.scrollingElement.scrollHeight - 2;
  4580. // }
  4581. let lastOffsetTop = 0;
  4582. window.addEventListener('scroll', function(evt) {
  4583.  
  4584. //console.log(evt.target)
  4585.  
  4586. if (!scriptEnable) return;
  4587.  
  4588. let isTwoCol = (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS
  4589. if(isTwoCol) return;
  4590.  
  4591. if (!kRef(scrollingVideosList)) return;
  4592. if (videoListBeforeSearch) return;
  4593.  
  4594.  
  4595.  
  4596. let visibleHeight = document.scrollingElement.clientHeight;
  4597. let totalHeight = document.scrollingElement.scrollHeight;
  4598.  
  4599. if (totalHeight < visibleHeight * 1.5) return; // filter out two column view;
  4600.  
  4601. let z = window.pageYOffset + visibleHeight;
  4602. let h_advanced = totalHeight - (visibleHeight > 5 * 40 ? visibleHeight * 0.5 : 40);
  4603.  
  4604.  
  4605.  
  4606. if (z > h_advanced) {
  4607.  
  4608. let ct = Date.now();
  4609. if (ct - lastScrollFetch < 500) return; //prevent continuous calling
  4610.  
  4611. lastScrollFetch = ct;
  4612.  
  4613. let res = setVideosTwoColumns(2 | 4, true)
  4614. if (res.m3 && res.m2) {
  4615.  
  4616. //wait for DOM change, just in case
  4617. requestAnimationFrame(() => {
  4618. let { offsetTop } = res.m2 // as visibility of m2 & m3 switched.
  4619.  
  4620. if (offsetTop - lastOffsetTop < 40) return; // in case bug, or repeating calling. // the next button shall below the this button
  4621. lastOffsetTop = offsetTop
  4622.  
  4623. res.m2.parentNode.dispatchEvent(new Event('yt-service-request-sent-button-renderer'))
  4624.  
  4625. res = null
  4626. })
  4627.  
  4628. } else {
  4629.  
  4630. res = null
  4631. }
  4632.  
  4633.  
  4634. }
  4635.  
  4636.  
  4637.  
  4638.  
  4639. }, bubblePassive)
  4640.  
  4641. let fetchPendings = [];
  4642. let mtf_forceCheckLiveVideo_disable = 0;
  4643.  
  4644. let storeLastPanel = null;
  4645.  
  4646.  
  4647.  
  4648.  
  4649.  
  4650. let mgChatFrame = {
  4651. setVar(elm) {
  4652. mgChatFrame.kVar = mWeakRef(elm)
  4653. },
  4654. getVar() {
  4655. return kRef(mgChatFrame.kVar)
  4656. },
  4657. inPage() {
  4658. let elm = mgChatFrame.getVar();
  4659. if (!elm) return false;
  4660. let ytdFlexyElm = kRef(ytdFlexy);
  4661. if(!ytdFlexyElm) return false;
  4662. return elementContains.call(ytdFlexyElm, elm)
  4663. }
  4664. };
  4665.  
  4666. const timeline = {
  4667. // after initialized (initObserver)
  4668. cn1:{},
  4669. cn2:{},
  4670. setTimeout( /** @type {TimerHandler} */ f,/** @type {number} */ d){
  4671. let cid = setTimeout(f,d)
  4672. timeline.cn1[cid]=true
  4673. return cid;
  4674. },
  4675. clearTimeout(/** @type {number} */ cid){
  4676. timeline.cn1[cid]=false; return clearTimeout(cid)
  4677. },
  4678. setInterval(/** @type {TimerHandler} */ f,/** @type {number} */ d){
  4679. let cid = setInterval(f,d);
  4680. timeline.cn2[cid]=true
  4681. return cid;
  4682. },
  4683. clearInterval(/** @type {number} */ cid){
  4684. timeline.cn2[cid]=false; return clearInterval(cid)
  4685. },
  4686. reset(){
  4687. for(let cid in timeline.cn1) timeline.cn1[cid] && clearTimeout(cid)
  4688. for(let cid in timeline.cn2) timeline.cn2[cid] && clearInterval(cid)
  4689. timeline.cn1={}
  4690. timeline.cn2={}
  4691. }
  4692. }
  4693.  
  4694. class AttributeMutationObserver extends MutationObserver {
  4695. constructor(flist){
  4696. super((mutations, observer)=>{
  4697. for(const mutation of mutations){
  4698. if (mutation.type === 'attributes') {
  4699. this.checker(mutation.target, mutation.attributeName)
  4700. }
  4701. }
  4702. })
  4703. this.flist=flist;
  4704. this.res={}
  4705. }
  4706. takeRecords(){
  4707. super.takeRecords();
  4708. }
  4709. disconnect(){
  4710. this._target = null;
  4711. super.disconnect();
  4712. }
  4713. observe(/** @type {Node} */ target){
  4714. if(this._target) return;
  4715. //console.log(123124, target)
  4716. this._target = mWeakRef(target);
  4717. //console.log(123125, kRef(this._target))
  4718. const options = {
  4719. attributes: true,
  4720. attributeFilter: Object.keys(this.flist),
  4721. //attributeFilter: [ "status", "username" ],
  4722. attributeOldValue: true
  4723. }
  4724. super.observe(target, options)
  4725. }
  4726. checker(/** @type {Node} */ target,/** @type {string} */ attributeName){
  4727. let nv = target.getAttribute(attributeName);
  4728. if(this.res[attributeName]!==nv){
  4729. this.res[attributeName] = nv
  4730. let f = this.flist[attributeName];
  4731. if(f) f(attributeName, nv);
  4732.  
  4733. }
  4734. }
  4735. check(delay = 0){
  4736. setTimeout(()=>{
  4737. let target = kRef(this._target)
  4738. if(target!==null){
  4739. for(const key of Object.keys(this.flist)){
  4740. this.checker(target,key)
  4741. }
  4742. }else{
  4743. console.log('target is null') //disconnected??
  4744. }
  4745. target = null;
  4746. },delay)
  4747. }
  4748. }
  4749.  
  4750. function goYoutubeGeniusLyrics() {
  4751.  
  4752. setTimeout(function $f() {
  4753.  
  4754. if (!document.documentElement.hasAttribute('w-engagement-panel-genius-lyrics')) return setTimeout($f, 100)
  4755.  
  4756. document.documentElement.dispatchEvent(new CustomEvent('engagement-panel-genius-lyrics'))
  4757.  
  4758.  
  4759. }, 100)
  4760.  
  4761.  
  4762.  
  4763. }
  4764.  
  4765.  
  4766.  
  4767. //Object.keys($0).filter(key=>!(key in $0.constructor.prototype))
  4768.  
  4769. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML'))
  4770. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML')).filter(k=>$0 instanceof window[k])
  4771.  
  4772.  
  4773. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  4774.  
  4775. /*
  4776. fix bug for comment section - version 1.8.7
  4777. This issue is the bug in browser's rendering
  4778. I guess, this is due to the lines clamp with display:-webkit-box
  4779. use stupid coding to let it re-render when its content become visible
  4780. /*
  4781.  
  4782. ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander {
  4783. color: var(--yt-spec-text-primary);
  4784. display: -webkit-box;
  4785. overflow: hidden;
  4786. max-height: none;
  4787. -webkit-box-orient: vertical;
  4788. -webkit-line-clamp: var(--ytd-expander-max-lines, 4);
  4789. }
  4790.  
  4791. // v1.8.36 imposed a effective solution for fixing this bug
  4792.  
  4793. */
  4794.  
  4795. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  4796.  
  4797.  
  4798. /**
  4799. *
  4800.  
  4801.  
  4802. f.initChildrenObserver=function(){var a=this;this.observer=new MutationObserver(function(){return a.childrenChanged()});
  4803. this.observer.observe(this.$.content,{subtree:!0,childList:!0,attributes:!0});this.childrenChanged()};
  4804. 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()})}))};
  4805.  
  4806.  
  4807. f.onIronResize=function(){this.recomputeOnResize&&this.childrenChanged()};
  4808.  
  4809.  
  4810. onButtonClick_:function(){this.fire("yt-close-upsell-dialog")},
  4811. 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};
  4812. n(xI,L);f=xI.prototype;f.alwaysToggleableChanged=function(){this.alwaysToggleable&&(this.canToggle=!0)};
  4813.  
  4814.  
  4815. f.calculateCanCollapse=function(){this.canToggle=this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  4816. f.detachObserver=function(){this.observer&&this.observer.disconnect()};
  4817.  
  4818. *
  4819. *
  4820. *
  4821. */
  4822.  
  4823.  
  4824.  
  4825.  
  4826. })();
  4827.  
  4828.  
  4829.  
  4830.  
  4831.  
  4832.  
  4833.  
  4834.  
  4835.  
  4836.  
  4837.  
  4838.  
  4839.  
  4840.  
  4841.  
  4842.  
  4843.  
  4844.  
  4845.  
  4846.  
  4847.  
  4848.  
  4849.  
  4850.  
  4851.  
  4852.  
  4853.  
  4854.  
  4855.  
  4856.  
  4857.  
  4858.  
  4859.  
  4860.  
  4861.  
  4862.  
  4863.  
  4864.  
  4865.  
  4866.  
  4867.  
  4868. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  4869.  
  4870. }
  4871.  
  4872.  
  4873. ;!(function $$() {
  4874. 'use strict';
  4875.  
  4876. if(document.documentElement==null) return window.requestAnimationFrame($$)
  4877.  
  4878. const cssTxt = GM_getResourceText("contentCSS");
  4879.  
  4880. function addStyle (styleText) {
  4881. const styleNode = document.createElement('style');
  4882. styleNode.type = 'text/css';
  4883. styleNode.textContent = styleText;
  4884. document.documentElement.appendChild(styleNode);
  4885. return styleNode;
  4886. }
  4887.  
  4888. addStyle (cssTxt);
  4889.  
  4890. main(window.$);
  4891.  
  4892.  
  4893. // Your code here...
  4894. })();

QingJ © 2025

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