YouTube 超快聊天

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

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

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

QingJ © 2025

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