YouTube 超快聊天

YouTube直播聊天的终极性能提升

当前为 2023-08-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Super Fast Chat
  3. // @version 0.21.0
  4. // @license MIT
  5. // @name:ja YouTube スーパーファーストチャット
  6. // @name:zh-TW YouTube 超快聊天
  7. // @name:zh-CN YouTube 超快聊天
  8. // @icon https://github.com/cyfung1031/userscript-supports/raw/main/icons/super-fast-chat.png
  9. // @namespace UserScript
  10. // @match https://www.youtube.com/live_chat*
  11. // @match https://www.youtube.com/live_chat_replay*
  12. // @author CY Fung
  13. // @run-at document-start
  14. // @grant none
  15. // @unwrap
  16. // @allFrames true
  17. // @inject-into page
  18. //
  19. // @compatible firefox Violentmonkey
  20. // @compatible firefox Tampermonkey
  21. // @compatible firefox FireMonkey
  22. // @compatible chrome Violentmonkey
  23. // @compatible chrome Tampermonkey
  24. // @compatible opera Violentmonkey
  25. // @compatible opera Tampermonkey
  26. // @compatible safari Stay
  27. // @compatible edge Violentmonkey
  28. // @compatible edge Tampermonkey
  29. // @compatible brave Violentmonkey
  30. // @compatible brave Tampermonkey
  31. //
  32. // @description Ultimate Performance Boost for YouTube Live Chats
  33. // @description:ja YouTubeのライブチャットの究極のパフォーマンスブースト
  34. // @description:zh-TW YouTube直播聊天的終極性能提升
  35. // @description:zh-CN YouTube直播聊天的终极性能提升
  36. //
  37. // ==/UserScript==
  38.  
  39. ((__CONTEXT__) => {
  40. 'use strict';
  41.  
  42. const ENABLE_REDUCED_MAXITEMS_FOR_FLUSH = true; // TRUE to enable trimming down to 25 messages when there are too many unrendered messages
  43. const MAX_ITEMS_FOR_TOTAL_DISPLAY = 90; // By default, 250 latest messages will be displayed, but displaying 90 messages is already sufficient.
  44. const MAX_ITEMS_FOR_FULL_FLUSH = 25; // If there are too many new (stacked) messages not yet rendered, clean all and flush 25 latest messages then incrementally added back to 90 messages
  45.  
  46. const ENABLE_NO_SMOOTH_TRANSFORM = true; // Depends on whether you want the animation effect for new chat messages
  47. const USE_OPTIMIZED_ON_SCROLL_ITEMS = true; // TRUE for the majority
  48. const USE_WILL_CHANGE_CONTROLLER = false; // FALSE for the majority
  49. const ENABLE_FULL_RENDER_REQUIRED_PREFERRED = true; // In Chrome, the rendering of new chat messages could be too fast for no smooth transform. 80ms delay of displaying new messages should be sufficient for element rendering.
  50. const ENABLE_OVERFLOW_ANCHOR_PREFERRED = true; // Enable `overflow-anchor: auto` to lock the scroll list at the bottom for no smooth transform.
  51.  
  52. const FIX_SHOW_MORE_BUTTON_LOCATION = true; // When there are voting options (bottom panel), move the "show more" button to the top.
  53. const FIX_INPUT_PANEL_OVERFLOW_ISSUE = true; // When the super chat button is flicking with color, the scrollbar might come out.
  54. const FIX_INPUT_PANEL_BORDER_ISSUE = true; // No border should be allowed if there is an empty input panel.
  55. const SET_CONTAIN_FOR_CHATROOM = true; // Rendering hacks (`contain`) for chatroom elements. [ General ]
  56.  
  57. const FORCE_CONTENT_VISIBILITY_UNSET = true; // Content-visibility should be always VISIBLE for high performance and great rendering.
  58. const FORCE_WILL_CHANGE_UNSET = true; // Will-change should be always UNSET (auto) for high performance and low energy impact.
  59.  
  60. // Replace requestAnimationFrame timers with custom implementation
  61. const ENABLE_RAF_HACK_TICKERS = true; // When there is a ticker
  62. const ENABLE_RAF_HACK_DOCKED_MESSAGE = true; // To be confirmed
  63. const ENABLE_RAF_HACK_INPUT_RENDERER = true; // To be confirmed
  64. const ENABLE_RAF_HACK_EMOJI_PICKER = true; // When changing the page of the emoji picker
  65.  
  66. // Force rendering all the character subsets of the designated font(s) before messages come (Pre-Rendering of Text)
  67. const ENABLE_FONT_PRE_RENDERING_PREFERRED = 1 | 2 | 4 | 8 | 16;
  68.  
  69. // Backdrop `filter: blur(4px)` inside the iframe can extend to the whole page, causing a negative visual impact on the video you are watching.
  70. const NO_BACKDROP_FILTER_WHEN_MENU_SHOWN = true;
  71.  
  72. // Data Manipulation for Participants (Participant List)
  73. const DO_PARTICIPANT_LIST_HACKS = true; // TRUE for the majority
  74. const SHOW_PARTICIPANT_CHANGES_IN_CONSOLE = false; // Just too annoying to show them all in popular chat
  75. const CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT = true; // Only consider changes in renderable content (not concerned with the last chat message of the participants)
  76. const PARTICIPANT_UPDATE_ONLY_ONLY_IF_MODIFICATION_DETECTED = true;
  77.  
  78. // show more button
  79. const ENABLE_SHOW_MORE_BLINKER = true; // BLINK WHEN NEW MESSAGES COME
  80.  
  81. // faster stampDomArray_ for participants list creation
  82. const ENABLE_FLAGS_MAINTAIN_STABLE_LIST_VAL = 1; // 0 - OFF; 1 - ON; 2 - ON(PARTICIPANTS_LIST ONLY)
  83. const USE_MAINTAIN_STABLE_LIST_ONLY_WHEN_KS_FLAG_IS_SET = false;
  84.  
  85. // reuse yt components
  86. const ENABLE_FLAGS_REUSE_COMPONENTS = true;
  87.  
  88. // images <Group#I01>
  89. const AUTHOR_PHOTO_SINGLE_THUMBNAIL = 1; // 0 - disable; 1- smallest; 2- largest
  90. const EMOJI_IMAGE_SINGLE_THUMBNAIL = 1; // 0 - disable; 1- smallest; 2- largest
  91. const LEAST_IMAGE_SIZE = 48; // minium size = 48px
  92.  
  93. const ENABLE_PRELOAD_THUMBNAIL = true;
  94. const ENABLE_BASE_PREFETCHING = true;
  95.  
  96. const FIX_SETSRC_AND_THUMBNAILCHANGE_ = true;
  97. const FIX_THUMBNAIL_DATACHANGED = true;
  98. const REMOVE_PRELOADAVATARFORADDACTION = true;
  99.  
  100. const FIX_THUMBNAIL_SIZE_ON_ITEM_ADDITION = true; // important [depends on <Group#I01>]
  101. const FIX_THUMBNAIL_SIZE_ON_ITEM_REPLACEMENT = true; // [depends on <Group#I01>]
  102.  
  103. // =======================================================================================================
  104.  
  105. // AUTOMAICALLY DETERMINED
  106. const ENABLE_FLAGS_MAINTAIN_STABLE_LIST = ENABLE_FLAGS_MAINTAIN_STABLE_LIST_VAL === 1;
  107. const ENABLE_FLAGS_MAINTAIN_STABLE_LIST_FOR_PARTICIPANTS_LIST = ENABLE_FLAGS_MAINTAIN_STABLE_LIST_VAL >= 1;
  108.  
  109. // image sizing code
  110. // (d = (d = KC(a.customThumbnail.thumbnails, 16)) ? lc(oc(d)) : null)
  111.  
  112. // function KC(a, b, c, d) {
  113. // d = void 0 === d ? "width" : d;
  114. // if (!a || !a.length)
  115. // return null;
  116. // if (z("kevlar_tuner_should_always_use_device_pixel_ratio")) {
  117. // var e = window.devicePixelRatio;
  118. // z("kevlar_tuner_should_clamp_device_pixel_ratio") ? e = Math.min(e, zl("kevlar_tuner_clamp_device_pixel_ratio")) : z("kevlar_tuner_should_use_thumbnail_factor") && (e = zl("kevlar_tuner_thumbnail_factor"));
  119. // HC = e
  120. // } else
  121. // HC || (HC = window.devicePixelRatio);
  122. // e = HC;
  123. // z("kevlar_tuner_should_always_use_device_pixel_ratio") ? b *= e : 1 < e && (b *= e);
  124. // if (z("kevlar_tuner_min_thumbnail_quality"))
  125. // return a[0].url || null;
  126. // e = a.length;
  127. // if (z("kevlar_tuner_max_thumbnail_quality"))
  128. // return a[e - 1].url || null;
  129. // if (c)
  130. // for (var h = 0; h < e; h++)
  131. // if (0 <= a[h].url.indexOf(c))
  132. // return a[h].url || null;
  133. // for (c = 0; c < e; c++)
  134. // if (a[c][d] >= b)
  135. // return a[c].url || null;
  136. // for (b = e - 1; 0 < b; b--)
  137. // if (a[b][d])
  138. // return a[b].url || null;
  139. // return a[0].url || null
  140. // }
  141.  
  142. const { IntersectionObserver } = __CONTEXT__;
  143.  
  144. /** @type {globalThis.PromiseConstructor} */
  145. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  146.  
  147. if (!IntersectionObserver) return console.warn("Your browser does not support IntersectionObserver.\nPlease upgrade to the latest version.")
  148.  
  149. // necessity of cssText3_smooth_transform_position to be checked.
  150. const cssText3_smooth_transform_position = ENABLE_NO_SMOOTH_TRANSFORM ? `
  151.  
  152. #item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer {
  153. position: static !important;
  154. }
  155.  
  156. `: '';
  157.  
  158. // fallback if dummy style fn fails
  159. const cssText4_smooth_transform_forced_props = ENABLE_NO_SMOOTH_TRANSFORM ? `
  160.  
  161. /* optional */
  162. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  163. height: auto !important;
  164. min-height: unset !important;
  165. }
  166.  
  167. #items.style-scope.yt-live-chat-item-list-renderer {
  168. transform: translateY(0px) !important;
  169. }
  170.  
  171. /* optional */
  172.  
  173. `: '';
  174.  
  175. const cssText5 = SET_CONTAIN_FOR_CHATROOM ? `
  176.  
  177. /* ------------------------------------------------------------------------------------------------------------- */
  178.  
  179. yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip, yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer, yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer #image, yt-live-chat-author-chip #chat-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer #image img {
  180. contain: layout style;
  181. }
  182.  
  183. #items.style-scope.yt-live-chat-item-list-renderer {
  184. contain: layout paint style;
  185. }
  186.  
  187. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  188. contain: style;
  189. }
  190.  
  191. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  192. contain: size style;
  193. }
  194.  
  195. #contents.style-scope.yt-live-chat-item-list-renderer, #chat.style-scope.yt-live-chat-renderer, img.style-scope.yt-img-shadow[width][height] {
  196. contain: size layout paint style;
  197. }
  198.  
  199. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label], .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label] > #container {
  200. contain: layout paint style;
  201. }
  202.  
  203. yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer, yt-live-chat-membership-item-renderer.style-scope.yt-live-chat-item-list-renderer, yt-live-chat-paid-message-renderer.style-scope.yt-live-chat-item-list-renderer, yt-live-chat-banner-manager.style-scope.yt-live-chat-item-list-renderer {
  204. contain: layout style;
  205. }
  206.  
  207. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  208. contain: layout paint style;
  209. }
  210.  
  211. /* ------------------------------------------------------------------------------------------------------------- */
  212.  
  213. ` : '';
  214.  
  215. const cssText6b_show_more_button = FIX_SHOW_MORE_BUTTON_LOCATION ? `
  216.  
  217. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer{
  218. top: 4px;
  219. transition-property: top;
  220. bottom: unset;
  221. }
  222.  
  223. yt-live-chat-renderer[has-action-panel-renderer] #show-more.yt-live-chat-item-list-renderer[disabled]{
  224. top: -42px;
  225. }
  226.  
  227. `: '';
  228.  
  229. const cssText6c_input_panel_overflow = FIX_INPUT_PANEL_OVERFLOW_ISSUE ? `
  230.  
  231. #input-panel #picker-buttons yt-live-chat-icon-toggle-button-renderer#product-picker {
  232. contain: layout style;
  233. }
  234.  
  235. #chat.yt-live-chat-renderer ~ #panel-pages.yt-live-chat-renderer {
  236. overflow: visible;
  237. }
  238.  
  239. `: '';
  240.  
  241. const cssText6d_input_panel_border = FIX_INPUT_PANEL_BORDER_ISSUE ? `
  242.  
  243. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer:not(:empty) {
  244. --yt-live-chat-action-panel-top-border: none;
  245. }
  246.  
  247. html #panel-pages.yt-live-chat-renderer > #input-panel.yt-live-chat-renderer.iron-selected > *:first-child {
  248. border-top: 1px solid var(--yt-live-chat-panel-pages-border-color);
  249. }
  250.  
  251. html #panel-pages.yt-live-chat-renderer {
  252. border-top: 0;
  253. border-bottom: 0;
  254. }
  255.  
  256. `: '';
  257.  
  258. const cssText7b_content_visibility_unset = FORCE_CONTENT_VISIBILITY_UNSET ? `
  259.  
  260. img,
  261. yt-img-shadow[height][width],
  262. yt-img-shadow {
  263. content-visibility: visible !important;
  264. }
  265.  
  266. ` : '';
  267.  
  268. const cssText7c_will_change_unset = FORCE_WILL_CHANGE_UNSET ? `
  269.  
  270. /* remove YouTube constant will-change */
  271. /* constant value will slow down the performance; default auto */
  272.  
  273. /* www-player.css */
  274. html .ytp-contextmenu,
  275. html .ytp-settings-menu {
  276. will-change: unset;
  277. }
  278.  
  279. /* frequently matched elements */
  280. html .fill.yt-interaction,
  281. html .stroke.yt-interaction,
  282. html .yt-spec-touch-feedback-shape__fill,
  283. html .yt-spec-touch-feedback-shape__stroke {
  284. will-change: unset;
  285. }
  286.  
  287. /* live_chat_polymer.js */
  288. /*
  289. html .toggle-button.tp-yt-paper-toggle-button,
  290. html #primaryProgress.tp-yt-paper-progress,
  291. html #secondaryProgress.tp-yt-paper-progress,
  292. html #onRadio.tp-yt-paper-radio-button,
  293. html .fill.yt-interaction,
  294. html .stroke.yt-interaction,
  295. html .yt-spec-touch-feedback-shape__fill,
  296. html .yt-spec-touch-feedback-shape__stroke {
  297. will-change: unset;
  298. }
  299. */
  300.  
  301. /* desktop_polymer_enable_wil_icons.js */
  302. /* html .fill.yt-interaction,
  303. html .stroke.yt-interaction, */
  304. html tp-yt-app-header::before,
  305. html tp-yt-iron-list,
  306. html #items.tp-yt-iron-list > *,
  307. html #onRadio.tp-yt-paper-radio-button,
  308. html .toggle-button.tp-yt-paper-toggle-button,
  309. html ytd-thumbnail-overlay-toggle-button-renderer[use-expandable-tooltip] #label.ytd-thumbnail-overlay-toggle-button-renderer,
  310. html #items.ytd-post-multi-image-renderer,
  311. html #items.ytd-horizontal-card-list-renderer,
  312. html #items.yt-horizontal-list-renderer,
  313. html #left-arrow.yt-horizontal-list-renderer,
  314. html #right-arrow.yt-horizontal-list-renderer,
  315. html #items.ytd-video-description-infocards-section-renderer,
  316. html #items.ytd-video-description-music-section-renderer,
  317. html #chips.ytd-feed-filter-chip-bar-renderer,
  318. html #chips.yt-chip-cloud-renderer,
  319. html #items.ytd-merch-shelf-renderer,
  320. html #items.ytd-product-details-image-carousel-renderer,
  321. html ytd-video-preview,
  322. html #player-container.ytd-video-preview,
  323. html #primaryProgress.tp-yt-paper-progress,
  324. html #secondaryProgress.tp-yt-paper-progress,
  325. html ytd-miniplayer[enabled] /* ,
  326. html .yt-spec-touch-feedback-shape__fill,
  327. html .yt-spec-touch-feedback-shape__stroke */ {
  328. will-change: unset;
  329. }
  330.  
  331. /* other */
  332. .ytp-videowall-still-info-content[class],
  333. .ytp-suggestion-image[class] {
  334. will-change: unset !important;
  335. }
  336.  
  337. ` : '';
  338.  
  339. const ENABLE_FONT_PRE_RENDERING = typeof HTMLElement.prototype.append === 'function' ? (ENABLE_FONT_PRE_RENDERING_PREFERRED || 0) : 0;
  340. const cssText8_fonts_pre_render = ENABLE_FONT_PRE_RENDERING ? `
  341.  
  342. elzm-fonts {
  343. visibility: collapse;
  344. position: fixed;
  345. top: -10px;
  346. left: -10px;
  347. font-size: 10pt;
  348. line-height: 100%;
  349. width: 100px;
  350. height: 100px;
  351. transform: scale(0.1);
  352. transform: scale(0.01);
  353. transform: scale(0.001);
  354. transform-origin: 0 0;
  355. contain: strict;
  356. display: block;
  357.  
  358. user-select: none !important;
  359. pointer-events: none !important;
  360. }
  361.  
  362. elzm-fonts[id]#elzm-fonts-yk75g {
  363. user-select: none !important;
  364. pointer-events: none !important;
  365. }
  366.  
  367. elzm-font {
  368. visibility: collapse;
  369. position: absolute;
  370. line-height: 100%;
  371. width: 100px;
  372. height: 100px;
  373. contain: strict;
  374. display: block;
  375.  
  376. user-select: none !important;
  377. pointer-events: none !important;
  378. }
  379.  
  380. elzm-font::before {
  381. visibility: collapse;
  382. position: absolute;
  383. line-height: 100%;
  384. width: 100px;
  385. height: 100px;
  386. contain: strict;
  387. display: block;
  388.  
  389. content: '0aZ!@#$~^&*()_-+[]{}|;:><?\\0460\\0301\\0900\\1F00\\0370\\0102\\0100\\28EB2\\28189\\26DA0\\25A9C\\249BB\\23F61\\22E8B\\21927\\21076\\2048E\\1F6F5\\FF37\\F94F\\F0B2\\9F27\\9D9A\\9BEA\\9A6B\\98EC\\9798\\9602\\949D\\9370\\926B\\913A\\8FA9\\8E39\\8CC1\\8B26\\8983\\8804\\8696\\8511\\83BC\\828D\\8115\\7F9A\\7E5B\\7D07\\7B91\\7A2C\\78D2\\776C\\7601\\74AA\\73B9\\7265\\70FE\\6FBC\\6E88\\6D64\\6C3F\\6A9C\\6957\\67FE\\66B3\\6535\\63F2\\628E\\612F\\5FE7\\5E6C\\5CEE\\5B6D\\5A33\\58BC\\575B\\5611\\54BF\\536E\\51D0\\505D\\4F22\\4AD1\\41DB\\3B95\\3572\\2F3F\\26FD\\25A1\\2477\\208D\\1D0A\\1FB\\A1\\A3\\B4\\2CB\\60\\10C\\E22\\A5\\4E08\\B0\\627\\2500\\5E\\201C\\3C\\B7\\23\\26\\3E\\D\\20\\25EE8\\1F235\\FFD7\\FA10\\F92D\\9E8B\\9C3E\\9AE5\\98EB\\971D\\944A\\92BC\\9143\\8F52\\8DC0\\8B2D\\8973\\87E2\\8655\\84B4\\82E8\\814A\\7F77\\7D57\\7BC8\\7A17\\7851\\768C\\7511\\736C\\7166\\6F58\\6D7C\\6B85\\69DD\\6855\\667E\\64D2\\62CF\\6117\\5F6C\\5D9B\\5BBC\\598B\\57B3\\5616\\543F\\528D\\50DD\\4F57\\4093\\3395\\32B5\\31C8\\3028\\2F14\\25E4\\24D1\\2105\\2227\\A8\\2D9\\2CA\\2467\\B1\\2020\\2466\\251C\\266B\\AF\\4E91\\221E\\2464\\2266\\2207\\4E32\\25B3\\2463\\2010\\2103\\3014\\25C7\\24\\25BD\\4E18\\2460\\21D2\\2015\\2193\\4E03\\7E\\25CB\\2191\\25BC\\3D\\500D\\4E01\\25\\30F6\\2605\\266A\\40\\2B\\4E16\\7C\\A9\\4E\\21\\1F1E9\\FEE3\\F0A7\\9F3D\\9DFA\\9C3B\\9A5F\\98C8\\972A\\95B9\\94E7\\9410\\92B7\\914C\\8FE2\\8E2D\\8CAF\\8B5E\\8A02\\8869\\86E4\\8532\\83B4\\82A9\\814D\\7FFA\\7ED7\\7DC4\\7CCC\\7BC3\\7ACA\\797C\\783E\\770F\\760A\\74EF\\73E7\\72DD\\719C\\7005\\6ED8\\6DC3\\6CB2\\6A01\\68E1\\6792\\663A\\64F8\\63BC\\623B\\60FA\\5FD1\\5EA3\\5D32\\5BF5\\5AB2\\5981\\5831\\570A\\5605\\5519\\53FB\\52A2\\5110\\4FE3\\4EB8\\3127\\279C\\2650\\254B\\23E9\\207B\\1D34\\2AE\\176\\221A\\161\\200B\\300C\\4E4C\\1F921\\FF78\\FA0A\\F78A\\9EB9\\9D34\\9BD3\\9A6F\\9912\\97C6\\964E\\950C\\93E4\\92E5\\91F0\\90BB\\8F68\\8E18\\8B6C\\89F6\\889B\\874C\\8602\\84B1\\8378\\826E\\8113\\7FB1\\7EAF\\7D89\\7C20\\7AFB\\7988\\7840\\7705\\75CC\\749A\\73B3\\727F\\7113\\6FE8\\6ED6\\6DD3\\6CDA\\6BBB\\6A31\\6900\\67D9\\66A7\\655D\\6427\\630D\\61C6\\60AC\\5F78\\5E34\\5CE0\\5B80\\5A51\\590B\\57A1\\566F\\5551\\543D\\52DB\\518F\\5032\\3A17\\305C\\2749\\264A\\2567\\2476\\2139\\1EC0\\11AF\\2C8\\1AF\\E17\\2190\\2022\\2502\\2312\\2025\\50';
  390.  
  391. user-select: none !important;
  392. pointer-events: none !important;
  393. }
  394.  
  395. `: '';
  396.  
  397. const cssText9_no_backdrop_filter_when_menu_shown = NO_BACKDROP_FILTER_WHEN_MENU_SHOWN ? `
  398. tp-yt-iron-dropdown.yt-live-chat-app ytd-menu-popup-renderer {
  399. -webkit-backdrop-filter: none;
  400. backdrop-filter: none;
  401. }
  402. `: '';
  403.  
  404. const cssText10_show_more_blinker = ENABLE_SHOW_MORE_BLINKER ? `
  405.  
  406. @keyframes blinker-miuzp {
  407. 0%, 60%, 100% {
  408. opacity: 1;
  409. }
  410. 30% {
  411. opacity: 0.6;
  412. }
  413. }
  414.  
  415. yt-icon-button#show-more.has-new-messages-miuzp {
  416. animation: blinker-miuzp 1.74s linear infinite;
  417. }
  418.  
  419. `: '';
  420.  
  421.  
  422. const addCss = () => `
  423.  
  424. ${cssText8_fonts_pre_render}
  425.  
  426. ${cssText9_no_backdrop_filter_when_menu_shown}
  427.  
  428. @supports (contain: layout paint style) {
  429.  
  430. ${cssText5}
  431.  
  432. }
  433.  
  434. @supports (color: var(--general)) {
  435.  
  436. html {
  437. --yt-live-chat-item-list-renderer-padding: 0px 0px;
  438. }
  439.  
  440. ${cssText3_smooth_transform_position}
  441.  
  442. ${cssText7c_will_change_unset}
  443.  
  444. ${cssText7b_content_visibility_unset}
  445.  
  446. yt-live-chat-item-list-renderer:not([allow-scroll]) #item-scroller.yt-live-chat-item-list-renderer {
  447. overflow-y: scroll;
  448. padding-right: 0;
  449. }
  450.  
  451. ${cssText4_smooth_transform_forced_props}
  452.  
  453. yt-icon[icon="down_arrow"] > *, yt-icon-button#show-more > * {
  454. pointer-events: none !important;
  455. }
  456.  
  457. #continuations, #continuations * {
  458. contain: strict;
  459. position: fixed;
  460. top: 2px;
  461. height: 1px;
  462. width: 2px;
  463. height: 1px;
  464. visibility: collapse;
  465. }
  466.  
  467. ${cssText6b_show_more_button}
  468.  
  469. ${cssText6d_input_panel_border}
  470.  
  471. ${cssText6c_input_panel_overflow}
  472.  
  473. }
  474.  
  475.  
  476. @supports (overflow-anchor: auto) {
  477.  
  478. .no-anchor * {
  479. overflow-anchor: none;
  480. }
  481. .no-anchor > item-anchor {
  482. overflow-anchor: auto;
  483. }
  484.  
  485. item-anchor {
  486.  
  487. height:1px;
  488. width: 100%;
  489. transform: scaleY(0.00001);
  490. transform-origin:0 0;
  491. contain: strict;
  492. opacity:0;
  493. display:flex;
  494. position:relative;
  495. flex-shrink:0;
  496. flex-grow:0;
  497. margin-bottom:0;
  498. overflow:hidden;
  499. box-sizing:border-box;
  500. visibility: visible;
  501. content-visibility: visible;
  502. contain-intrinsic-size: auto 1px;
  503. pointer-events:none !important;
  504.  
  505. }
  506.  
  507. #item-scroller.style-scope.yt-live-chat-item-list-renderer[class] {
  508. overflow-anchor: initial !important; /* whenever ENABLE_OVERFLOW_ANCHOR or not */
  509. }
  510.  
  511. html item-anchor {
  512.  
  513. height: 1px;
  514. width: 1px;
  515. top: auto;
  516. left: auto;
  517. right: auto;
  518. bottom: auto;
  519. transform: translateY(-1px);
  520. position: absolute;
  521. z-index: -1;
  522.  
  523. }
  524.  
  525. }
  526.  
  527. @supports (color: var(--pre-rendering)) {
  528.  
  529. @keyframes dontRenderAnimation {
  530. 0% {
  531. background-position-x: 3px;
  532. }
  533. 100% {
  534. background-position-x: 4px;
  535. }
  536. }
  537.  
  538. .dont-render {
  539. /* visibility: collapse !important; */
  540. /* visibility: collapse will make innerText become "" which conflicts with BetterStreamChat; see https://gf.qytechs.cn/scripts/469878/discussions/197267 */
  541. transform: scale(0.01) !important;
  542. transform: scale(0.00001) !important;
  543. transform: scale(0.0000001) !important;
  544. transform-origin: 0 0 !important;
  545. z-index: -1 !important;
  546. contain: strict !important;
  547. box-sizing: border-box !important;
  548.  
  549. height: 1px !important;
  550. height: 0.1px !important;
  551. height: 0.01px !important;
  552. height: 0.0001px !important;
  553. height: 0.000001px !important;
  554.  
  555. animation: dontRenderAnimation 1ms linear 80ms 1 normal forwards !important;
  556.  
  557. }
  558.  
  559. }
  560.  
  561. ${cssText10_show_more_blinker}
  562.  
  563. `;
  564.  
  565.  
  566. const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : (this instanceof Window ? this : window);
  567.  
  568. // Create a unique key for the script and check if it is already running
  569. const hkey_script = 'mchbwnoasqph';
  570. if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  571. win[hkey_script] = true;
  572.  
  573.  
  574. function dr(s) {
  575. // reserved for future use
  576. return s;
  577. // return window.deWeakJS ? window.deWeakJS(s) : s;
  578. }
  579.  
  580. const getProto = (element) => {
  581. if (element) {
  582. const cnt = element.inst || element;
  583. return cnt.constructor.prototype || null;
  584. }
  585. return null;
  586. }
  587.  
  588. const assertor = (f) => f() || console.assert(false, f + "");
  589.  
  590. const fnIntegrity = (f, d) => {
  591. if (!f || typeof f !== 'function') {
  592. console.warn('f is not a function', f);
  593. return;
  594. }
  595. let p = f + "", s = 0, j = -1, w = 0;
  596. for (let i = 0, l = p.length; i < l; i++) {
  597. const t = p[i];
  598. if (((t >= 'a' && t <= 'z') || (t >= 'A' && t <= 'Z'))) {
  599. if (j < i - 1) w++;
  600. j = i;
  601. } else {
  602. s++;
  603. }
  604. }
  605. let itz = `${f.length}.${s}.${w}`;
  606. if (!d) {
  607. return itz;
  608. } else {
  609. return itz === d;
  610. }
  611. }
  612.  
  613.  
  614. console.assert(MAX_ITEMS_FOR_TOTAL_DISPLAY > 0 && MAX_ITEMS_FOR_FULL_FLUSH > 0 && MAX_ITEMS_FOR_TOTAL_DISPLAY > MAX_ITEMS_FOR_FULL_FLUSH)
  615.  
  616. let ENABLE_FULL_RENDER_REQUIRED_CAPABLE = false;
  617. const isContainSupport = CSS.supports('contain', 'layout paint style');
  618. if (!isContainSupport) {
  619. console.warn("Your browser does not support css property 'contain'.\nPlease upgrade to the latest version.".trim());
  620. } else {
  621. ENABLE_FULL_RENDER_REQUIRED_CAPABLE = true;
  622. }
  623.  
  624. let ENABLE_OVERFLOW_ANCHOR_CAPABLE = false;
  625. const isOverflowAnchorSupport = CSS.supports('overflow-anchor', 'auto');
  626. if (!isOverflowAnchorSupport) {
  627. console.warn("Your browser does not support css property 'overflow-anchor'.\nPlease upgrade to the latest version.".trim());
  628. } else {
  629. ENABLE_OVERFLOW_ANCHOR_CAPABLE = true;
  630. }
  631.  
  632. const NOT_FIREFOX = !CSS.supports('-moz-appearance', 'none'); // 1. Firefox does not have the flicking issue; 2. Firefox's OVERFLOW_ANCHOR is less effective than Chromium's.
  633.  
  634. const ENABLE_OVERFLOW_ANCHOR = ENABLE_OVERFLOW_ANCHOR_PREFERRED && ENABLE_OVERFLOW_ANCHOR_CAPABLE && ENABLE_NO_SMOOTH_TRANSFORM;
  635. const ENABLE_FULL_RENDER_REQUIRED = ENABLE_FULL_RENDER_REQUIRED_PREFERRED && ENABLE_FULL_RENDER_REQUIRED_CAPABLE && ENABLE_OVERFLOW_ANCHOR && ENABLE_NO_SMOOTH_TRANSFORM && NOT_FIREFOX;
  636.  
  637.  
  638. const fxOperator = (proto, propertyName) => {
  639. let propertyDescriptorGetter = null;
  640. try {
  641. propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
  642. } catch (e) { }
  643. return typeof propertyDescriptorGetter === 'function' ? (e) => {
  644. try {
  645.  
  646. return propertyDescriptorGetter.call(dr(e));
  647. } catch (e) { }
  648. return e[propertyName];
  649. } : (e) => e[propertyName];
  650. };
  651.  
  652. const nodeParent = fxOperator(Node.prototype, 'parentNode');
  653. // const nFirstElem = fxOperator(HTMLElement.prototype, 'firstElementChild');
  654. const nPrevElem = fxOperator(HTMLElement.prototype, 'previousElementSibling');
  655. const nNextElem = fxOperator(HTMLElement.prototype, 'nextElementSibling');
  656. const nLastElem = fxOperator(HTMLElement.prototype, 'lastElementChild');
  657.  
  658. const groupCollapsed = (text1, text2) => {
  659.  
  660. console.groupCollapsed(`%c${text1}%c${text2}`,
  661. "background-color: #010502; color: #6acafe; font-weight: 700; padding: 2px;",
  662. "background-color: #010502; color: #6ad9fe; font-weight: 300; padding: 2px;"
  663. );
  664. }
  665.  
  666.  
  667. const EVENT_KEY_ON_REGISTRY_READY = "ytI-ce-registry-created";
  668. const onRegistryReady = (callback) => {
  669. if (typeof customElements === 'undefined') {
  670. if (!('__CE_registry' in document)) {
  671. // https://github.com/webcomponents/polyfills/
  672. Object.defineProperty(document, '__CE_registry', {
  673. get() {
  674. // return undefined
  675. },
  676. set(nv) {
  677. if (typeof nv == 'object') {
  678. delete this.__CE_registry;
  679. this.__CE_registry = nv;
  680. this.dispatchEvent(new CustomEvent(EVENT_KEY_ON_REGISTRY_READY));
  681. }
  682. return true;
  683. },
  684. enumerable: false,
  685. configurable: true
  686. })
  687. }
  688. let eventHandler = (evt) => {
  689. document.removeEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
  690. const f = callback;
  691. callback = null;
  692. eventHandler = null;
  693. f();
  694. };
  695. document.addEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
  696. } else {
  697. callback();
  698. }
  699. };
  700.  
  701. const promiseForCustomYtElementsReady = new Promise(onRegistryReady);
  702.  
  703. /* globals WeakRef:false */
  704.  
  705. /** @type {(o: Object | null) => WeakRef | null} */
  706. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null);
  707.  
  708. /** @type {(wr: Object | null) => Object | null} */
  709. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  710.  
  711. const { addCssManaged } = (() => {
  712.  
  713. const addFontPreRendering = () => {
  714.  
  715. groupCollapsed("YouTube Super Fast Chat", " | Fonts Pre-Rendering");
  716.  
  717. let efsContainer = document.createElement('elzm-fonts');
  718. efsContainer.id = 'elzm-fonts-yk75g'
  719.  
  720. const arr = [];
  721. let p = document.createElement('elzm-font');
  722. arr.push(p);
  723.  
  724. if (ENABLE_FONT_PRE_RENDERING & 1) {
  725. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  726.  
  727. p = document.createElement('elzm-font');
  728. p.style.fontWeight = size;
  729. arr.push(p);
  730. }
  731. }
  732.  
  733. if (ENABLE_FONT_PRE_RENDERING & 2) {
  734. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  735.  
  736. p = document.createElement('elzm-font');
  737. p.style.fontFamily = 'Roboto';
  738. p.style.fontWeight = size;
  739. arr.push(p);
  740. }
  741. }
  742.  
  743. if (ENABLE_FONT_PRE_RENDERING & 4) {
  744. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  745.  
  746. p = document.createElement('elzm-font');
  747. p.style.fontFamily = '"YouTube Noto",Roboto,Arial,Helvetica,sans-serif';
  748. p.style.fontWeight = size;
  749. arr.push(p);
  750. }
  751. }
  752.  
  753.  
  754. if (ENABLE_FONT_PRE_RENDERING & 8) {
  755. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  756.  
  757. p = document.createElement('elzm-font');
  758. p.style.fontFamily = '"Noto",Roboto,Arial,Helvetica,sans-serif';
  759. p.style.fontWeight = size;
  760. arr.push(p);
  761. }
  762. }
  763.  
  764.  
  765. if (ENABLE_FONT_PRE_RENDERING & 16) {
  766. for (const size of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
  767.  
  768. p = document.createElement('elzm-font');
  769. p.style.fontFamily = 'sans-serif';
  770. p.style.fontWeight = size;
  771. arr.push(p);
  772. }
  773. }
  774.  
  775. console.log('number of elzm-font elements', arr.length);
  776.  
  777. HTMLElement.prototype.append.apply(efsContainer, arr);
  778.  
  779. (document.body || document.documentElement).appendChild(efsContainer);
  780.  
  781.  
  782. console.log('elzm-font elements have been added to the page for rendering.');
  783.  
  784. console.groupEnd();
  785.  
  786. }
  787.  
  788. let isCssAdded = false;
  789.  
  790. function addCssElement() {
  791. let s = document.createElement('style');
  792. s.id = 'ewRvC';
  793. return s;
  794. }
  795.  
  796. const addCssManaged = () => {
  797. if (!isCssAdded && document.documentElement && document.head) {
  798. isCssAdded = true;
  799. document.head.appendChild(dr(addCssElement())).textContent = addCss();
  800. if (ENABLE_FONT_PRE_RENDERING) {
  801. Promise.resolve().then(addFontPreRendering)
  802. }
  803. }
  804. }
  805.  
  806. return { addCssManaged };
  807. })();
  808.  
  809.  
  810. const { setupStyle } = (() => {
  811.  
  812. const sp7 = Symbol();
  813.  
  814. const proxyHelperFn = (dummy) => ({
  815.  
  816. get(target, prop) {
  817. return (prop in dummy) ? dummy[prop] : prop === sp7 ? target : target[prop];
  818. },
  819. set(target, prop, value) {
  820. if (!(prop in dummy)) {
  821. target[prop] = value;
  822. }
  823. return true;
  824. },
  825. has(target, prop) {
  826. return (prop in target);
  827. },
  828. deleteProperty(target, prop) {
  829. return true;
  830. },
  831. ownKeys(target) {
  832. return Object.keys(target);
  833. },
  834. defineProperty(target, key, descriptor) {
  835. return Object.defineProperty(target, key, descriptor);
  836. },
  837. getOwnPropertyDescriptor(target, key) {
  838. return Object.getOwnPropertyDescriptor(target, key);
  839. },
  840.  
  841. });
  842.  
  843. const setupStyle = (m1, m2) => {
  844. if (!ENABLE_NO_SMOOTH_TRANSFORM) return;
  845.  
  846. const dummy1v = {
  847. transform: '',
  848. height: '',
  849. minHeight: '',
  850. paddingBottom: '',
  851. paddingTop: ''
  852. };
  853.  
  854. const dummyStyleFn = (k) => (function () { const style = this[sp7]; return style[k](...arguments); });
  855. for (const k of ['toString', 'getPropertyPriority', 'getPropertyValue', 'item', 'removeProperty', 'setProperty']) {
  856. dummy1v[k] = dummyStyleFn(k);
  857. }
  858.  
  859. const dummy1p = proxyHelperFn(dummy1v);
  860. const sp1v = new Proxy(m1.style, dummy1p);
  861. const sp2v = new Proxy(m2.style, dummy1p);
  862. Object.defineProperty(m1, 'style', { get() { return sp1v }, set() { }, enumerable: true, configurable: true });
  863. Object.defineProperty(m2, 'style', { get() { return sp2v }, set() { }, enumerable: true, configurable: true });
  864. m1.removeAttribute("style");
  865. m2.removeAttribute("style");
  866.  
  867. }
  868.  
  869. return { setupStyle };
  870.  
  871. })();
  872.  
  873.  
  874.  
  875. /*
  876. function innerTextFixFn() {
  877.  
  878. if (elemInnerText.status) return;
  879. elemInnerText.status = 1;
  880.  
  881.  
  882. const pd = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerText');
  883. if (!pd) return;
  884. if (pd.configurable !== true) return;
  885. const getterFn = pd.get;
  886. if (typeof getterFn !== 'function') return;
  887. const getterGn = function () {
  888. const resultText = getterFn.apply(this, arguments);
  889. if (!resultText&& elemInnerText.status === 1) {
  890. const html = this.innerHTML;
  891. console.log(1234, html)
  892. if (html) {
  893. elemInnerText.status = 2;
  894. console.log(new Error().stack)
  895.  
  896. }
  897. }
  898. return resultText;
  899. }
  900. let np = Object.assign({}, pd, { get: getterGn });
  901. console.log(124, np)
  902. Object.defineProperty(HTMLElement.prototype, 'innerText', np);
  903. console.log(125, Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerText').get)
  904.  
  905. } */
  906.  
  907.  
  908. const cleanContext = async (win) => {
  909. const waitFn = requestAnimationFrame; // shall have been binded to window
  910. try {
  911. let mx = 16; // MAX TRIAL
  912. const frameId = 'vanillajs-iframe-v1';
  913. /** @type {HTMLIFrameElement | null} */
  914. let frame = document.getElementById(frameId);
  915. let removeIframeFn = null;
  916. if (!frame) {
  917. frame = document.createElement('iframe');
  918. frame.id = 'vanillajs-iframe-v1';
  919. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  920. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  921. n.appendChild(frame);
  922. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  923. const root = document.documentElement;
  924. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  925. removeIframeFn = (setTimeout) => {
  926. const removeIframeOnDocumentReady = (e) => {
  927. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  928. win = null;
  929. setTimeout(() => {
  930. n.remove();
  931. n = null;
  932. }, 200);
  933. }
  934. if (document.readyState !== 'loading') {
  935. removeIframeOnDocumentReady();
  936. } else {
  937. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  938. }
  939. }
  940. }
  941. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  942. const fc = frame.contentWindow;
  943. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  944. const { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval } = fc;
  945. const res = { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval };
  946. for (let k in res) res[k] = res[k].bind(win); // necessary
  947. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  948. return res;
  949. } catch (e) {
  950. console.warn(e);
  951. return null;
  952. }
  953. };
  954.  
  955. const flagsFnOnInterval = (ENABLE_FLAGS_MAINTAIN_STABLE_LIST || ENABLE_FLAGS_REUSE_COMPONENTS) ? (() => {
  956.  
  957. const flags = () => {
  958. try {
  959. return ytcfg.data_.EXPERIMENT_FLAGS;
  960. } catch (e) { }
  961. return null;
  962. };
  963.  
  964. const flagsForced = () => {
  965. try {
  966. return ytcfg.data_.EXPERIMENTS_FORCED_FLAGS;
  967. } catch (e) { }
  968. return null;
  969. };
  970.  
  971. const flagsFn = (EXPERIMENT_FLAGS) => {
  972.  
  973. if (!EXPERIMENT_FLAGS) return;
  974.  
  975. if (ENABLE_FLAGS_MAINTAIN_STABLE_LIST) {
  976. if (USE_MAINTAIN_STABLE_LIST_ONLY_WHEN_KS_FLAG_IS_SET ? EXPERIMENT_FLAGS.kevlar_should_maintain_stable_list === true : true) {
  977. EXPERIMENT_FLAGS.kevlar_tuner_should_test_maintain_stable_list = true;
  978. EXPERIMENT_FLAGS.kevlar_should_maintain_stable_list = true;
  979. }
  980. }
  981.  
  982. if (ENABLE_FLAGS_REUSE_COMPONENTS) {
  983. EXPERIMENT_FLAGS.kevlar_tuner_should_test_reuse_components = true;
  984. EXPERIMENT_FLAGS.kevlar_tuner_should_reuse_components = true;
  985. }
  986.  
  987. };
  988.  
  989. const uf = (EXPERIMENT_FLAGS) => {
  990. if (EXPERIMENT_FLAGS === undefined) EXPERIMENT_FLAGS = flags();
  991. if (EXPERIMENT_FLAGS) {
  992. flagsFn(EXPERIMENT_FLAGS);
  993. flagsFn(flagsForced());
  994. }
  995. }
  996.  
  997. uf();
  998.  
  999. const flagsFnOnInterval = (setInterval, clearInterval) => {
  1000.  
  1001. uf();
  1002. let cidFlags = 0;
  1003. let kd = 0;
  1004. new Promise(flagResolve => {
  1005.  
  1006. const flagTimer = () => {
  1007. const EXPERIMENT_FLAGS = flags();
  1008. if (!EXPERIMENT_FLAGS) return;
  1009. uf(EXPERIMENT_FLAGS);
  1010. if (kd > 0) return;
  1011. kd = 1;
  1012. const delay1000 = new Promise(resolve => setTimeout(resolve, 1000));
  1013. const moDeferred = new Promise(resolve => {
  1014. let mo = new MutationObserver(() => {
  1015. if (mo) {
  1016. mo.disconnect();
  1017. mo.takeRecords();
  1018. mo = null;
  1019. resolve();
  1020. }
  1021. });
  1022. mo.observe(document, { subtree: true, childList: true });
  1023. });
  1024. Promise.race([delay1000, moDeferred]).then(flagResolve);
  1025. };
  1026.  
  1027. uf();
  1028. cidFlags = setInterval(flagTimer, 1);
  1029. }).then(() => {
  1030. if (cidFlags > 0) clearInterval(cidFlags);
  1031. cidFlags = 0;
  1032. uf();
  1033. });
  1034.  
  1035. }
  1036.  
  1037. return flagsFnOnInterval;
  1038.  
  1039. })() : null;
  1040.  
  1041. let kptPF = null;
  1042. const emojiPrefetched = new Set();
  1043. const authorPhotoPrefetched = new Set();
  1044.  
  1045. function linker(link, rel, href, _as){
  1046. return new Promise(resolve=>{
  1047. if (!link) link = document.createElement('link');
  1048. link.rel = rel;
  1049. if (_as) link.setAttribute('as', _as);
  1050. link.onload = function () {
  1051. resolve({
  1052. link: this,
  1053. success: true
  1054. })
  1055. this.remove();
  1056. };
  1057. link.onerror = function () {
  1058. resolve({
  1059. link: this,
  1060. success: false
  1061. });
  1062. this.remove();
  1063. };
  1064. link.href = href;
  1065. document.head.appendChild(link);
  1066. link= null;
  1067. });
  1068. }
  1069.  
  1070. cleanContext(win).then(__CONTEXT__ => {
  1071. if (!__CONTEXT__) return null;
  1072.  
  1073. const { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval } = __CONTEXT__;
  1074.  
  1075. if(flagsFnOnInterval) flagsFnOnInterval(setInterval, clearInterval);
  1076.  
  1077. function basePrefetching() {
  1078.  
  1079. new Promise(resolve => {
  1080.  
  1081. if (document.readyState !== 'loading') {
  1082. resolve();
  1083. } else {
  1084. win.addEventListener("DOMContentLoaded", resolve, false);
  1085. }
  1086.  
  1087. }).then(() => {
  1088. const arr = [
  1089. 'https://www.youtube.com', 'https://youtube.com', 'https://googlevideo.com',
  1090. 'https://googleapis.com', 'https://www.youtube.com', 'https://accounts.youtube.com',
  1091. 'https://www.gstatic.com', 'https://ggpht.com',
  1092. 'https://yt3.ggpht.com', 'https://yt4.ggpht.com'
  1093. ];
  1094. for (const h of arr) {
  1095.  
  1096. let link = null;
  1097. if (kptPF === null) {
  1098. link = document.createElement('link');
  1099. if (link.relList && link.relList.supports) {
  1100. kptPF = (link.relList.supports('dns-prefetch') ? 1 : 0) + (link.relList.supports('preconnect') ? 2 : 0) + (link.relList.supports('prefetch') ? 4 : 0) + (link.relList.supports('subresource') ? 8 : 0)
  1101. } else {
  1102. kptPF = 0;
  1103. }
  1104. console.log('kptPF', kptPF)
  1105. }
  1106. if (ENABLE_BASE_PREFETCHING) {
  1107. if (kptPF & 1) {
  1108. linker(link, 'dns-prefetch', h);
  1109. link = null;
  1110. }
  1111. if (kptPF & 2) {
  1112. linker(link, 'preconnect', h);
  1113. link = null;
  1114. }
  1115. }
  1116.  
  1117. }
  1118. })
  1119.  
  1120.  
  1121. }
  1122.  
  1123. basePrefetching();
  1124.  
  1125. (() => {
  1126. // data manipulation
  1127.  
  1128. if (!DO_PARTICIPANT_LIST_HACKS) return;
  1129.  
  1130. class Mutex {
  1131.  
  1132. constructor() {
  1133. this.p = Promise.resolve()
  1134. }
  1135.  
  1136. /**
  1137. * @param {(lockResolve: () => void)} f
  1138. */
  1139. lockWith(f) {
  1140. this.p = this.p.then(() => new Promise(f).catch(console.warn))
  1141. }
  1142.  
  1143. }
  1144.  
  1145.  
  1146. /**
  1147.  
  1148. h.onParticipantsChanged = function() {
  1149. this.notifyPath("participantsManager.participants")
  1150. }
  1151.  
  1152.  
  1153. at h.onParticipantsChanged (live_chat_polymer.js:8334:41)
  1154. at e.<anonymous> (live_chat_polymer.js:1637:69)
  1155. at e.Bb [as __shady_dispatchEvent] (webcomponents-sd.js:46:110)
  1156. at k.dispatchEvent (webcomponents-sd.js:122:237)
  1157. at mu (live_chat_polymer.js:1677:71)
  1158. at Object.wga [as fn] (live_chat_polymer.js:1678:99)
  1159. at a._propertiesChanged (live_chat_polymer.js:1726:426)
  1160. at b._flushProperties (live_chat_polymer.js:1597:200)
  1161. at a._invalidateProperties (live_chat_polymer.js:1718:69)
  1162. at a.notifyPath (live_chat_polymer.js:1741:182)
  1163.  
  1164. */
  1165.  
  1166.  
  1167. const beforeParticipantsMap = new WeakMap();
  1168.  
  1169. const { notifyPath7081 } = (() => {
  1170.  
  1171. const mutexParticipants = new Mutex();
  1172.  
  1173. let uvid = 0;
  1174. let r95dm = 0;
  1175. let c95dm = -1;
  1176.  
  1177. const foundMap = (base, content) => {
  1178. /*
  1179. let lastSearch = 0;
  1180. let founds = base.map(baseEntry => {
  1181. let search = content.indexOf(baseEntry, lastSearch);
  1182. if (search < 0) return false;
  1183. lastSearch = search + 1;
  1184. return true;
  1185. });
  1186. return founds;
  1187. */
  1188. const contentSet = new Set(content);
  1189. return base.map(baseEntry => contentSet.has(baseEntry));
  1190.  
  1191. }
  1192.  
  1193.  
  1194.  
  1195. const spliceIndicesFunc = (beforeParticipants, participants, idsBefore, idsAfter) => {
  1196.  
  1197. const handler1 = {
  1198. get(target, prop, receiver) {
  1199. if (prop === 'object') {
  1200. return participants; // avoid memory leakage
  1201. }
  1202. if (prop === 'type') {
  1203. return 'splice';
  1204. }
  1205. return target[prop];
  1206. }
  1207. };
  1208. const releaser = () => {
  1209. participants = null;
  1210. }
  1211.  
  1212. let foundsForAfter = foundMap(idsAfter, idsBefore);
  1213. let foundsForBefore = foundMap(idsBefore, idsAfter);
  1214.  
  1215. let indexSplices = [];
  1216. let contentUpdates = [];
  1217. for (let i = 0, j = 0; i < foundsForBefore.length || j < foundsForAfter.length;) {
  1218. if (beforeParticipants[i] === participants[j]) {
  1219. i++; j++;
  1220. } else if (idsBefore[i] === idsAfter[j]) {
  1221. // content changed
  1222. contentUpdates.push({ indexI: i, indexJ: j })
  1223. i++; j++;
  1224. } else {
  1225. let addedCount = 0;
  1226. for (let q = j; q < foundsForAfter.length; q++) {
  1227. if (foundsForAfter[q] === false) addedCount++;
  1228. else break;
  1229. }
  1230. let removedCount = 0;
  1231. for (let q = i; q < foundsForBefore.length; q++) {
  1232. if (foundsForBefore[q] === false) removedCount++;
  1233. else break;
  1234. }
  1235. if (!addedCount && !removedCount) {
  1236. throw 'ERROR(0xFF32): spliceIndicesFunc';
  1237. }
  1238. indexSplices.push(new Proxy({
  1239. index: j,
  1240. addedCount: addedCount,
  1241. removed: removedCount >= 1 ? beforeParticipants.slice(i, i + removedCount) : []
  1242. }, handler1));
  1243. i += removedCount;
  1244. j += addedCount;
  1245. }
  1246. }
  1247. foundsForBefore = null;
  1248. foundsForAfter = null;
  1249. idsBefore = null;
  1250. idsAfter = null;
  1251. beforeParticipants = null;
  1252. return { indexSplices, contentUpdates, releaser };
  1253.  
  1254. }
  1255.  
  1256. /*
  1257.  
  1258. customElements.get("yt-live-chat-participant-renderer").prototype.notifyPath=function(){ console.log(123); console.log(new Error().stack)}
  1259.  
  1260. VM63631:1 Error
  1261. at customElements.get.notifyPath (<anonymous>:1:122)
  1262. at e.forwardRendererStamperChanges_ (live_chat_polymer.js:4453:35)
  1263. at e.rendererStamperApplyChangeRecord_ (live_chat_polymer.js:4451:12)
  1264. at e.rendererStamperObserver_ (live_chat_polymer.js:4448:149)
  1265. at Object.pu [as fn] (live_chat_polymer.js:1692:118)
  1266. at ju (live_chat_polymer.js:1674:217)
  1267. at a._propertiesChanged (live_chat_polymer.js:1726:122)
  1268. at b._flushProperties (live_chat_polymer.js:1597:200)
  1269. at a._invalidateProperties (live_chat_polymer.js:1718:69)
  1270. at a.notifyPath (live_chat_polymer.js:1741:182)
  1271.  
  1272. */
  1273.  
  1274. function convertToIds(participants) {
  1275. return participants.map(participant => {
  1276. if (!participant || typeof participant !== 'object') {
  1277. console.warn('Error(0xFA41): convertToIds', participant);
  1278. return participant; // just in case
  1279. }
  1280. let keys = Object.keys(participant);
  1281. // liveChatTextMessageRenderer
  1282. // liveChatParticipantRenderer - livestream channel owner [no authorExternalChannelId]
  1283. // liveChatPaidMessageRenderer
  1284. /*
  1285.  
  1286. 'yt-live-chat-participant-renderer' utilizes the following:
  1287. authorName.simpleText: string
  1288. authorPhoto.thumbnails: Object{url:string, width:int, height:int} []
  1289. authorBadges[].liveChatAuthorBadgeRenderer.icon.iconType: string
  1290. authorBadges[].liveChatAuthorBadgeRenderer.tooltip: string
  1291. authorBadges[].liveChatAuthorBadgeRenderer.accessibility.accessibilityData: Object{label:string}
  1292.  
  1293. */
  1294. if (keys.length !== 1) {
  1295. console.warn('Error(0xFA42): convertToIds', participant);
  1296. return participant; // just in case
  1297. }
  1298. let key = keys[0];
  1299. let renderer = (participant[key] || 0);
  1300. let authorName = (renderer.authorName || 0);
  1301. let text = `${authorName.simpleText || authorName.text}`
  1302. let res = participant; // fallback if it is not a vaild entry
  1303. if (typeof text !== 'string') {
  1304. console.warn('Error(0xFA53): convertToIds', participant);
  1305. } else {
  1306. text = `${renderer.authorExternalChannelId || 'null'}|${text || ''}`;
  1307. if (text.length > 1) res = text;
  1308. }
  1309. return res;
  1310. // return renderer?`${renderer.id}|${renderer.authorExternalChannelId}`: '';
  1311. // note: renderer.id will be changed if the user typed something to trigger the update of the participants' record.
  1312. });
  1313. }
  1314.  
  1315. const checkChangeToParticipantRendererContent = CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT ? (p1, p2) => {
  1316. // just update when content is changed.
  1317. if (p1.authorName !== p2.authorName) return true;
  1318. if (p1.authorPhoto !== p2.authorPhoto) return true;
  1319. if (p1.authorBadges !== p2.authorBadges) return true;
  1320. return false;
  1321. } : (p1, p2) => {
  1322. // keep integrity all the time.
  1323. return p1 !== p2; // always true
  1324. }
  1325.  
  1326. function notifyPath7081(path) { // cnt "yt-live-chat-participant-list-renderer"
  1327.  
  1328. if (PARTICIPANT_UPDATE_ONLY_ONLY_IF_MODIFICATION_DETECTED) {
  1329. if (path !== "participantsManager.participants") {
  1330. return this.__notifyPath5036__.apply(this, arguments);
  1331. }
  1332. if (c95dm === r95dm) return;
  1333. } else {
  1334. const stack = new Error().stack;
  1335. if (path !== "participantsManager.participants" || stack.indexOf('.onParticipantsChanged') < 0) {
  1336. return this.__notifyPath5036__.apply(this, arguments);
  1337. }
  1338. }
  1339.  
  1340. if (uvid > 1e8) uvid = uvid % 100;
  1341. let tid = ++uvid;
  1342.  
  1343. const cnt = this; // "yt-live-chat-participant-list-renderer"
  1344. mutexParticipants.lockWith(lockResolve => {
  1345.  
  1346. const participants00 = cnt.participantsManager.participants;
  1347.  
  1348. if (tid !== uvid || typeof (participants00 || 0).splice !== 'function') {
  1349. lockResolve();
  1350. return;
  1351. }
  1352.  
  1353. let doUpdate = false;
  1354.  
  1355. if (PARTICIPANT_UPDATE_ONLY_ONLY_IF_MODIFICATION_DETECTED) {
  1356.  
  1357. if (!participants00.r94dm) {
  1358. participants00.r94dm = 1;
  1359. if (++r95dm > 1e9) r95dm = 9;
  1360. participants00.push = function () {
  1361. if (++r95dm > 1e9) r95dm = 9;
  1362. return Array.prototype.push.apply(this, arguments);
  1363. }
  1364. participants00.pop = function () {
  1365. if (++r95dm > 1e9) r95dm = 9;
  1366. return Array.prototype.pop.apply(this, arguments);
  1367. }
  1368. participants00.shift = function () {
  1369. if (++r95dm > 1e9) r95dm = 9;
  1370. return Array.prototype.shift.apply(this, arguments);
  1371. }
  1372. participants00.unshift = function () {
  1373. if (++r95dm > 1e9) r95dm = 9;
  1374. return Array.prototype.unshift.apply(this, arguments);
  1375. }
  1376. participants00.splice = function () {
  1377. if (++r95dm > 1e9) r95dm = 9;
  1378. return Array.prototype.splice.apply(this, arguments);
  1379. }
  1380. participants00.sort = function () {
  1381. if (++r95dm > 1e9) r95dm = 9;
  1382. return Array.prototype.sort.apply(this, arguments);
  1383. }
  1384. participants00.reverse = function () {
  1385. if (++r95dm > 1e9) r95dm = 9;
  1386. return Array.prototype.reverse.apply(this, arguments);
  1387. }
  1388. }
  1389.  
  1390. if (c95dm !== r95dm) {
  1391. c95dm = r95dm;
  1392. doUpdate = true;
  1393. }
  1394.  
  1395. } else {
  1396. doUpdate = true;
  1397. }
  1398.  
  1399. if (!doUpdate) {
  1400. lockResolve();
  1401. return;
  1402. }
  1403.  
  1404. const participants = participants00.slice(0);
  1405. const beforeParticipants = beforeParticipantsMap.get(cnt) || [];
  1406. beforeParticipantsMap.set(cnt, participants);
  1407.  
  1408. const resPromise = (async () => {
  1409.  
  1410. if (beforeParticipants.length === 0) {
  1411. // not error
  1412. return 0;
  1413. }
  1414.  
  1415. let countOfElements = cnt.__getAllParticipantsDOMRenderedLength__()
  1416.  
  1417. // console.log(participants.length, doms.length) // different if no requestAnimationFrame
  1418. if (beforeParticipants.length !== countOfElements) {
  1419. // there is somewrong for the cache. - sometimes happen
  1420. return 0;
  1421. }
  1422.  
  1423. const idsBefore = convertToIds(beforeParticipants);
  1424. const idsAfter = convertToIds(participants);
  1425.  
  1426. let { indexSplices, contentUpdates, releaser } = spliceIndicesFunc(beforeParticipants, participants, idsBefore, idsAfter);
  1427.  
  1428. let res = 1; // default 1 for no update
  1429.  
  1430. if (indexSplices.length >= 1) {
  1431.  
  1432.  
  1433. // let p2 = participants.slice(indexSplices[0].index, indexSplices[0].index+indexSplices[0].addedCount);
  1434. // let p1 = indexSplices[0].removed;
  1435. // console.log(indexSplices.length, indexSplices ,p1,p2, convertToIds(p1),convertToIds(p2))
  1436.  
  1437. /* folllow
  1438. a.notifyPath(c + ".splices", d);
  1439. a.notifyPath(c + ".length", b.length);
  1440. */
  1441. // stampDomArraySplices_
  1442.  
  1443.  
  1444. await new Promise(resolve => {
  1445. cnt.resolveForDOMRendering781 = resolve;
  1446.  
  1447. cnt.__notifyPath5036__("participantsManager.participants.splices", {
  1448. indexSplices
  1449. });
  1450. indexSplices = null;
  1451. releaser();
  1452. releaser = null;
  1453. cnt.__notifyPath5036__("participantsManager.participants.length",
  1454. participants.length
  1455. );
  1456.  
  1457. });
  1458.  
  1459. await Promise.resolve(0); // play safe for the change of 'length'
  1460. countOfElements = cnt.__getAllParticipantsDOMRenderedLength__();
  1461.  
  1462. let wrongSize = participants.length !== countOfElements
  1463. if (wrongSize) {
  1464. console.warn("ERROR(0xE2C3): notifyPath7081", beforeParticipants.length, participants.length, doms.length)
  1465. return 0;
  1466. }
  1467.  
  1468. res = 2 | 4;
  1469.  
  1470. } else {
  1471.  
  1472. indexSplices = null;
  1473. releaser();
  1474. releaser = null;
  1475.  
  1476. if (participants.length !== countOfElements) {
  1477. // other unhandled cases
  1478. return 0;
  1479. }
  1480.  
  1481. }
  1482.  
  1483. // participants.length === countOfElements before contentUpdates
  1484. if (contentUpdates.length >= 1) {
  1485. for (const contentUpdate of contentUpdates) {
  1486. let isChanged = checkChangeToParticipantRendererContent(beforeParticipants[contentUpdate.indexI], participants[contentUpdate.indexJ]);
  1487. if (isChanged) {
  1488. cnt.__notifyPath5036__(`participantsManager.participants[${contentUpdate.indexJ}]`);
  1489. res |= 4 | 8;
  1490. }
  1491. }
  1492. }
  1493. contentUpdates = null;
  1494.  
  1495. return res;
  1496.  
  1497.  
  1498. })();
  1499.  
  1500.  
  1501. resPromise.then(resValue => {
  1502.  
  1503. const isLogRequired = SHOW_PARTICIPANT_CHANGES_IN_CONSOLE && ((resValue === 0) || ((resValue & 4) === 4));
  1504. isLogRequired && groupCollapsed("Participant List Change", `tid = ${tid}; res = ${resValue}`);
  1505. if (resValue === 0) {
  1506. new Promise(resolve => {
  1507. cnt.resolveForDOMRendering781 = resolve;
  1508. isLogRequired && console.log("Full Refresh begins");
  1509. cnt.__notifyPath5036__("participantsManager.participants"); // full refresh
  1510. }).then(() => {
  1511. isLogRequired && console.log("Full Refresh ends");
  1512. console.groupEnd();
  1513. }).then(lockResolve);
  1514. return;
  1515. }
  1516.  
  1517. const delayLockResolve = (resValue & 4) === 4;
  1518.  
  1519. if (delayLockResolve) {
  1520. isLogRequired && console.log(`Number of participants (before): ${beforeParticipants.length}`);
  1521. isLogRequired && console.log(`Number of participants (after): ${participants.length}`);
  1522. isLogRequired && console.log(`Total number of rendered participants: ${cnt.__getAllParticipantsDOMRenderedLength__()}`);
  1523. isLogRequired && console.log(`Participant Renderer Content Updated: ${(resValue & 8) === 8}`);
  1524. isLogRequired && console.groupEnd();
  1525. // requestAnimationFrame is required to avoid particiant update during DOM changing (stampDomArraySplices_)
  1526. // mutex lock with requestAnimationFrame can also disable participants update in background
  1527. requestAnimationFrame(lockResolve);
  1528. } else {
  1529. lockResolve();
  1530. }
  1531.  
  1532. });
  1533.  
  1534. });
  1535.  
  1536. }
  1537.  
  1538. return { notifyPath7081 };
  1539.  
  1540. })();
  1541.  
  1542.  
  1543. const onRegistryReadyForDataManipulation = () => {
  1544.  
  1545. function dummy5035(a, b, c) { }
  1546. function dummy411(a, b, c) { }
  1547.  
  1548.  
  1549. if (REMOVE_PRELOADAVATARFORADDACTION) {
  1550.  
  1551. customElements.whenDefined("yt-live-chat-renderer").then(() => {
  1552.  
  1553. const tag = "yt-live-chat-renderer";
  1554. const cProto = getProto(document.createElement(tag));
  1555.  
  1556. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-renderer hacks");
  1557.  
  1558. if (typeof cProto.preloadAvatarForAddAction === 'function' && !cProto.preloadAvatarForAddAction66 && cProto.preloadAvatarForAddAction.length === 1) {
  1559.  
  1560. cProto.preloadAvatarForAddAction66 = cProto.preloadAvatarForAddAction;
  1561. cProto.preloadAvatarForAddAction = function () {
  1562. // ===== skip =====
  1563.  
  1564. // if (a.item) {
  1565. // a = a.item;
  1566. // var b = Object.keys(a)[0];
  1567. // a = a[b];
  1568. // b = KC(this.get("authorPhoto.thumbnails", a), 24);
  1569. // "string" === typeof b && (this.preloadImage(b),
  1570. // a.authorPhoto.webThumbnailDetailsExtensionData = {
  1571. // isPreloaded: !0
  1572. // })
  1573. // }
  1574.  
  1575. // ===== skip =====
  1576. }
  1577. console.log("cProto.preloadAvatarForAddAction - OK");
  1578. } else {
  1579. console.log("cProto.preloadAvatarForAddAction - NG");
  1580.  
  1581. }
  1582. console.groupEnd();
  1583.  
  1584. // cProto.preloadImage = function (){
  1585. // // although we do in flushItem; just avoid being used by other usages ...
  1586. // }
  1587.  
  1588. });
  1589.  
  1590. }
  1591.  
  1592.  
  1593. customElements.whenDefined("yt-live-chat-participant-list-renderer").then(() => {
  1594.  
  1595. if (!DO_PARTICIPANT_LIST_HACKS) return;
  1596.  
  1597. const tag = "yt-live-chat-participant-list-renderer";
  1598. const cProto = getProto(document.createElement(tag));
  1599. if (!cProto || typeof cProto.attached !== 'function') {
  1600. // for _registered, proto.attached shall exist when the element is defined.
  1601. // for controller extraction, attached shall exist when instance creates.
  1602. console.warn(`proto.attached for ${tag} is unavailable.`);
  1603. return;
  1604. }
  1605.  
  1606.  
  1607. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-participant-list-renderer hacks");
  1608.  
  1609. const fgsArr = ['kevlar_tuner_should_test_maintain_stable_list', 'kevlar_should_maintain_stable_list', 'kevlar_tuner_should_test_reuse_components', 'kevlar_tuner_should_reuse_components'];
  1610. const fgs = {};
  1611. for (const key of fgsArr) fgs[key] = undefined;
  1612.  
  1613. try {
  1614. const EXPERIMENT_FLAGS = ytcfg.data_.EXPERIMENT_FLAGS;
  1615. for (const key of fgsArr) fgs[key] = EXPERIMENT_FLAGS[key];
  1616. } catch (e) { }
  1617. console.log(`EXPERIMENT_FLAGS: ${JSON.stringify(fgs, null, 2)}`);
  1618.  
  1619. const canDoReplacement = (() => {
  1620. if (typeof cProto.__notifyPath5035__ === 'function' && cProto.__notifyPath5035__.name !== 'dummy5035') {
  1621. console.warn('YouTube Live Chat Tamer is running.');
  1622. return;
  1623. }
  1624.  
  1625. if (typeof cProto.__attached411__ === 'function' && cProto.__attached411__.name !== 'dummy411') {
  1626. console.warn('YouTube Live Chat Tamer is running.');
  1627. return;
  1628. }
  1629.  
  1630. cProto.__notifyPath5035__ = dummy5035 // just to against Live Chat Tamer
  1631. cProto.__attached411__ = dummy411 // just to against Live Chat Tamer
  1632.  
  1633. if (typeof cProto.flushRenderStamperComponentBindings_ !== 'function' || cProto.flushRenderStamperComponentBindings_.length !== 0) {
  1634. console.warn("ERROR(0xE355): cProto.flushRenderStamperComponentBindings_ not found");
  1635. return;
  1636. }
  1637.  
  1638. if (typeof cProto.flushRenderStamperComponentBindings66_ === 'function') {
  1639. console.warn("ERROR(0xE356): cProto.flushRenderStamperComponentBindings66_");
  1640. return;
  1641. }
  1642.  
  1643. if (typeof cProto.__getAllParticipantsDOMRenderedLength__ === 'function') {
  1644. console.warn("ERROR(0xE357): cProto.__getAllParticipantsDOMRenderedLength__");
  1645. return;
  1646. }
  1647. return true;
  1648. })();
  1649.  
  1650. console.log(`Data Manipulation Boost = ${canDoReplacement}`);
  1651.  
  1652. assertor(() => fnIntegrity(cProto.attached, '0.32.22')) // just warning
  1653. if (typeof cProto.flushRenderStamperComponentBindings_ === 'function') {
  1654. let fiRSCB = fnIntegrity(cProto.flushRenderStamperComponentBindings_);
  1655. let s = fiRSCB.split('.');
  1656. if (s[0] === '0' && +s[1] > 381 && +s[1] < 391 && +s[2] > 228 && +s[2] < 238) {
  1657. console.log("flushRenderStamperComponentBindings_ - OK", fiRSCB);
  1658. } else {
  1659. console.log("flushRenderStamperComponentBindings_ - failed", fiRSCB);
  1660. }
  1661. } else {
  1662. console.log("flushRenderStamperComponentBindings_ - not found");
  1663. }
  1664. // assertor(() => fnIntegrity(cProto.flushRenderStamperComponentBindings_, '0.386.233')) // just warning
  1665.  
  1666. if (typeof cProto.flushRenderStamperComponentBindings_ === 'function') {
  1667. cProto.flushRenderStamperComponentBindings66_ = cProto.flushRenderStamperComponentBindings_;
  1668. cProto.flushRenderStamperComponentBindings_ = function () {
  1669. // console.log('flushRenderStamperComponentBindings_')
  1670. this.flushRenderStamperComponentBindings66_();
  1671. if (this.resolveForDOMRendering781) {
  1672. this.resolveForDOMRendering781();
  1673. this.resolveForDOMRendering781 = null;
  1674. }
  1675. };
  1676. }
  1677.  
  1678. cProto.__getAllParticipantsDOMRenderedLength__ = function () {
  1679. const container = this.$.participants;
  1680. return HTMLElement.prototype.querySelectorAll.call(container, 'yt-live-chat-participant-renderer').length;
  1681. }
  1682.  
  1683. const onPageElements = [...document.querySelectorAll('yt-live-chat-participant-list-renderer:not(.n9fJ3)')];
  1684.  
  1685. cProto.__attached412__ = cProto.attached;
  1686. const fpPList = function (hostElement) {
  1687. const cnt = hostElement.inst || hostElement;
  1688. if (beforeParticipantsMap.has(cnt)) return;
  1689. hostElement.classList.add('n9fJ3');
  1690.  
  1691. assertor(() => (cnt.__dataEnabled === true && cnt.__dataReady === true));
  1692. if (typeof cnt.notifyPath !== 'function' || typeof cnt.__notifyPath5036__ !== 'undefined') {
  1693. console.warn("ERROR(0xE318): yt-live-chat-participant-list-renderer")
  1694. return;
  1695. }
  1696.  
  1697. groupCollapsed("Participant List attached", "");
  1698. // cnt.$.participants.appendChild = cnt.$.participants.__shady_native_appendChild = function(){
  1699. // console.log(123, 'appendChild');
  1700. // return HTMLElement.prototype.appendChild.apply(this, arguments)
  1701. // }
  1702.  
  1703. // cnt.$.participants.insertBefore =cnt.$.participants.__shady_native_insertBefore = function(){
  1704. // console.log(123, 'insertBefore');
  1705. // return HTMLElement.prototype.insertBefore.apply(this, arguments)
  1706. // }
  1707.  
  1708. cnt.__notifyPath5036__ = cnt.notifyPath
  1709. const participants = ((cnt.participantsManager || 0).participants || 0);
  1710. assertor(() => (participants.length > -1 && typeof participants.slice === 'function'));
  1711. console.log(`initial number of participants: ${participants.length}`);
  1712. const newParticipants = (participants.length >= 1 && typeof participants.slice === 'function') ? participants.slice(0) : [];
  1713. beforeParticipantsMap.set(cnt, newParticipants);
  1714. cnt.notifyPath = notifyPath7081;
  1715. console.log(`CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT = ${CHECK_CHANGE_TO_PARTICIPANT_RENDERER_CONTENT}`);
  1716. console.groupEnd();
  1717. }
  1718. cProto.attached = function () {
  1719. fpPList(this.hostElement || this);
  1720. this.__attached412__.apply(this, arguments);
  1721. };
  1722.  
  1723.  
  1724. if (ENABLE_FLAGS_MAINTAIN_STABLE_LIST_FOR_PARTICIPANTS_LIST) {
  1725.  
  1726. /** @type {boolean | (()=>boolean)} */
  1727. let toUseMaintainStableList = USE_MAINTAIN_STABLE_LIST_ONLY_WHEN_KS_FLAG_IS_SET ? (() => ytcfg.data_.EXPERIMENT_FLAGS.kevlar_should_maintain_stable_list === true) : true;
  1728. if (typeof cProto.stampDomArray_ === 'function' && cProto.stampDomArray_.length === 6 && !cProto.stampDomArray_.nIegT && !cProto.stampDomArray66_) {
  1729.  
  1730. let lastMessageDate = 0;
  1731. cProto.stampDomArray66_ = cProto.stampDomArray_;
  1732.  
  1733. cProto.stampDomArray_ = function (...args) {
  1734. if (args[0] && args[0].length > 0 && args[1] === "participants" && args[2] && args[3] === true && !args[5]) {
  1735. if (typeof toUseMaintainStableList === 'function') {
  1736. toUseMaintainStableList = toUseMaintainStableList();
  1737. }
  1738. args[5] = toUseMaintainStableList;
  1739. let currentDate = Date.now();
  1740. if (currentDate - lastMessageDate > 80) {
  1741. lastMessageDate = currentDate;
  1742. console.log('maintain_stable_list for participants list', toUseMaintainStableList);
  1743. }
  1744. }
  1745. return this.stampDomArray66_.apply(this, args);
  1746. }
  1747.  
  1748. cProto.stampDomArray_.nIegT = 1;
  1749.  
  1750. }
  1751. console.log(`ENABLE_FLAGS_MAINTAIN_STABLE_LIST_FOR_PARTICIPANTS_LIST - YES`);
  1752. } else {
  1753. console.log(`ENABLE_FLAGS_MAINTAIN_STABLE_LIST_FOR_PARTICIPANTS_LIST - NO`);
  1754. }
  1755.  
  1756. console.groupEnd();
  1757.  
  1758. if (onPageElements.length >= 1) {
  1759. for (const s of onPageElements) {
  1760. if ((s.inst || s).isAttached === true) {
  1761. fpPList(s);
  1762. }
  1763. }
  1764. }
  1765.  
  1766. });
  1767.  
  1768. };
  1769.  
  1770. promiseForCustomYtElementsReady.then(onRegistryReadyForDataManipulation);
  1771.  
  1772.  
  1773. })();
  1774.  
  1775. IntersectionObserver && (() => {
  1776.  
  1777. // dom manipulation
  1778.  
  1779. class RAFHub {
  1780. constructor() {
  1781. /** @type {number} */
  1782. this.startAt = 8170;
  1783. /** @type {number} */
  1784. this.counter = 0;
  1785. /** @type {number} */
  1786. this.rid = 0;
  1787. /** @type {Map<number, FrameRequestCallback>} */
  1788. this.funcs = new Map();
  1789. const funcs = this.funcs;
  1790. /** @type {FrameRequestCallback} */
  1791. this.bCallback = this.mCallback.bind(this);
  1792. this.pClear = () => funcs.clear();
  1793. }
  1794. /** @param {DOMHighResTimeStamp} highResTime */
  1795. mCallback(highResTime) {
  1796. this.rid = 0;
  1797. Promise.resolve().then(this.pClear);
  1798. this.funcs.forEach(func => Promise.resolve(highResTime).then(func).catch(console.warn));
  1799. }
  1800. /** @param {FrameRequestCallback} f */
  1801. request(f) {
  1802. if (this.counter > 1e9) this.counter = 9;
  1803. let cid = this.startAt + (++this.counter);
  1804. this.funcs.set(cid, f);
  1805. if (this.rid === 0) this.rid = requestAnimationFrame(this.bCallback);
  1806. return cid;
  1807. }
  1808. /** @param {number} cid */
  1809. cancel(cid) {
  1810. cid = +cid;
  1811. if (cid > 0) {
  1812. if (cid <= this.startAt) {
  1813. return cancelAnimationFrame(cid);
  1814. }
  1815. if (this.rid > 0) {
  1816. this.funcs.delete(cid);
  1817. if (this.funcs.size === 0) {
  1818. cancelAnimationFrame(this.rid);
  1819. this.rid = 0;
  1820. }
  1821. }
  1822. }
  1823. }
  1824. }
  1825.  
  1826.  
  1827. const rafHub = (ENABLE_RAF_HACK_TICKERS || ENABLE_RAF_HACK_DOCKED_MESSAGE || ENABLE_RAF_HACK_INPUT_RENDERER || ENABLE_RAF_HACK_EMOJI_PICKER) ? new RAFHub() : null;
  1828.  
  1829.  
  1830. let dt0 = Date.now() - 2000;
  1831. const dateNow = () => Date.now() - dt0;
  1832. // let lastScroll = 0;
  1833. // let lastLShow = 0;
  1834. let lastWheel = 0;
  1835. let lastMouseDown = 0;
  1836. let lastMouseUp = 0;
  1837. let currentMouseDown = false;
  1838. let lastTouchDown = 0;
  1839. let lastTouchUp = 0;
  1840. let currentTouchDown = false;
  1841. let lastUserInteraction = 0;
  1842.  
  1843. let scrollChatFn = null;
  1844.  
  1845. let skipDontRender = true; // true first; false by flushActiveItems_
  1846.  
  1847. ENABLE_FULL_RENDER_REQUIRED && (() => {
  1848.  
  1849. document.addEventListener('animationstart', (evt) => {
  1850.  
  1851. if (evt.animationName === 'dontRenderAnimation') {
  1852. evt.target.classList.remove('dont-render');
  1853. if (scrollChatFn) scrollChatFn();
  1854. }
  1855.  
  1856. }, true);
  1857.  
  1858. const f = (elm) => {
  1859. if (elm && elm.nodeType === 1) {
  1860. if (!skipDontRender) {
  1861. // innerTextFixFn();
  1862. elm.classList.add('dont-render');
  1863. }
  1864. }
  1865. }
  1866.  
  1867. Node.prototype.__appendChild931__ = function (a) {
  1868. a = dr(a);
  1869. if (this.id === 'items' && this.classList.contains('yt-live-chat-item-list-renderer')) {
  1870. if (a && a.nodeType === 1) f(a);
  1871. else if (a instanceof DocumentFragment) {
  1872. for (let n = a.firstChild; n; n = n.nextSibling) {
  1873. f(n);
  1874. }
  1875. }
  1876. }
  1877. }
  1878.  
  1879. Node.prototype.__appendChild932__ = function () {
  1880. this.__appendChild931__.apply(this, arguments);
  1881. return Node.prototype.appendChild.apply(this, arguments);
  1882. }
  1883.  
  1884.  
  1885. })();
  1886.  
  1887.  
  1888. const watchUserCSS = () => {
  1889.  
  1890. // if (!CSS.supports('contain-intrinsic-size', 'auto var(--wsr94)')) return;
  1891.  
  1892. const getElemFromWR = (nr) => {
  1893. const n = kRef(nr);
  1894. if (n && n.isConnected) return n;
  1895. return null;
  1896. }
  1897.  
  1898. const clearContentVisibilitySizing = () => {
  1899. Promise.resolve().then(() => {
  1900.  
  1901. let btnShowMoreWR = mWeakRef(document.querySelector('#show-more[disabled]'));
  1902.  
  1903. let lastVisibleItemWR = null;
  1904. for (const elm of document.querySelectorAll('[wSr93]')) {
  1905. if (elm.getAttribute('wSr93') === 'visible') lastVisibleItemWR = mWeakRef(elm);
  1906. elm.setAttribute('wSr93', '');
  1907. // custom CSS property --wsr94 not working when attribute wSr93 removed
  1908. }
  1909. requestAnimationFrame(() => {
  1910. const btnShowMore = getElemFromWR(btnShowMoreWR); btnShowMoreWR = null;
  1911. if (btnShowMore) btnShowMore.click();
  1912. else {
  1913. // would not work if switch it frequently
  1914. const lastVisibleItem = getElemFromWR(lastVisibleItemWR); lastVisibleItemWR = null;
  1915. if (lastVisibleItem) {
  1916.  
  1917. Promise.resolve()
  1918. .then(() => lastVisibleItem.scrollIntoView())
  1919. .then(() => lastVisibleItem.scrollIntoView(false))
  1920. .then(() => lastVisibleItem.scrollIntoView({ behavior: "instant", block: "end", inline: "nearest" }))
  1921. .catch(e => { }) // break the chain when method not callable
  1922.  
  1923. }
  1924. }
  1925. });
  1926.  
  1927. });
  1928.  
  1929. }
  1930.  
  1931. const mutObserver = new MutationObserver((mutations) => {
  1932. for (const mutation of mutations) {
  1933. if ((mutation.addedNodes || 0).length >= 1) {
  1934. for (const addedNode of mutation.addedNodes) {
  1935. if (addedNode.nodeName === 'STYLE') {
  1936. clearContentVisibilitySizing();
  1937. return;
  1938. }
  1939. }
  1940. }
  1941. if ((mutation.removedNodes || 0).length >= 1) {
  1942. for (const removedNode of mutation.removedNodes) {
  1943. if (removedNode.nodeName === 'STYLE') {
  1944. clearContentVisibilitySizing();
  1945. return;
  1946. }
  1947. }
  1948. }
  1949. }
  1950. });
  1951.  
  1952. mutObserver.observe(document.documentElement, {
  1953. childList: true,
  1954. subtree: false
  1955. });
  1956. mutObserver.observe(document.head, {
  1957. childList: true,
  1958. subtree: false
  1959. });
  1960. mutObserver.observe(document.body, {
  1961. childList: true,
  1962. subtree: false
  1963. });
  1964.  
  1965. }
  1966.  
  1967.  
  1968. class WillChangeController {
  1969. constructor(itemScroller, willChangeValue) {
  1970. this.element = itemScroller;
  1971. this.counter = 0;
  1972. this.active = false;
  1973. this.willChangeValue = willChangeValue;
  1974. }
  1975.  
  1976. beforeOper() {
  1977. if (!this.active) {
  1978. this.active = true;
  1979. this.element.style.willChange = this.willChangeValue;
  1980. }
  1981. this.counter++;
  1982. }
  1983.  
  1984. afterOper() {
  1985. const c = this.counter;
  1986. requestAnimationFrame(() => {
  1987. if (c === this.counter) {
  1988. this.active = false;
  1989. this.element.style.willChange = '';
  1990. }
  1991. });
  1992. }
  1993.  
  1994. release() {
  1995. const element = this.element;
  1996. this.element = null;
  1997. this.counter = 1e16;
  1998. this.active = false;
  1999. try {
  2000. element.style.willChange = '';
  2001. } catch (e) { }
  2002. }
  2003.  
  2004. }
  2005.  
  2006.  
  2007. const { lcRendererElm, visObserver } = (() => {
  2008.  
  2009.  
  2010.  
  2011. let lcRendererWR = null;
  2012.  
  2013. const lcRendererElm = () => {
  2014. let lcRenderer = kRef(lcRendererWR);
  2015. if (!lcRenderer || !lcRenderer.isConnected) {
  2016. lcRenderer = document.querySelector('yt-live-chat-item-list-renderer.yt-live-chat-renderer');
  2017. lcRendererWR = lcRenderer ? mWeakRef(lcRenderer) : null;
  2018. }
  2019. return lcRenderer;
  2020. };
  2021.  
  2022.  
  2023. let hasFirstShowMore = false;
  2024.  
  2025. const visObserverFn = (entry) => {
  2026.  
  2027. const target = entry.target;
  2028. if (!target) return;
  2029. // if(target.classList.contains('dont-render')) return;
  2030. let isVisible = entry.isIntersecting === true && entry.intersectionRatio > 0.5;
  2031. // const h = entry.boundingClientRect.height;
  2032. /*
  2033. if (h < 16) { // wrong: 8 (padding/margin); standard: 32; test: 16 or 20
  2034. // e.g. under fullscreen. the element created but not rendered.
  2035. target.setAttribute('wSr93', '');
  2036. return;
  2037. }
  2038. */
  2039. if (isVisible) {
  2040. // target.style.setProperty('--wsr94', h + 'px');
  2041. target.setAttribute('wSr93', 'visible');
  2042. if (nNextElem(target) === null) {
  2043.  
  2044. // firstVisibleItemDetected = true;
  2045. /*
  2046. if (dateNow() - lastScroll < 80) {
  2047. lastLShow = 0;
  2048. lastScroll = 0;
  2049. Promise.resolve().then(clickShowMore);
  2050. } else {
  2051. lastLShow = dateNow();
  2052. }
  2053. */
  2054. // lastLShow = dateNow();
  2055. } else if (!hasFirstShowMore) { // should more than one item being visible
  2056. // implement inside visObserver to ensure there is sufficient delay
  2057. hasFirstShowMore = true;
  2058. requestAnimationFrame(() => {
  2059. // foreground page
  2060. // page visibly ready -> load the latest comments at initial loading
  2061. const lcRenderer = lcRendererElm();
  2062. if (lcRenderer) {
  2063. (lcRenderer.inst || lcRenderer).scrollToBottom_();
  2064. }
  2065. });
  2066. }
  2067. }
  2068. else if (target.getAttribute('wSr93') === 'visible') { // ignore target.getAttribute('wSr93') === '' to avoid wrong sizing
  2069.  
  2070. // target.style.setProperty('--wsr94', h + 'px');
  2071. target.setAttribute('wSr93', 'hidden');
  2072. } // note: might consider 0 < entry.intersectionRatio < 0.5 and target.getAttribute('wSr93') === '' <new last item>
  2073.  
  2074. }
  2075.  
  2076.  
  2077.  
  2078. const visObserver = new IntersectionObserver((entries) => {
  2079.  
  2080. for (const entry of entries) {
  2081.  
  2082. Promise.resolve(entry).then(visObserverFn);
  2083.  
  2084. }
  2085.  
  2086. }, {
  2087. // root: HTMLElement.prototype.closest.call(m2, '#item-scroller.yt-live-chat-item-list-renderer'), // nullable
  2088. rootMargin: "0px",
  2089. threshold: [0.05, 0.95],
  2090. });
  2091.  
  2092.  
  2093. return { lcRendererElm, visObserver }
  2094.  
  2095.  
  2096. })();
  2097.  
  2098. const { setupMutObserver } = (() => {
  2099.  
  2100. const mutFn = (items) => {
  2101. for (let node = nLastElem(items); node !== null; node = nPrevElem(node)) {
  2102. if (node.hasAttribute('wSr93')) break;
  2103. node.setAttribute('wSr93', '');
  2104. visObserver.observe(node);
  2105. }
  2106. }
  2107.  
  2108. const mutObserver = new MutationObserver((mutations) => {
  2109. const items = (mutations[0] || 0).target;
  2110. if (!items) return;
  2111. mutFn(items);
  2112. });
  2113.  
  2114. const setupMutObserver = (m2) => {
  2115. scrollChatFn = null;
  2116. mutObserver.disconnect();
  2117. mutObserver.takeRecords();
  2118. if (m2) {
  2119. if (typeof m2.__appendChild932__ === 'function') {
  2120. if (typeof m2.appendChild === 'function') m2.appendChild = m2.__appendChild932__;
  2121. if (typeof m2.__shady_native_appendChild === 'function') m2.__shady_native_appendChild = m2.__appendChild932__;
  2122. }
  2123. mutObserver.observe(m2, {
  2124. childList: true,
  2125. subtree: false
  2126. });
  2127. mutFn(m2);
  2128.  
  2129.  
  2130. if (ENABLE_OVERFLOW_ANCHOR) {
  2131.  
  2132. let items = m2;
  2133. let addedAnchor = false;
  2134. if (items) {
  2135. if (items.nextElementSibling === null) {
  2136. items.classList.add('no-anchor');
  2137. addedAnchor = true;
  2138. items.parentNode.appendChild(dr(document.createElement('item-anchor')));
  2139. }
  2140. }
  2141.  
  2142.  
  2143.  
  2144. if (addedAnchor) {
  2145. nodeParent(m2).classList.add('no-anchor'); // required
  2146. }
  2147.  
  2148. }
  2149.  
  2150. // let div = document.createElement('div');
  2151. // div.id = 'qwcc';
  2152. // HTMLElement.prototype.appendChild.call(document.querySelector('yt-live-chat-item-list-renderer'), div )
  2153. // bufferRegion =div;
  2154.  
  2155. // buffObserver.takeRecords();
  2156. // buffObserver.disconnect();
  2157. // buffObserver.observe(div, {
  2158. // childList: true,
  2159. // subtree: false
  2160. // })
  2161.  
  2162.  
  2163.  
  2164. }
  2165. }
  2166.  
  2167. return { setupMutObserver };
  2168.  
  2169.  
  2170.  
  2171. })();
  2172.  
  2173. const setupEvents = () => {
  2174.  
  2175.  
  2176. let scrollCount = 0;
  2177.  
  2178. const passiveCapture = typeof IntersectionObserver === 'function' ? { capture: true, passive: true } : true;
  2179.  
  2180.  
  2181. const delayFlushActiveItemsAfterUserActionK_ = () => {
  2182.  
  2183. const lcRenderer = lcRendererElm();
  2184. if (lcRenderer) {
  2185. const cnt = (lcRenderer.inst || lcRenderer);
  2186. if (!cnt.hasUserJustInteracted11_) return;
  2187. if (cnt.atBottom && cnt.allowScroll && cnt.activeItems_.length >= 1 && cnt.hasUserJustInteracted11_()) {
  2188. cnt.delayFlushActiveItemsAfterUserAction11_ && cnt.delayFlushActiveItemsAfterUserAction11_();
  2189. }
  2190. }
  2191.  
  2192. }
  2193.  
  2194. document.addEventListener('scroll', (evt) => {
  2195. if (!evt || !evt.isTrusted) return;
  2196. // lastScroll = dateNow();
  2197. if (++scrollCount > 1e9) scrollCount = 9;
  2198. }, passiveCapture); // support contain => support passive
  2199.  
  2200. let lastScrollCount = -1;
  2201. document.addEventListener('wheel', (evt) => {
  2202. if (!evt || !evt.isTrusted) return;
  2203. if (lastScrollCount === scrollCount) return;
  2204. lastScrollCount = scrollCount;
  2205. lastWheel = dateNow();
  2206. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  2207. }, passiveCapture); // support contain => support passive
  2208.  
  2209. document.addEventListener('mousedown', (evt) => {
  2210. if (!evt || !evt.isTrusted) return;
  2211. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  2212. lastMouseDown = dateNow();
  2213. currentMouseDown = true;
  2214. lastUserInteraction = lastMouseDown;
  2215. }, passiveCapture);
  2216.  
  2217. document.addEventListener('pointerdown', (evt) => {
  2218. if (!evt || !evt.isTrusted) return;
  2219. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  2220. lastMouseDown = dateNow();
  2221. currentMouseDown = true;
  2222. lastUserInteraction = lastMouseDown;
  2223. }, passiveCapture);
  2224.  
  2225. document.addEventListener('click', (evt) => {
  2226. if (!evt || !evt.isTrusted) return;
  2227. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  2228. lastMouseDown = lastMouseUp = dateNow();
  2229. currentMouseDown = false;
  2230. lastUserInteraction = lastMouseDown;
  2231. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  2232. }, passiveCapture);
  2233.  
  2234. document.addEventListener('tap', (evt) => {
  2235. if (!evt || !evt.isTrusted) return;
  2236. if (((evt || 0).target || 0).id !== 'item-scroller') return;
  2237. lastMouseDown = lastMouseUp = dateNow();
  2238. currentMouseDown = false;
  2239. lastUserInteraction = lastMouseDown;
  2240. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  2241. }, passiveCapture);
  2242.  
  2243.  
  2244. document.addEventListener('mouseup', (evt) => {
  2245. if (!evt || !evt.isTrusted) return;
  2246. if (currentMouseDown) {
  2247. lastMouseUp = dateNow();
  2248. currentMouseDown = false;
  2249. lastUserInteraction = lastMouseUp;
  2250. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  2251. }
  2252. }, passiveCapture);
  2253.  
  2254.  
  2255. document.addEventListener('pointerup', (evt) => {
  2256. if (!evt || !evt.isTrusted) return;
  2257. if (currentMouseDown) {
  2258. lastMouseUp = dateNow();
  2259. currentMouseDown = false;
  2260. lastUserInteraction = lastMouseUp;
  2261. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  2262. }
  2263. }, passiveCapture);
  2264.  
  2265. document.addEventListener('touchstart', (evt) => {
  2266. if (!evt || !evt.isTrusted) return;
  2267. lastTouchDown = dateNow();
  2268. currentTouchDown = true;
  2269. lastUserInteraction = lastTouchDown;
  2270. }, passiveCapture);
  2271.  
  2272. document.addEventListener('touchmove', (evt) => {
  2273. if (!evt || !evt.isTrusted) return;
  2274. lastTouchDown = dateNow();
  2275. currentTouchDown = true;
  2276. lastUserInteraction = lastTouchDown;
  2277. }, passiveCapture);
  2278.  
  2279. document.addEventListener('touchend', (evt) => {
  2280. if (!evt || !evt.isTrusted) return;
  2281. if (currentTouchDown) {
  2282. lastTouchUp = dateNow();
  2283. currentTouchDown = false;
  2284. lastUserInteraction = lastTouchUp;
  2285. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  2286. }
  2287. }, passiveCapture);
  2288.  
  2289. document.addEventListener('touchcancel', (evt) => {
  2290. if (!evt || !evt.isTrusted) return;
  2291. if (currentTouchDown) {
  2292. lastTouchUp = dateNow();
  2293. currentTouchDown = false;
  2294. lastUserInteraction = lastTouchUp;
  2295. delayFlushActiveItemsAfterUserActionK_ && delayFlushActiveItemsAfterUserActionK_();
  2296. }
  2297. }, passiveCapture);
  2298.  
  2299.  
  2300. }
  2301.  
  2302.  
  2303. const onRegistryReadyForDOMOperations = () => {
  2304. let firstCheckedOnYtInit = false;
  2305.  
  2306. const mightFirstCheckOnYtInit = () => {
  2307. if (firstCheckedOnYtInit) return;
  2308. firstCheckedOnYtInit = true;
  2309.  
  2310. if (!document.body || !document.head) return;
  2311.  
  2312. if (!assertor(() => location.pathname.startsWith('/live_chat') && location.search.indexOf('continuation=') >= 0)) return;
  2313.  
  2314. addCssManaged();
  2315.  
  2316. let efsContainer = document.getElementById('elzm-fonts-yk75g');
  2317. if (efsContainer && efsContainer.parentNode !== document.body) {
  2318. document.body.appendChild(efsContainer);
  2319. }
  2320.  
  2321.  
  2322. }
  2323.  
  2324. if (!assertor(() => location.pathname.startsWith('/live_chat') && location.search.indexOf('continuation=') >= 0)) return;
  2325. // if (!assertor(() => document.getElementById('yt-masthead') === null)) return;
  2326.  
  2327. if (document.documentElement && document.head) {
  2328.  
  2329. addCssManaged();
  2330.  
  2331. }
  2332. // console.log(document.body===null)
  2333.  
  2334. customElements.whenDefined('yt-live-chat-item-list-renderer').then(() => {
  2335.  
  2336.  
  2337. const tag = "yt-live-chat-item-list-renderer"
  2338. const dummy = document.createElement(tag);
  2339.  
  2340.  
  2341. const cProto = getProto(dummy);
  2342. if (!cProto || !cProto.attached) {
  2343. console.warn(`proto.attached for ${tag} is unavailable.`);
  2344. return;
  2345. }
  2346.  
  2347.  
  2348. mightFirstCheckOnYtInit();
  2349. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-item-list-renderer hacks");
  2350. console.log("[Begin]");
  2351.  
  2352. const mclp = cProto;
  2353. try {
  2354. assertor(() => typeof mclp.scrollToBottom_ === 'function');
  2355. assertor(() => typeof mclp.flushActiveItems_ === 'function');
  2356. assertor(() => typeof mclp.canScrollToBottom_ === 'function');
  2357. assertor(() => typeof mclp.setAtBottom === 'function');
  2358. assertor(() => typeof mclp.scrollToBottom66_ === 'undefined');
  2359. assertor(() => typeof mclp.flushActiveItems66_ === 'undefined');
  2360. } catch (e) { }
  2361.  
  2362.  
  2363. try {
  2364. assertor(() => typeof mclp.attached === 'function');
  2365. assertor(() => typeof mclp.detached === 'function');
  2366. assertor(() => typeof mclp.canScrollToBottom_ === 'function');
  2367. assertor(() => typeof mclp.isSmoothScrollEnabled_ === 'function');
  2368. assertor(() => typeof mclp.maybeResizeScrollContainer_ === 'function');
  2369. assertor(() => typeof mclp.refreshOffsetContainerHeight_ === 'function');
  2370. assertor(() => typeof mclp.smoothScroll_ === 'function');
  2371. assertor(() => typeof mclp.resetSmoothScroll_ === 'function');
  2372. } catch (e) { }
  2373.  
  2374. mclp.__intermediate_delay__ = null;
  2375.  
  2376. let mzk = 0;
  2377. let myk = 0;
  2378. let mlf = 0;
  2379. let myw = 0;
  2380. let mzt = 0;
  2381. let zarr = null;
  2382. let mlg = 0;
  2383.  
  2384. if ((mclp.clearList || 0).length === 0) {
  2385. assertor(() => fnIntegrity(mclp.clearList, '0.106.50'));
  2386. mclp.clearList66 = mclp.clearList;
  2387. mclp.clearList = function () {
  2388. mzk++;
  2389. myk++;
  2390. mlf++;
  2391. myw++;
  2392. mzt++;
  2393. mlg++;
  2394. zarr = null;
  2395. this.__intermediate_delay__ = null;
  2396. this.clearList66();
  2397. };
  2398. console.log("clearList", "OK");
  2399. } else {
  2400. console.log("clearList", "NG");
  2401. }
  2402.  
  2403.  
  2404. let onListRendererAttachedDone = false;
  2405.  
  2406. function setList(itemOffset, items) {
  2407.  
  2408. const isFirstTime = onListRendererAttachedDone === false;
  2409.  
  2410. if (isFirstTime) {
  2411. onListRendererAttachedDone = true;
  2412. Promise.resolve().then(watchUserCSS);
  2413. addCssManaged();
  2414. setupEvents();
  2415. }
  2416.  
  2417. setupStyle(itemOffset, items);
  2418.  
  2419. setupMutObserver(items);
  2420. }
  2421.  
  2422. mclp.attached419 = async function () {
  2423.  
  2424. if (!this.isAttached) return;
  2425.  
  2426. let maxTrial = 16;
  2427. while (!this.$ || !this.$['item-scroller'] || !this.$['item-offset'] || !this.$['items']) {
  2428. if (--maxTrial < 0 || !this.isAttached) return;
  2429. await new Promise(requestAnimationFrame);
  2430. }
  2431.  
  2432. if (this.isAttached !== true) return;
  2433.  
  2434. if (!this.$) {
  2435. console.warn("!this.$");
  2436. return;
  2437. }
  2438. if (!this.$) return;
  2439. /** @type {HTMLElement | null} */
  2440. const itemScroller = this.$['item-scroller'];
  2441. /** @type {HTMLElement | null} */
  2442. const itemOffset = this.$['item-offset'];
  2443. /** @type {HTMLElement | null} */
  2444. const items = this.$['items'];
  2445.  
  2446. if (!itemScroller || !itemOffset || !items) {
  2447. console.warn("items.parentNode !== itemOffset");
  2448. return;
  2449. }
  2450.  
  2451. if (nodeParent(items) !== itemOffset) {
  2452.  
  2453. console.warn("items.parentNode !== itemOffset");
  2454. return;
  2455. }
  2456.  
  2457.  
  2458. if (items.id !== 'items' || itemOffset.id !== "item-offset") {
  2459.  
  2460. console.warn("id incorrect");
  2461. return;
  2462. }
  2463.  
  2464. const isTargetItems = HTMLElement.prototype.matches.call(items, '#item-offset.style-scope.yt-live-chat-item-list-renderer > #items.style-scope.yt-live-chat-item-list-renderer')
  2465.  
  2466. if (!isTargetItems) {
  2467. console.warn("!isTargetItems");
  2468. return;
  2469. }
  2470.  
  2471. setList(itemOffset, items);
  2472.  
  2473. }
  2474.  
  2475. mclp.attached331 = mclp.attached;
  2476. mclp.attached = function () {
  2477. this.attached419 && this.attached419();
  2478. return this.attached331();
  2479. }
  2480.  
  2481. mclp.detached331 = mclp.detached;
  2482.  
  2483. mclp.detached = function () {
  2484. setupMutObserver();
  2485. return this.detached331();
  2486. }
  2487.  
  2488. const t29s = document.querySelectorAll("yt-live-chat-item-list-renderer");
  2489. for (const t29 of t29s) {
  2490. if ((t29.inst || t29).isAttached === true) {
  2491. t29.attached419();
  2492. }
  2493. }
  2494.  
  2495. if ((mclp.async || 0).length === 2 && (mclp.cancelAsync || 0).length === 1) {
  2496.  
  2497. assertor(() => fnIntegrity(mclp.async, '2.24.15'));
  2498. assertor(() => fnIntegrity(mclp.cancelAsync, '1.15.8'));
  2499.  
  2500. /** @type {Map<number, any>} */
  2501. const aMap = new Map();
  2502. let count = 6150;
  2503. mclp.async66 = mclp.async;
  2504. mclp.async = function (e, f) {
  2505. // ensure the previous operation is done
  2506. // .async is usually after the time consuming functions like flushActiveItems_ and scrollToBottom_
  2507. const hasF = arguments.length === 2;
  2508. const stack = new Error().stack;
  2509. const isFlushAsync = stack.indexOf('flushActiveItems_') >= 0;
  2510. if (count > 1e9) count = 6159;
  2511. const resId = ++count;
  2512. aMap.set(resId, e);
  2513. (this.__intermediate_delay__ || Promise.resolve()).then(rk => {
  2514. const rp = aMap.get(resId);
  2515. if (typeof rp !== 'function') {
  2516. return;
  2517. }
  2518. let cancelCall = false;
  2519. if (isFlushAsync) {
  2520. if (rk < 0) {
  2521. cancelCall = true;
  2522. } else if (rk === 2 && arguments[0] === this.maybeScrollToBottom_) {
  2523. cancelCall = true;
  2524. }
  2525. }
  2526. if (cancelCall) {
  2527. aMap.delete(resId);
  2528. } else {
  2529. const asyncEn = function () {
  2530. aMap.delete(resId);
  2531. return rp.apply(this, arguments);
  2532. };
  2533. aMap.set(resId, hasF ? this.async66(asyncEn, f) : this.async66(asyncEn));
  2534. }
  2535. });
  2536.  
  2537. return resId;
  2538. }
  2539.  
  2540. mclp.cancelAsync66 = mclp.cancelAsync;
  2541. mclp.cancelAsync = function (resId) {
  2542. if (resId <= 6150) {
  2543. this.cancelAsync66(resId);
  2544. } else if (aMap.has(resId)) {
  2545. const rp = aMap.get(resId);
  2546. aMap.delete(resId);
  2547. if (typeof rp !== 'function') {
  2548. this.cancelAsync66(rp);
  2549. }
  2550. }
  2551. }
  2552.  
  2553. console.log("async", "OK");
  2554. } else {
  2555. console.log("async", "NG");
  2556. }
  2557.  
  2558.  
  2559. if ((mclp.showNewItems_ || 0).length === 0 && ENABLE_NO_SMOOTH_TRANSFORM) {
  2560.  
  2561. assertor(() => fnIntegrity(mclp.showNewItems_, '0.170.79'));
  2562. mclp.showNewItems66_ = mclp.showNewItems_;
  2563.  
  2564. mclp.showNewItems77_ = async function () {
  2565. if (myk > 1e9) myk = 9;
  2566. let tid = ++myk;
  2567.  
  2568. await new Promise(requestAnimationFrame);
  2569.  
  2570. if (tid !== myk) {
  2571. return;
  2572. }
  2573.  
  2574. const cnt = this;
  2575.  
  2576. await Promise.resolve();
  2577. cnt.showNewItems66_();
  2578.  
  2579. await Promise.resolve();
  2580.  
  2581. }
  2582.  
  2583. mclp.showNewItems_ = function () {
  2584.  
  2585. const cnt = this;
  2586. cnt.__intermediate_delay__ = new Promise(resolve => {
  2587. cnt.showNewItems77_().then(() => {
  2588. resolve();
  2589. });
  2590. });
  2591. }
  2592.  
  2593. console.log("showNewItems_", "OK");
  2594. } else {
  2595. console.log("showNewItems_", "NG");
  2596. }
  2597.  
  2598.  
  2599. if ((mclp.flushActiveItems_ || 0).length === 0) {
  2600.  
  2601. assertor(() => fnIntegrity(mclp.flushActiveItems_, '0.137.81'));
  2602.  
  2603. let hasMoreMessageState = !ENABLE_SHOW_MORE_BLINKER ? -1 : 0;
  2604.  
  2605. let contensWillChangeController = null;
  2606.  
  2607. mclp.flushActiveItems66_ = mclp.flushActiveItems_;
  2608.  
  2609. mclp.flushActiveItems78_ = async function (tid) {
  2610. try {
  2611. const lockedMaxItemsToDisplay = this.data.maxItemsToDisplay944;
  2612. let logger = false;
  2613. const cnt = this;
  2614. let immd = cnt.__intermediate_delay__;
  2615. await new Promise(requestAnimationFrame);
  2616.  
  2617. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2618. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  2619.  
  2620. mlf++;
  2621. if (mlg > 1e9) mlg = 9;
  2622. ++mlg;
  2623.  
  2624. const tmpMaxItemsCount = this.data.maxItemsToDisplay;
  2625. const reducedMaxItemsToDisplay = MAX_ITEMS_FOR_FULL_FLUSH;
  2626. let changeMaxItemsToDisplay = false;
  2627. const activeItemsLen = this.activeItems_.length;
  2628. if (activeItemsLen > tmpMaxItemsCount && tmpMaxItemsCount > 0) {
  2629. logger = true;
  2630.  
  2631. groupCollapsed("YouTube Super Fast Chat", " | flushActiveItems78_");
  2632.  
  2633. logger && console.log('[Begin]')
  2634.  
  2635. console.log('this.activeItems_.length > N', activeItemsLen, tmpMaxItemsCount);
  2636. if (ENABLE_REDUCED_MAXITEMS_FOR_FLUSH && lockedMaxItemsToDisplay === tmpMaxItemsCount && lockedMaxItemsToDisplay !== reducedMaxItemsToDisplay) {
  2637. console.log('reduce maxitems');
  2638. if (tmpMaxItemsCount > reducedMaxItemsToDisplay) {
  2639. // as all the rendered chats are already "outdated"
  2640. // all old chats shall remove and reduced number of few chats will be rendered
  2641. // then restore to the original number
  2642. changeMaxItemsToDisplay = true;
  2643. this.data.maxItemsToDisplay = reducedMaxItemsToDisplay;
  2644. console.log(`'maxItemsToDisplay' is reduced from ${tmpMaxItemsCount} to ${reducedMaxItemsToDisplay}.`)
  2645. }
  2646. this.activeItems_.splice(0, activeItemsLen - this.data.maxItemsToDisplay);
  2647. // console.log('changeMaxItemsToDisplay 01', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  2648.  
  2649. console.log('new this.activeItems_.length > N', this.activeItems_.length);
  2650. } else {
  2651. this.activeItems_.splice(0, activeItemsLen - (tmpMaxItemsCount < 900 ? tmpMaxItemsCount : 900));
  2652.  
  2653. console.log('new this.activeItems_.length > N', this.activeItems_.length);
  2654. }
  2655. }
  2656. // it is found that it will render all stacked chats after switching back from background
  2657. // to avoid lagging in popular livestream with massive chats, trim first before rendering.
  2658. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  2659.  
  2660.  
  2661. const items = (cnt.$ || 0).items;
  2662.  
  2663. if (USE_WILL_CHANGE_CONTROLLER) {
  2664. if (contensWillChangeController && contensWillChangeController.element !== items) {
  2665. contensWillChangeController.release();
  2666. contensWillChangeController = null;
  2667. }
  2668. if (!contensWillChangeController) contensWillChangeController = new WillChangeController(items, 'contents');
  2669. }
  2670. const wcController = contensWillChangeController;
  2671. cnt.__intermediate_delay__ = Promise.all([cnt.__intermediate_delay__ || null, immd || null]);
  2672. wcController && wcController.beforeOper();
  2673. await Promise.resolve();
  2674. const len1 = cnt.activeItems_.length;
  2675. let waitFor = [];
  2676. if (cnt.activeItems_.length > 0) {
  2677.  
  2678. function setThumbnails(config) {
  2679. const { baseObject, thumbnails, flag0 } = config;
  2680. if (thumbnails && thumbnails.length > 0) {
  2681. if (flag0 > 0) {
  2682. let pSize = 0;
  2683. let newThumbnails = [];
  2684. for (const thumbnail of thumbnails) {
  2685. if (!thumbnail || !thumbnail.url) continue;
  2686. const squarePhoto = thumbnail.width === thumbnail.height && typeof thumbnail.width === 'number';
  2687. const condSize = pSize <= 0 || (flag0 === 1 ? pSize > thumbnail.width : pSize < thumbnail.width);
  2688. const leastSizeFulfilled = squarePhoto ? thumbnail.width >= LEAST_IMAGE_SIZE : true;
  2689. if ((!squarePhoto || condSize) && leastSizeFulfilled) {
  2690. newThumbnails.push(thumbnail);
  2691. imageLinks.add(thumbnail.url);
  2692. }
  2693. if (squarePhoto && condSize && leastSizeFulfilled) {
  2694. pSize = thumbnail.width;
  2695. }
  2696. }
  2697. if (thumbnails.length !== newThumbnails.length && thumbnails === baseObject.thumbnails && newThumbnails.length > 0) {
  2698. baseObject.thumbnails = newThumbnails;
  2699. } else {
  2700. newThumbnails.length = 0;
  2701. }
  2702. newThumbnails = null;
  2703. } else {
  2704. for (const thumbnail of thumbnails) {
  2705. if (thumbnail && thumbnail.url) {
  2706. imageLinks.add(thumbnail.url);
  2707. }
  2708. }
  2709. }
  2710. }
  2711. }
  2712.  
  2713. /** @type {Set<string>} */
  2714. const imageLinks = new Set();
  2715. for (const item of cnt.activeItems_) {
  2716. const liveChatTextMessageRenderer = (item || 0).liveChatTextMessageRenderer || 0;
  2717. if (!liveChatTextMessageRenderer) continue;
  2718.  
  2719. const messageRuns = (liveChatTextMessageRenderer.message || 0).runs || 0;
  2720.  
  2721. if (messageRuns && messageRuns.length > 0) {
  2722. for (const run of messageRuns) {
  2723. const emojiImage = (((run || 0).emoji || 0).image || 0);
  2724. const emojiThumbnails = (emojiImage.thumbnails || 0);
  2725.  
  2726. setThumbnails({
  2727. baseObject: emojiImage,
  2728. thumbnails: emojiThumbnails,
  2729. flag0: EMOJI_IMAGE_SINGLE_THUMBNAIL,
  2730.  
  2731. });
  2732.  
  2733. }
  2734. }
  2735.  
  2736. const authorPhoto = liveChatTextMessageRenderer.authorPhoto || 0;
  2737. const authorPhotoThumbnails = (authorPhoto.thumbnails || 0);
  2738.  
  2739. setThumbnails({
  2740. baseObject: authorPhoto,
  2741. thumbnails: authorPhotoThumbnails,
  2742. flag0: AUTHOR_PHOTO_SINGLE_THUMBNAIL,
  2743.  
  2744. });
  2745. }
  2746.  
  2747. if (ENABLE_PRELOAD_THUMBNAIL && kptPF !== null && (kptPF & (8 | 4))) {
  2748. if (emojiPrefetched.size > 512) emojiPrefetched.clear();
  2749. if (authorPhotoPrefetched.size > 68) authorPhotoPrefetched.clear();
  2750. imageLinks.forEach(imageLink => {
  2751. let d = false;
  2752. const isEmoji = imageLink.includes('/emoji/');
  2753. const pretechedSet = isEmoji ? emojiPrefetched : authorPhotoPrefetched;
  2754.  
  2755. if (!pretechedSet.has(imageLink)) {
  2756. pretechedSet.add(imageLink);
  2757. d = true;
  2758. }
  2759. if (d) {
  2760. const rel = kptPF & 8 ? 'subresource' : kptPF & 4 ? 'prefetch' : '';
  2761. if (rel) {
  2762. waitFor.push(linker(null, rel, imageLink, 'image'));
  2763. }
  2764. }
  2765.  
  2766.  
  2767. })
  2768. }
  2769.  
  2770. // console.log(cnt.activeItems_)
  2771. // console.log([...imageLinks.values()])
  2772. }
  2773. skipDontRender = (((cnt.$ || 0).items || 0).childElementCount || 0) === 0;
  2774. // console.log('ss1', Date.now())
  2775. if (waitFor.length > 0) {
  2776. await Promise.race([new Promise(r => setTimeout(r, 250)), Promise.all(waitFor)]);
  2777. }
  2778. // console.log('ss2', Date.now())
  2779. cnt.flushActiveItems66_();
  2780. skipDontRender = (((cnt.$ || 0).items || 0).childElementCount || 0) === 0;
  2781. const len2 = cnt.activeItems_.length;
  2782. const bAsync = len1 !== len2;
  2783. await Promise.resolve();
  2784. if (wcController) {
  2785. if (bAsync) {
  2786. cnt.async(() => {
  2787. wcController.afterOper();
  2788. });
  2789. } else {
  2790. wcController.afterOper();
  2791. }
  2792. }
  2793. if (changeMaxItemsToDisplay && this.data.maxItemsToDisplay === reducedMaxItemsToDisplay && tmpMaxItemsCount > reducedMaxItemsToDisplay) {
  2794. this.data.maxItemsToDisplay = tmpMaxItemsCount;
  2795.  
  2796. logger && console.log(`'maxItemsToDisplay' is restored from ${reducedMaxItemsToDisplay} to ${tmpMaxItemsCount}.`);
  2797. // console.log('changeMaxItemsToDisplay 02', this.data.maxItemsToDisplay, oMaxItemsToDisplay, reducedMaxItemsToDisplay)
  2798. } else if (changeMaxItemsToDisplay) {
  2799.  
  2800. logger && console.log(`'maxItemsToDisplay' cannot be restored`, {
  2801. maxItemsToDisplay: this.data.maxItemsToDisplay,
  2802. reducedMaxItemsToDisplay,
  2803. originalMaxItemsToDisplay: tmpMaxItemsCount
  2804. });
  2805. }
  2806. logger && console.log('[End]')
  2807.  
  2808. logger && console.groupEnd();
  2809.  
  2810. if (!ENABLE_NO_SMOOTH_TRANSFORM) {
  2811.  
  2812.  
  2813. const ff = () => {
  2814.  
  2815. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2816. // if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2817. if (!cnt.atBottom && cnt.allowScroll && cnt.hasUserJustInteracted11_ && !cnt.hasUserJustInteracted11_()) {
  2818. cnt.scrollToBottom_();
  2819.  
  2820. Promise.resolve().then(() => {
  2821.  
  2822. if (cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2823. if (!cnt.canScrollToBottom_()) cnt.scrollToBottom_();
  2824. });
  2825.  
  2826.  
  2827. }
  2828. }
  2829.  
  2830. ff();
  2831.  
  2832.  
  2833. Promise.resolve().then(ff);
  2834.  
  2835. // requestAnimationFrame(ff);
  2836. } else if (true) { // it might not be sticky to bottom when there is a full refresh.
  2837.  
  2838. const knt = cnt;
  2839. if (!scrollChatFn) {
  2840. const cnt = knt;
  2841. const f = () => {
  2842. const itemScroller = cnt.itemScroller;
  2843. if (!itemScroller || itemScroller.isConnected === false || cnt.isAttached === false) return;
  2844. if (!cnt.atBottom) {
  2845. cnt.scrollToBottom_();
  2846. } else if (itemScroller.scrollTop === 0) { // not yet interacted by user; cannot stick to bottom
  2847. itemScroller.scrollTop = itemScroller.scrollHeight;
  2848. }
  2849. };
  2850. scrollChatFn = () => Promise.resolve().then(f).then(f);
  2851. }
  2852.  
  2853. if (!ENABLE_FULL_RENDER_REQUIRED) scrollChatFn();
  2854. }
  2855.  
  2856. return 1;
  2857.  
  2858.  
  2859. } catch (e) {
  2860. console.warn(e);
  2861. }
  2862. }
  2863.  
  2864. mclp.flushActiveItems77_ = async function () {
  2865. try {
  2866.  
  2867. const cnt = this;
  2868. if (mlf > 1e9) mlf = 9;
  2869. let tid = ++mlf;
  2870. if (tid !== mlf || cnt.isAttached === false || (cnt.hostElement || cnt).isConnected === false) return;
  2871. if (!cnt.activeItems_ || cnt.activeItems_.length === 0) return;
  2872.  
  2873. // 4 times to maxItems to avoid frequent trimming.
  2874. // 1 ... 10 ... 20 ... 30 ... 40 ... 50 ... 60 => 16 ... 20 ... 30 ..... 60 ... => 16
  2875.  
  2876. const lockedMaxItemsToDisplay = this.data.maxItemsToDisplay944;
  2877. this.activeItems_.length > lockedMaxItemsToDisplay * 4 && lockedMaxItemsToDisplay > 4 && this.activeItems_.splice(0, this.activeItems_.length - lockedMaxItemsToDisplay - 1);
  2878. if (cnt.canScrollToBottom_()) {
  2879. if (cnt.mutexPromiseFA77) await cnt.mutexPromiseFA77;
  2880. if (tid !== mlf) return;
  2881. let qResolve = null;
  2882. cnt.mutexPromiseFA77 = new Promise(resolve => { qResolve = resolve; })
  2883. let res;
  2884. try {
  2885. res = await cnt.flushActiveItems78_(tid);
  2886. if (hasMoreMessageState === 1) {
  2887. hasMoreMessageState = 0;
  2888. const showMore = cnt.$['show-more'];
  2889. if (showMore) {
  2890. showMore.classList.remove('has-new-messages-miuzp');
  2891. }
  2892. }
  2893. } catch (e) {
  2894. console.warn(e);
  2895. }
  2896. qResolve && qResolve();
  2897. return res;
  2898. } else {
  2899. if (hasMoreMessageState === 0) {
  2900. hasMoreMessageState = 1;
  2901. const showMore = cnt.$['show-more'];
  2902. if (showMore) {
  2903. showMore.classList.add('has-new-messages-miuzp');
  2904. }
  2905. }
  2906. // cnt.flushActiveItems66_();
  2907. // this.activeItems_.length > this.data.maxItemsToDisplay && this.activeItems_.splice(0, this.activeItems_.length - this.data.maxItemsToDisplay);
  2908. return 2;
  2909. }
  2910. } catch (e) {
  2911. console.warn(e);
  2912. }
  2913. }
  2914.  
  2915. mclp.flushActiveItems_ = function () {
  2916. const cnt = this;
  2917.  
  2918. if (arguments.length !== 0 || !cnt.activeItems_ || !cnt.canScrollToBottom_) return cnt.flushActiveItems66_.apply(this, arguments);
  2919.  
  2920. if (cnt.activeItems_.length === 0) {
  2921. cnt.__intermediate_delay__ = null;
  2922. return;
  2923. }
  2924.  
  2925. const cntData = ((cnt || 0).data || 0);
  2926. if (cntData.maxItemsToDisplay944 === undefined) {
  2927. cntData.maxItemsToDisplay944 = null;
  2928. if (cntData.maxItemsToDisplay > MAX_ITEMS_FOR_TOTAL_DISPLAY) cntData.maxItemsToDisplay = MAX_ITEMS_FOR_TOTAL_DISPLAY;
  2929. cntData.maxItemsToDisplay944 = cntData.maxItemsToDisplay || null;
  2930. }
  2931.  
  2932. // ignore previous __intermediate_delay__ and create a new one
  2933. cnt.__intermediate_delay__ = new Promise(resolve => {
  2934. cnt.flushActiveItems77_().then(rt => {
  2935. if (rt === 1) resolve(1); // success, scroll to bottom
  2936. else if (rt === 2) resolve(2); // success, trim
  2937. else resolve(-1); // skip
  2938. });
  2939. });
  2940.  
  2941. }
  2942. console.log("flushActiveItems_", "OK");
  2943. } else {
  2944. console.log("flushActiveItems_", "NG");
  2945. }
  2946.  
  2947. mclp.delayFlushActiveItemsAfterUserAction11_ = async function () {
  2948. try {
  2949. if (mlg > 1e9) mlg = 9;
  2950. const tid = ++mlg;
  2951. const keepTrialCond = () => this.atBottom && this.allowScroll && (tid === mlg) && this.isAttached === true && this.activeItems_.length >= 1 && (this.hostElement || 0).isConnected === true;
  2952. const runCond = () => this.canScrollToBottom_();
  2953. if (!keepTrialCond()) return;
  2954. if (runCond()) return this.flushActiveItems_() | 1; // avoid return promise
  2955. await new Promise(r => setTimeout(r, 80));
  2956. if (!keepTrialCond()) return;
  2957. if (runCond()) return this.flushActiveItems_() | 1;
  2958. await new Promise(requestAnimationFrame);
  2959. if (runCond()) return this.flushActiveItems_() | 1;
  2960. } catch (e) {
  2961. console.warn(e);
  2962. }
  2963. }
  2964.  
  2965. if ((mclp.atBottomChanged_ || 0).length === 1) {
  2966. // note: if the scrolling is too frequent, the show more visibility might get wrong.
  2967. assertor(() => fnIntegrity(mclp.atBottomChanged_, '1.75.39'));
  2968.  
  2969. const querySelector = HTMLElement.prototype.querySelector;
  2970. const U = (element) => ({
  2971. querySelector: (selector) => querySelector.call(element, selector)
  2972. });
  2973.  
  2974. let qid = 0;
  2975. mclp.atBottomChanged_ = function (a) {
  2976. let tid = ++qid;
  2977. var b = this;
  2978. a ? this.hideShowMoreAsync_ || (this.hideShowMoreAsync_ = this.async(function () {
  2979. if (tid !== qid) return;
  2980. U(b.hostElement).querySelector("#show-more").style.visibility = "hidden"
  2981. }, 200)) : (this.hideShowMoreAsync_ && this.cancelAsync(this.hideShowMoreAsync_),
  2982. this.hideShowMoreAsync_ = null,
  2983. U(this.hostElement).querySelector("#show-more").style.visibility = "visible")
  2984. }
  2985.  
  2986. console.log("atBottomChanged_", "OK");
  2987. } else {
  2988. console.log("atBottomChanged_", "NG");
  2989. }
  2990.  
  2991. if ((mclp.onScrollItems_ || 0).length === 1) {
  2992.  
  2993. assertor(() => fnIntegrity(mclp.onScrollItems_, '1.17.9'));
  2994. mclp.onScrollItems66_ = mclp.onScrollItems_;
  2995. mclp.onScrollItems77_ = async function (evt) {
  2996. if (myw > 1e9) myw = 9;
  2997. let tid = ++myw;
  2998.  
  2999. await new Promise(requestAnimationFrame);
  3000.  
  3001. if (tid !== myw) {
  3002. return;
  3003. }
  3004.  
  3005. const cnt = this;
  3006.  
  3007. await Promise.resolve();
  3008. if (USE_OPTIMIZED_ON_SCROLL_ITEMS) {
  3009. await Promise.resolve().then(() => {
  3010. this.ytRendererBehavior.onScroll(evt);
  3011. }).then(() => {
  3012. if (this.canScrollToBottom_()) {
  3013. const hasUserJustInteracted = this.hasUserJustInteracted11_ ? this.hasUserJustInteracted11_() : true;
  3014. if (hasUserJustInteracted) {
  3015. // only when there is an user action
  3016. this.setAtBottom();
  3017. return 1;
  3018. }
  3019. } else {
  3020. // no message inserting
  3021. this.setAtBottom();
  3022. return 1;
  3023. }
  3024. }).then((r) => {
  3025.  
  3026. if (this.activeItems_.length) {
  3027.  
  3028. if (this.canScrollToBottom_()) {
  3029. this.flushActiveItems_();
  3030. return 1 && r;
  3031. } else if (this.atBottom && this.allowScroll && (this.hasUserJustInteracted11_ && this.hasUserJustInteracted11_())) {
  3032. // delayed due to user action
  3033. this.delayFlushActiveItemsAfterUserAction11_ && this.delayFlushActiveItemsAfterUserAction11_();
  3034. return 0;
  3035. }
  3036. }
  3037. }).then((r) => {
  3038. if (r) {
  3039. // ensure setAtBottom is correctly set
  3040. this.setAtBottom();
  3041. }
  3042. });
  3043. } else {
  3044. cnt.onScrollItems66_(evt);
  3045. }
  3046.  
  3047. await Promise.resolve();
  3048.  
  3049. }
  3050.  
  3051. mclp.onScrollItems_ = function (evt) {
  3052.  
  3053. const cnt = this;
  3054. cnt.__intermediate_delay__ = new Promise(resolve => {
  3055. cnt.onScrollItems77_(evt).then(() => {
  3056. resolve();
  3057. });
  3058. });
  3059. }
  3060. console.log("onScrollItems_", "OK");
  3061. } else {
  3062. console.log("onScrollItems_", "NG");
  3063. }
  3064.  
  3065. if ((mclp.handleLiveChatActions_ || 0).length === 1) {
  3066.  
  3067. assertor(() => fnIntegrity(mclp.handleLiveChatActions_, '1.31.17'));
  3068. mclp.handleLiveChatActions66_ = mclp.handleLiveChatActions_;
  3069.  
  3070. mclp.handleLiveChatActions77_ = async function (arr) {
  3071. if (typeof (arr || 0).length !== 'number') {
  3072. this.handleLiveChatActions66_(arr);
  3073. return;
  3074. }
  3075. if (mzt > 1e9) mzt = 9;
  3076. let tid = ++mzt;
  3077.  
  3078. if (zarr === null) zarr = arr;
  3079. else Array.prototype.push.apply(zarr, arr);
  3080. arr = null;
  3081.  
  3082. await new Promise(requestAnimationFrame);
  3083.  
  3084. if (tid !== mzt || zarr === null) {
  3085. return;
  3086. }
  3087.  
  3088. const carr = zarr;
  3089. zarr = null;
  3090.  
  3091. await Promise.resolve();
  3092. this.handleLiveChatActions66_(carr);
  3093. await Promise.resolve();
  3094.  
  3095. }
  3096.  
  3097. mclp.handleLiveChatActions_ = function (arr) {
  3098.  
  3099. const cnt = this;
  3100. cnt.__intermediate_delay__ = new Promise(resolve => {
  3101. cnt.handleLiveChatActions77_(arr).then(() => {
  3102. resolve();
  3103. });
  3104. });
  3105. }
  3106. console.log("handleLiveChatActions_", "OK");
  3107. } else {
  3108. console.log("handleLiveChatActions_", "NG");
  3109. }
  3110.  
  3111. mclp.hasUserJustInteracted11_ = () => {
  3112. const t = dateNow();
  3113. return (t - lastWheel < 80) || currentMouseDown || currentTouchDown || (t - lastUserInteraction < 80);
  3114. }
  3115.  
  3116. if ((mclp.canScrollToBottom_ || 0).length === 0) {
  3117.  
  3118. assertor(() => fnIntegrity(mclp.canScrollToBottom_, '0.9.5'));
  3119.  
  3120. mclp.canScrollToBottom_ = function () {
  3121. return this.atBottom && this.allowScroll && !this.hasUserJustInteracted11_();
  3122. }
  3123.  
  3124. console.log("canScrollToBottom_", "OK");
  3125. } else {
  3126. console.log("canScrollToBottom_", "NG");
  3127. }
  3128.  
  3129. if (ENABLE_NO_SMOOTH_TRANSFORM) {
  3130.  
  3131. mclp.isSmoothScrollEnabled_ = function () {
  3132. return false;
  3133. }
  3134.  
  3135. mclp.maybeResizeScrollContainer_ = function () {
  3136. //
  3137. }
  3138.  
  3139. mclp.refreshOffsetContainerHeight_ = function () {
  3140. //
  3141. }
  3142.  
  3143. mclp.smoothScroll_ = function () {
  3144. //
  3145. }
  3146.  
  3147. mclp.resetSmoothScroll_ = function () {
  3148. //
  3149. }
  3150. console.log("ENABLE_NO_SMOOTH_TRANSFORM", "OK");
  3151. } else {
  3152. console.log("ENABLE_NO_SMOOTH_TRANSFORM", "NG");
  3153. }
  3154.  
  3155. if (typeof mclp.handleAddChatItemAction_ === 'function' && !mclp.handleAddChatItemAction66_ && FIX_THUMBNAIL_SIZE_ON_ITEM_ADDITION) {
  3156.  
  3157.  
  3158.  
  3159. function setThumbnails(config) {
  3160. const { baseObject, thumbnails, flag0 } = config;
  3161. if (thumbnails && thumbnails.length > 0) {
  3162. if (flag0 > 0) {
  3163. let pSize = 0;
  3164. let newThumbnails = [];
  3165. for (const thumbnail of thumbnails) {
  3166. if (!thumbnail || !thumbnail.url) continue;
  3167. const squarePhoto = thumbnail.width === thumbnail.height && typeof thumbnail.width === 'number';
  3168. const condSize = pSize <= 0 || (flag0 === 1 ? pSize > thumbnail.width : pSize < thumbnail.width);
  3169. const leastSizeFulfilled = squarePhoto ? thumbnail.width >= LEAST_IMAGE_SIZE : true;
  3170. if ((!squarePhoto || condSize) && leastSizeFulfilled) {
  3171. newThumbnails.push(thumbnail);
  3172. // imageLinks.add(thumbnail.url);
  3173. }
  3174. if (squarePhoto && condSize && leastSizeFulfilled) {
  3175. pSize = thumbnail.width;
  3176. }
  3177. }
  3178. if (thumbnails.length !== newThumbnails.length && thumbnails === baseObject.thumbnails && newThumbnails.length > 0) {
  3179. baseObject.thumbnails = newThumbnails;
  3180. } else {
  3181. newThumbnails.length = 0;
  3182. }
  3183. newThumbnails = null;
  3184. } else {
  3185. for (const thumbnail of thumbnails) {
  3186. if (thumbnail && thumbnail.url) {
  3187. // imageLinks.add(thumbnail.url);
  3188. }
  3189. }
  3190. }
  3191. }
  3192. }
  3193.  
  3194.  
  3195. function fixLiveChatItem(a){
  3196.  
  3197.  
  3198. const liveChatTextMessageRenderer = ((a || 0).item || 0).liveChatTextMessageRenderer || 0;
  3199. if (liveChatTextMessageRenderer) {
  3200.  
  3201.  
  3202. const messageRuns = (liveChatTextMessageRenderer.message || 0).runs || 0;
  3203.  
  3204. if (messageRuns && messageRuns.length > 0) {
  3205. for (const run of messageRuns) {
  3206. const emojiImage = (((run || 0).emoji || 0).image || 0);
  3207. const emojiThumbnails = (emojiImage.thumbnails || 0);
  3208.  
  3209. setThumbnails({
  3210. baseObject: emojiImage,
  3211. thumbnails: emojiThumbnails,
  3212. flag0: EMOJI_IMAGE_SINGLE_THUMBNAIL,
  3213.  
  3214. });
  3215.  
  3216. }
  3217. }
  3218.  
  3219. const authorPhoto = liveChatTextMessageRenderer.authorPhoto || 0;
  3220. setThumbnails({
  3221. baseObject:authorPhoto,
  3222. thumbnails:authorPhoto.thumbnails,
  3223. flag0: AUTHOR_PHOTO_SINGLE_THUMBNAIL,
  3224. })
  3225. }
  3226.  
  3227. }
  3228. mclp.handleAddChatItemAction66_ = mclp.handleAddChatItemAction_;
  3229. mclp.handleAddChatItemAction_ = function (a) {
  3230.  
  3231. try {
  3232. if (arguments.length >= 1 && a && typeof a === 'object' && !('length' in a)) {
  3233. fixLiveChatItem(a);
  3234. console.assert(arguments[0] === a);
  3235. }
  3236.  
  3237. } catch (e) { }
  3238. return this.handleAddChatItemAction66_.apply(this, arguments);
  3239. }
  3240.  
  3241.  
  3242. if(FIX_THUMBNAIL_SIZE_ON_ITEM_ADDITION) console.log("handleAddChatItemAction_ [ FIX_THUMBNAIL_SIZE_ON_ITEM_ADDITION ]", "OK");
  3243. }else{
  3244.  
  3245.  
  3246. if(FIX_THUMBNAIL_SIZE_ON_ITEM_ADDITION) console.log("handleAddChatItemAction_ [ FIX_THUMBNAIL_SIZE_ON_ITEM_ADDITION ]", "OK");
  3247. }
  3248.  
  3249.  
  3250. if (typeof mclp.handleReplaceChatItemAction_ === 'function' && !mclp.handleReplaceChatItemAction66_ && FIX_THUMBNAIL_SIZE_ON_ITEM_REPLACEMENT) {
  3251.  
  3252. mclp.handleReplaceChatItemAction66_ = mclp.handleReplaceChatItemAction_;
  3253. mclp.handleReplaceChatItemAction_ = function (k) {
  3254. try {
  3255. if (k && k.replacementItem) {
  3256. let a = k.replacementItem;
  3257. if (a && typeof a === 'object' && !('length' in a)) {
  3258. fixLiveChatItem(a);
  3259. console.assert(arguments[0] === a);
  3260. }
  3261. }
  3262. } catch (e) { }
  3263. return this.handleReplaceChatItemAction66_.apply(this, arguments);
  3264. }
  3265.  
  3266.  
  3267.  
  3268. if(FIX_THUMBNAIL_SIZE_ON_ITEM_REPLACEMENT) console.log("handleReplaceChatItemAction_ [ FIX_THUMBNAIL_SIZE_ON_ITEM_REPLACEMENT ]", "OK");
  3269.  
  3270. }else{
  3271.  
  3272.  
  3273. if(FIX_THUMBNAIL_SIZE_ON_ITEM_REPLACEMENT) console.log("handleReplaceChatItemAction_ [ FIX_THUMBNAIL_SIZE_ON_ITEM_REPLACEMENT ]", "OK");
  3274. }
  3275.  
  3276. console.log("[End]");
  3277. console.groupEnd();
  3278.  
  3279. });
  3280.  
  3281.  
  3282. const tickerContainerSetAttribute = function (attrName, attrValue) { // ensure '14.30000001%'.toFixed(1)
  3283.  
  3284. let yd = (this.__dataHost || (this.inst || 0).__dataHost || 0).__data;
  3285.  
  3286. if (arguments.length === 2 && attrName === 'style' && yd && attrValue) {
  3287.  
  3288. // let v = yd.containerStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
  3289. let v = `${attrValue}`;
  3290. // conside a ticker is 101px width
  3291. // 1% = 1.01px
  3292. // 0.2% = 0.202px
  3293.  
  3294.  
  3295. const ratio1 = (yd.ratio * 100);
  3296. if (ratio1 > -1) { // avoid NaN
  3297.  
  3298. // countdownDurationMs
  3299. // 600000 - 0.2% <1% = 6s> <0.2% = 1.2s>
  3300. // 300000 - 0.5% <1% = 3s> <0.5% = 1.5s>
  3301. // 150000 - 1% <1% = 1.5s>
  3302. // 75000 - 2% <1% =0.75s > <2% = 1.5s>
  3303. // 30000 - 5% <1% =0.3s > <5% = 1.5s>
  3304.  
  3305. // 99px * 5% = 4.95px
  3306.  
  3307. // 15000 - 10% <1% =0.15s > <10% = 1.5s>
  3308.  
  3309.  
  3310. // 1% Duration
  3311.  
  3312. let ratio2 = ratio1;
  3313.  
  3314. const ydd = yd.data;
  3315. const d1 = ydd.durationSec;
  3316. const d2 = ydd.fullDurationSec;
  3317.  
  3318. if (d1 === d2 && d1 > 1) {
  3319.  
  3320. if (d1 > 400) ratio2 = Math.round(ratio2 * 5) / 5; // 0.2%
  3321. else if (d1 > 200) ratio2 = Math.round(ratio2 * 2) / 2; // 0.5%
  3322. else if (d1 > 100) ratio2 = Math.round(ratio2 * 1) / 1; // 1%
  3323. else if (d1 > 50) ratio2 = Math.round(ratio2 * 0.5) / 0.5; // 2%
  3324. else if (d1 > 25) ratio2 = Math.round(ratio2 * 0.2) / 0.2; // 5% (max => 99px * 5% = 4.95px)
  3325. else ratio2 = Math.round(ratio2 * 0.2) / 0.2;
  3326.  
  3327. } else {
  3328. ratio2 = Math.round(ratio2 * 5) / 5; // 0.2% (min)
  3329. }
  3330.  
  3331. // ratio2 = Math.round(ratio2 * 5) / 5;
  3332. ratio2 = ratio2.toFixed(1);
  3333. v = v.replace(`${ratio1}%`, `${ratio2}%`).replace(`${ratio1}%`, `${ratio2}%`);
  3334.  
  3335. if (yd.__style_last__ === v) return;
  3336. yd.__style_last__ = v;
  3337. // do not consider any delay here.
  3338. // it shall be inside the looping for all properties changes. all the css background ops are in the same microtask.
  3339.  
  3340. }
  3341.  
  3342. HTMLElement.prototype.setAttribute.call(dr(this), attrName, v);
  3343.  
  3344.  
  3345. } else {
  3346. HTMLElement.prototype.setAttribute.apply(dr(this), arguments);
  3347. }
  3348.  
  3349. };
  3350.  
  3351.  
  3352. const fpTicker = (renderer) => {
  3353. const cnt = renderer.inst || renderer;
  3354. assertor(() => typeof (cnt || 0).is === 'string');
  3355. assertor(() => ((cnt || 0).hostElement || 0).nodeType === 1);
  3356. const container = (cnt.$ || 0).container;
  3357. if (container) {
  3358. assertor(() => (container || 0).nodeType === 1);
  3359. assertor(() => typeof container.setAttribute === 'function');
  3360. container.setAttribute = tickerContainerSetAttribute;
  3361. } else {
  3362. console.warn(`"container" does not exist in ${cnt.is}`);
  3363. }
  3364. };
  3365.  
  3366. const tags = ["yt-live-chat-ticker-paid-message-item-renderer", "yt-live-chat-ticker-paid-sticker-item-renderer",
  3367. "yt-live-chat-ticker-renderer", "yt-live-chat-ticker-sponsor-item-renderer"];
  3368.  
  3369.  
  3370. Promise.all(tags.map(tag => customElements.whenDefined(tag))).then(() => {
  3371.  
  3372. mightFirstCheckOnYtInit();
  3373. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-ticker-... hacks");
  3374. console.log("[Begin]");
  3375.  
  3376. for (const tag of tags) {
  3377. const dummy = document.createElement(tag);
  3378.  
  3379. const cProto = getProto(dummy);
  3380. if (!cProto || !cProto.attached) {
  3381. console.warn(`proto.attached for ${tag} is unavailable.`);
  3382. continue;
  3383. }
  3384.  
  3385. cProto.attached77 = cProto.attached;
  3386.  
  3387. cProto.attached = function () {
  3388. fpTicker(this.hostElement || this);
  3389. return this.attached77();
  3390. }
  3391.  
  3392. for (const elm of document.getElementsByTagName(tag)) {
  3393. if ((elm || elm.inst).isAttached === true) {
  3394. fpTicker(elm);
  3395. }
  3396. }
  3397.  
  3398. if (ENABLE_RAF_HACK_TICKERS && rafHub !== null) {
  3399.  
  3400. // cancelable - this.rafId < isAnimationPausedChanged >
  3401.  
  3402. let doHack = false;
  3403.  
  3404. if (typeof cProto.startCountdown === 'function' && typeof cProto.updateTimeout === 'function' && typeof cProto.isAnimationPausedChanged === 'function') {
  3405.  
  3406. console.log('startCountdown', typeof cProto.startCountdown)
  3407. console.log('updateTimeout', typeof cProto.updateTimeout)
  3408. console.log('isAnimationPausedChanged', typeof cProto.isAnimationPausedChanged)
  3409.  
  3410. doHack = fnIntegrity(cProto.startCountdown, '2.66.37') && fnIntegrity(cProto.updateTimeout, '1.76.45') && fnIntegrity(cProto.isAnimationPausedChanged, '2.56.30')
  3411.  
  3412. }
  3413. if (doHack) {
  3414.  
  3415. cProto.startCountdown = function (a, b) {
  3416. // console.log('cProto.startCountdown', tag) // yt-live-chat-ticker-sponsor-item-renderer
  3417. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  3418. b = void 0 === b ? 0 : b;
  3419. void 0 !== a && (this.countdownMs = 1E3 * a,
  3420. this.countdownDurationMs = b ? 1E3 * b : this.countdownMs,
  3421. this.ratio = 1,
  3422. this.lastCountdownTimeMs || this.isAnimationPaused || (this.lastCountdownTimeMs = performance.now(),
  3423. this.rafId = rafHub.request(this.boundUpdateTimeout37_)))
  3424. };
  3425.  
  3426. cProto.updateTimeout = function (a) {
  3427. // console.log('cProto.updateTimeout', tag) // yt-live-chat-ticker-sponsor-item-renderer
  3428. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  3429. this.countdownMs = Math.max(0, this.countdownMs - (a - (this.lastCountdownTimeMs || 0)));
  3430. this.ratio = this.countdownMs / this.countdownDurationMs;
  3431. this.isAttached && this.countdownMs ? (this.lastCountdownTimeMs = a,
  3432. this.rafId = rafHub.request(this.boundUpdateTimeout37_)) : (this.lastCountdownTimeMs = null,
  3433. this.isAttached && ("auto" === this.hostElement.style.width && this.setContainerWidth(),
  3434. this.slideDown()))
  3435. };
  3436.  
  3437. cProto.isAnimationPausedChanged = function (a, b) {
  3438. if (!this.boundUpdateTimeout37_) this.boundUpdateTimeout37_ = this.updateTimeout.bind(this);
  3439. a ? rafHub.cancel(this.rafId) : !a && b && (a = this.lastCountdownTimeMs || 0,
  3440. this.detlaSincePausedSecs && (a = (this.lastCountdownTimeMs || 0) + 1E3 * this.detlaSincePausedSecs,
  3441. this.detlaSincePausedSecs = 0),
  3442. this.boundUpdateTimeout37_(a),
  3443. this.lastCountdownTimeMs = window.performance.now())
  3444. };
  3445.  
  3446. console.log('RAF_HACK_TICKERS', tag, "OK")
  3447. } else if (tag === 'yt-live-chat-ticker-renderer') {
  3448.  
  3449. // no timer function to be set on yt-live-chat-ticker-renderer
  3450.  
  3451. } else {
  3452.  
  3453. console.log('RAF_HACK_TICKERS', tag, "NG")
  3454. }
  3455.  
  3456. }
  3457.  
  3458. }
  3459. console.log("[End]");
  3460. console.groupEnd();
  3461.  
  3462.  
  3463. })
  3464.  
  3465.  
  3466. if (ENABLE_RAF_HACK_INPUT_RENDERER && rafHub !== null) {
  3467.  
  3468.  
  3469. customElements.whenDefined("yt-live-chat-message-input-renderer").then(() => {
  3470.  
  3471. mightFirstCheckOnYtInit();
  3472. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-message-input-renderer hacks");
  3473. console.log("[Begin]");
  3474. (() => {
  3475.  
  3476. const tag = "yt-live-chat-message-input-renderer"
  3477. const dummy = document.createElement(tag);
  3478.  
  3479. const cProto = getProto(dummy);
  3480. if (!cProto || !cProto.attached) {
  3481. console.warn(`proto.attached for ${tag} is unavailable.`);
  3482. return;
  3483. }
  3484.  
  3485. let doHack = false;
  3486. if (typeof cProto.handleTimeout === 'function' && typeof cProto.updateTimeout === 'function') {
  3487.  
  3488. // not cancellable
  3489. console.log('handleTimeout', typeof cProto.handleTimeout)
  3490. console.log('updateTimeout', typeof cProto.updateTimeout)
  3491.  
  3492. doHack = fnIntegrity(cProto.handleTimeout, '1.27.16') && fnIntegrity(cProto.updateTimeout, '1.50.33');
  3493.  
  3494. }
  3495.  
  3496. if (doHack) {
  3497.  
  3498. cProto.handleTimeout = function (a) {
  3499. console.log('cProto.handleTimeout', tag)
  3500. if (!this.boundUpdateTimeout38_) this.boundUpdateTimeout38_ = this.updateTimeout.bind(this);
  3501. this.timeoutDurationMs = this.timeoutMs = a;
  3502. this.countdownRatio = 1;
  3503. 0 === this.lastTimeoutTimeMs && rafHub.request(this.boundUpdateTimeout38_)
  3504. };
  3505. cProto.updateTimeout = function (a) {
  3506. console.log('cProto.updateTimeout', tag)
  3507. if (!this.boundUpdateTimeout38_) this.boundUpdateTimeout38_ = this.updateTimeout.bind(this);
  3508. this.lastTimeoutTimeMs && (this.timeoutMs = Math.max(0, this.timeoutMs - (a - this.lastTimeoutTimeMs)),
  3509. this.countdownRatio = this.timeoutMs / this.timeoutDurationMs);
  3510. this.isAttached && this.timeoutMs ? (this.lastTimeoutTimeMs = a,
  3511. rafHub.request(this.boundUpdateTimeout38_)) : this.lastTimeoutTimeMs = 0
  3512. };
  3513.  
  3514. console.log('RAF_HACK_INPUT_RENDERER', tag, "OK")
  3515. } else {
  3516.  
  3517. console.log('RAF_HACK_INPUT_RENDERER', tag, "NG")
  3518. }
  3519.  
  3520. })();
  3521.  
  3522. console.log("[End]");
  3523.  
  3524. console.groupEnd();
  3525.  
  3526.  
  3527. })
  3528.  
  3529.  
  3530. }
  3531.  
  3532. if (ENABLE_RAF_HACK_EMOJI_PICKER && rafHub !== null) {
  3533.  
  3534.  
  3535. customElements.whenDefined("yt-emoji-picker-renderer").then(() => {
  3536.  
  3537. mightFirstCheckOnYtInit();
  3538. groupCollapsed("YouTube Super Fast Chat", " | yt-emoji-picker-renderer hacks");
  3539. console.log("[Begin]");
  3540. (() => {
  3541.  
  3542. const tag = "yt-emoji-picker-renderer"
  3543. const dummy = document.createElement(tag);
  3544.  
  3545. const cProto = getProto(dummy);
  3546. if (!cProto || !cProto.attached) {
  3547. console.warn(`proto.attached for ${tag} is unavailable.`);
  3548. return;
  3549. }
  3550.  
  3551. let doHack = false;
  3552. if (typeof cProto.animateScroll_ === 'function') {
  3553.  
  3554. // not cancellable
  3555. console.log('animateScroll_', typeof cProto.animateScroll_)
  3556.  
  3557. doHack = fnIntegrity(cProto.animateScroll_, '1.102.49')
  3558.  
  3559. }
  3560.  
  3561. if (doHack) {
  3562.  
  3563. const querySelector = HTMLElement.prototype.querySelector;
  3564. const U = (element) => ({
  3565. querySelector: (selector) => querySelector.call(element, selector)
  3566. });
  3567.  
  3568. cProto.animateScroll_ = function (a) {
  3569. // console.log('cProto.animateScroll_', tag) // yt-emoji-picker-renderer
  3570. if (!this.boundAnimateScroll39_) this.boundAnimateScroll39_ = this.animateScroll_.bind(this);
  3571. this.lastAnimationTime_ || (this.lastAnimationTime_ = a);
  3572. a -= this.lastAnimationTime_;
  3573. 200 > a ? (U(this.hostElement).querySelector("#categories").scrollTop = this.animationStart_ + (this.animationEnd_ - this.animationStart_) * a / 200,
  3574. rafHub.request(this.boundAnimateScroll39_)) : (null != this.animationEnd_ && (U(this.hostElement).querySelector("#categories").scrollTop = this.animationEnd_),
  3575. this.animationEnd_ = this.animationStart_ = null,
  3576. this.lastAnimationTime_ = 0);
  3577. this.updateButtons_()
  3578. }
  3579.  
  3580. console.log('ENABLE_RAF_HACK_EMOJI_PICKER', tag, "OK")
  3581. } else {
  3582.  
  3583. console.log('ENABLE_RAF_HACK_EMOJI_PICKER', tag, "NG")
  3584. }
  3585.  
  3586. })();
  3587.  
  3588. console.log("[End]");
  3589.  
  3590. console.groupEnd();
  3591. });
  3592. }
  3593.  
  3594. if (ENABLE_RAF_HACK_DOCKED_MESSAGE && rafHub !== null) {
  3595.  
  3596.  
  3597. customElements.whenDefined("yt-live-chat-docked-message").then(() => {
  3598.  
  3599. mightFirstCheckOnYtInit();
  3600. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-docked-message hacks");
  3601. console.log("[Begin]");
  3602. (() => {
  3603.  
  3604. const tag = "yt-live-chat-docked-message"
  3605. const dummy = document.createElement(tag);
  3606.  
  3607. const cProto = getProto(dummy);
  3608. if (!cProto || !cProto.attached) {
  3609. console.warn(`proto.attached for ${tag} is unavailable.`);
  3610. return;
  3611. }
  3612.  
  3613. let doHack = false;
  3614. if (typeof cProto.detached === 'function' && typeof cProto.checkIntersections === 'function' && typeof cProto.onDockableMessagesChanged === 'function' && typeof cProto.boundCheckIntersections === 'undefined') {
  3615.  
  3616. // cancelable - this.intersectRAF <detached>
  3617. // yt-live-chat-docked-message
  3618. // boundCheckIntersections <-> checkIntersections
  3619. // onDockableMessagesChanged
  3620. // this.intersectRAF = window.requestAnimationFrame(this.boundCheckIntersections);
  3621.  
  3622. console.log('detached', typeof cProto.detached)
  3623. console.log('checkIntersections', typeof cProto.checkIntersections)
  3624. console.log('onDockableMessagesChanged', typeof cProto.onDockableMessagesChanged)
  3625.  
  3626. doHack = fnIntegrity(cProto.detached, '0.32.22') && fnIntegrity(cProto.checkIntersections, '0.128.85') && fnIntegrity(cProto.onDockableMessagesChanged, '0.20.11')
  3627.  
  3628. }
  3629.  
  3630. if (doHack) {
  3631.  
  3632. cProto.checkIntersections = function () {
  3633. console.log('cProto.checkIntersections', tag)
  3634. if (this.dockableMessages.length) {
  3635. this.intersectRAF = rafHub.request(this.boundCheckIntersections);
  3636. var a = this.dockableMessages[0]
  3637. , b = this.hostElement.getBoundingClientRect();
  3638. a = a.getBoundingClientRect();
  3639. var c = a.top - b.top
  3640. , d = 8 >= c;
  3641. c = 8 >= c - this.hostElement.clientHeight;
  3642. if (d) {
  3643. for (var e; d;) {
  3644. e = this.dockableMessages.shift();
  3645. d = this.dockableMessages[0];
  3646. if (!d)
  3647. break;
  3648. d = d.getBoundingClientRect();
  3649. c = d.top - b.top;
  3650. var f = 8 >= c;
  3651. if (8 >= c - a.height)
  3652. if (f)
  3653. a = d;
  3654. else
  3655. return;
  3656. d = f
  3657. }
  3658. this.dock(e)
  3659. } else
  3660. c && this.dockedItem && this.clear()
  3661. } else
  3662. this.intersectRAF = 0
  3663. }
  3664.  
  3665. cProto.onDockableMessagesChanged = function () {
  3666. // console.log('cProto.onDockableMessagesChanged', tag) // yt-live-chat-docked-message
  3667. this.dockableMessages.length && !this.intersectRAF && (this.intersectRAF = rafHub.request(this.boundCheckIntersections))
  3668. }
  3669.  
  3670. cProto.detached = function () {
  3671. this.intersectRAF && rafHub.cancel(this.intersectRAF)
  3672. }
  3673.  
  3674. console.log('ENABLE_RAF_HACK_DOCKED_MESSAGE', tag, "OK")
  3675. } else {
  3676.  
  3677. console.log('ENABLE_RAF_HACK_DOCKED_MESSAGE', tag, "NG")
  3678. }
  3679.  
  3680. })();
  3681.  
  3682. console.log("[End]");
  3683.  
  3684. console.groupEnd();
  3685.  
  3686. });
  3687.  
  3688. }
  3689.  
  3690. if(FIX_SETSRC_AND_THUMBNAILCHANGE_){
  3691.  
  3692.  
  3693. customElements.whenDefined("yt-img-shadow").then(() => {
  3694.  
  3695. mightFirstCheckOnYtInit();
  3696. groupCollapsed("YouTube Super Fast Chat", " | yt-img-shadow hacks");
  3697. console.log("[Begin]");
  3698. (() => {
  3699.  
  3700. const tag = "yt-img-shadow"
  3701. const dummy = document.createElement(tag);
  3702.  
  3703. const cProto = getProto(dummy);
  3704. if (!cProto || !cProto.attached) {
  3705. console.warn(`proto.attached for ${tag} is unavailable.`);
  3706. return;
  3707. }
  3708.  
  3709. if(typeof cProto.thumbnailChanged_ === 'function' && !cProto.thumbnailChanged66_){
  3710.  
  3711. cProto.thumbnailChanged66_ = cProto.thumbnailChanged_;
  3712. cProto.thumbnailChanged_ = function(a){
  3713.  
  3714. if(this.oldThumbnail_){
  3715.  
  3716. /*
  3717. console.log('old', this.oldThumbnail_.thumbnails)
  3718. console.log('new', this.thumbnail.thumbnails)
  3719. console.log(3466, this.oldThumbnail_.thumbnails === this.thumbnail.thumbnails)
  3720. */
  3721. if( this.oldThumbnail_.thumbnails === this.thumbnail.thumbnails) return;
  3722. }
  3723. return this.thumbnailChanged66_.apply(this,arguments)
  3724.  
  3725. }
  3726. console.log("cProto.thumbnailChanged_ - OK");
  3727.  
  3728. }else{
  3729. console.log("cProto.thumbnailChanged_ - NG");
  3730.  
  3731. }
  3732. if(typeof cProto.setSrc_ === 'function' && !cProto.setSrc66_){
  3733. console.log("cProto.setSrc_ - OK");
  3734.  
  3735. cProto.setSrc66_ = cProto.setSrc_;
  3736. cProto.setSrc_ = function(a){
  3737.  
  3738.  
  3739. let toSet = true;
  3740.  
  3741. if(this.$.img.src=== a) toSet = false;
  3742. else{
  3743.  
  3744. /*
  3745. const isEmoji = a.includes('/emoji/');
  3746. const pretechedSet = isEmoji ? emojiPrefetched : authorPhotoPrefetched;
  3747. if(pretechedSet.size>0){
  3748. if(!pretechedSet.has(a)) toSet = false;
  3749. }
  3750. */
  3751. }
  3752.  
  3753.  
  3754. // console.log( toSet, this.$.img.src=== a, this.$.img.src, a)
  3755.  
  3756. if(!toSet) return;
  3757.  
  3758. return this.setSrc66_.apply(this,arguments)
  3759.  
  3760. }
  3761.  
  3762. }else{
  3763.  
  3764. console.log("cProto.setSrc_ - NG");
  3765. }
  3766.  
  3767. })();
  3768.  
  3769. console.log("[End]");
  3770.  
  3771. console.groupEnd();
  3772.  
  3773. });
  3774.  
  3775. }
  3776.  
  3777. if(FIX_THUMBNAIL_DATACHANGED){
  3778.  
  3779.  
  3780.  
  3781. customElements.whenDefined("yt-live-chat-author-badge-renderer").then(() => {
  3782.  
  3783. mightFirstCheckOnYtInit();
  3784. groupCollapsed("YouTube Super Fast Chat", " | yt-live-chat-author-badge-renderer hacks");
  3785. console.log("[Begin]");
  3786. (() => {
  3787.  
  3788. const tag = "yt-live-chat-author-badge-renderer"
  3789. const dummy = document.createElement(tag);
  3790.  
  3791. const cProto = getProto(dummy);
  3792. if (!cProto || !cProto.attached) {
  3793. console.warn(`proto.attached for ${tag} is unavailable.`);
  3794. return;
  3795. }
  3796.  
  3797.  
  3798. if (typeof cProto.dataChanged === 'function' && !cProto.dataChanged86 && fnIntegrity(cProto.dataChanged) === '1.159.97') {
  3799.  
  3800.  
  3801.  
  3802. cProto.dataChanged86 = cProto.dataChanged;
  3803. cProto.dataChanged = function(a){
  3804.  
  3805. /*
  3806.  
  3807. for (var b = xC(Z(this.hostElement).querySelector("#image")); b.firstChild; )
  3808. b.removeChild(b.firstChild);
  3809. if (a)
  3810. if (a.icon) {
  3811. var c = document.createElement("yt-icon");
  3812. "MODERATOR" === a.icon.iconType && this.enableNewModeratorBadge ? (c.icon = "yt-sys-icons:shield-filled",
  3813. c.defaultToFilled = !0) : c.icon = "live-chat-badges:" + a.icon.iconType.toLowerCase();
  3814. b.appendChild(c)
  3815. } else if (a.customThumbnail) {
  3816. c = document.createElement("img");
  3817. var d;
  3818. (d = (d = KC(a.customThumbnail.thumbnails, 16)) ? lc(oc(d)) : null) ? (c.src = d,
  3819. b.appendChild(c),
  3820. c.setAttribute("alt", this.hostElement.ariaLabel || "")) : lq(new tm("Could not compute URL for thumbnail",a.customThumbnail))
  3821. }
  3822.  
  3823. */
  3824.  
  3825. const image = ((this||0).$||0).image
  3826. if (image && a && image.firstElementChild) {
  3827. let exisiting = image.firstElementChild;
  3828. if (exisiting === image.lastElementChild) {
  3829.  
  3830.  
  3831. if (a.icon && exisiting.nodeName.toUpperCase() === 'YT-ICON') {
  3832.  
  3833. var c = exisiting;
  3834. if ("MODERATOR" === a.icon.iconType && this.enableNewModeratorBadge) {
  3835. if (c.icon !== "yt-sys-icons:shield-filled") c.icon = "yt-sys-icons:shield-filled";
  3836. if (c.defaultToFilled !== true) c.defaultToFilled = true;
  3837. } else {
  3838. let p = "live-chat-badges:" + a.icon.iconType.toLowerCase();;
  3839. if (c.icon !== p) c.icon = p;
  3840. if (c.defaultToFilled !== false) c.defaultToFilled = false;
  3841. }
  3842. return;
  3843. } else if (a.customThumbnail && exisiting.nodeName.toUpperCase() == 'IMG') {
  3844.  
  3845. var c = exisiting;
  3846. if(a.customThumbnail.thumbnails.map(e=>e.url).includes(c.src)){
  3847.  
  3848. c.setAttribute("alt", this.hostElement.ariaLabel || "");
  3849. return;
  3850. }
  3851. /*
  3852. var d;
  3853. (d = (d = KC(a.customThumbnail.thumbnails, 16)) ? lc(oc(d)) : null) ? (c.src = d,
  3854.  
  3855. c.setAttribute("alt", this.hostElement.ariaLabel || "")) : lq(new tm("Could not compute URL for thumbnail", a.customThumbnail))
  3856. */
  3857. }
  3858.  
  3859. }
  3860. }
  3861. return this.dataChanged86.apply(this,arguments)
  3862.  
  3863. }
  3864. console.log("cProto.dataChanged - OK");
  3865.  
  3866. }else{
  3867. assertor(() => fnIntegrity(cProto.dataChanged, '1.159.97'));
  3868. console.log("cProto.dataChanged - NG");
  3869.  
  3870. }
  3871.  
  3872. })();
  3873.  
  3874. console.log("[End]");
  3875.  
  3876. console.groupEnd();
  3877.  
  3878. });
  3879.  
  3880. }
  3881.  
  3882.  
  3883. }
  3884.  
  3885. promiseForCustomYtElementsReady.then(onRegistryReadyForDOMOperations);
  3886.  
  3887.  
  3888. })();
  3889.  
  3890.  
  3891. });
  3892.  
  3893. })({ IntersectionObserver });

QingJ © 2025

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