YouTube: Floating Chat Window on Fullscreen

To make floating chat window on fullscreen

当前为 2023-12-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube: Floating Chat Window on Fullscreen
  3. // @namespace UserScript
  4. // @match https://www.youtube.com/*
  5. // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
  6. // @version 0.3.9
  7. // @license MIT License
  8. // @author CY Fung
  9. // @description To make floating chat window on fullscreen
  10. // @require https://update.greasyfork.org/scripts/465819/1289214/API%20for%20CustomElements%20in%20YouTube.js
  11. // @run-at document-start
  12. // @grant none
  13. // @unwrap
  14. // @allFrames true
  15. // @inject-into page
  16. // ==/UserScript==
  17.  
  18.  
  19. ((__CONTEXT__) => {
  20.  
  21.  
  22. let activeStyle = false;
  23.  
  24. let _lastStyleText = null;
  25. let tvc= 0;
  26.  
  27.  
  28.  
  29. let c27 = 0;
  30. let mouseDownActiveElement = null;
  31. document.addEventListener('click', function (evt) {
  32.  
  33. if(!document.fullscreenElement) return;
  34.  
  35. let byPass = false;
  36.  
  37. if (Date.now() - c27 < 40) byPass = true;
  38. else {
  39. return;
  40. }
  41.  
  42.  
  43. if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
  44. else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;
  45.  
  46. if (byPass) {
  47.  
  48. evt.stopPropagation();
  49. evt.stopImmediatePropagation();
  50. c27 = Date.now();
  51. }
  52. c27 = 0;
  53.  
  54. }, { capture: true, passive: false });
  55. document.addEventListener('mousedown', function (evt) {
  56.  
  57. if(!document.fullscreenElement) return;
  58. let byPass = false;
  59. const activeElement = document.activeElement || 0;
  60. mouseDownActiveElement = null;
  61. if (activeElement.nodeName === 'IFRAME') {
  62. if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) {
  63. byPass = true;
  64. mouseDownActiveElement = activeElement;
  65. }
  66. }
  67.  
  68. if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
  69. else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;
  70.  
  71.  
  72. if (byPass) {
  73.  
  74. evt.stopPropagation();
  75. evt.stopImmediatePropagation();
  76. c27 = Date.now();
  77. } else {
  78. mouseDownActiveElement = null;
  79. }
  80. c27 = 0;
  81.  
  82.  
  83.  
  84. }, { capture: true, passive: false });
  85. document.addEventListener('mouseup', function (evt) {
  86.  
  87. if(!document.fullscreenElement) return;
  88. let mde = mouseDownActiveElement;
  89. mouseDownActiveElement = null;
  90.  
  91. if (!mde) return;
  92.  
  93. let byPass = false;
  94. const activeElement = mde || 0;
  95. if (activeElement.nodeName === 'IFRAME') {
  96. if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) {
  97. byPass = true;
  98. }
  99. }
  100.  
  101. // if(Date.now()-c27 < 40 ) byPass = true;
  102. c27 = 0;
  103.  
  104. if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
  105. else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;
  106.  
  107. if (byPass) {
  108.  
  109. evt.stopPropagation();
  110. evt.stopImmediatePropagation();
  111. c27 = Date.now();
  112. }
  113.  
  114.  
  115. }, { capture: true, passive: false });
  116.  
  117.  
  118. const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : (this instanceof Window ? this : window);
  119.  
  120. const hkey_script = 'vdnvorrwsksy';
  121. if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  122. win[hkey_script] = true;
  123.  
  124. /** @type {globalThis.PromiseConstructor} */
  125. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  126.  
  127. const svgDefs = ()=>`
  128. <svg version="1.1" xmlns="//www.w3.org/2000/svg" xmlns:xlink="//www.w3.org/1999/xlink" style="display:none;">
  129. <defs>
  130. <filter id="stroke-text-svg-filter-03">
  131.  
  132. <feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" result="white-text"/>
  133. <feMorphology in="white-text" result="DILATED" operator="dilate" radius="2"></feMorphology>
  134. <feFlood flood-color="transparent" flood-opacity="1" result="PINK" id="floodColor-03"></feFlood>
  135. <feComposite in="PINK" in2="DILATED" operator="in" result="OUTLINE"></feComposite>
  136. <feMerge>
  137. <feMergeNode in="OUTLINE" />
  138. <feMergeNode in="SourceGraphic" />
  139. </feMerge>
  140. </filter>
  141. <filter id="stroke-text-svg-filter-04">
  142. <feMorphology operator="dilate" radius="2"></feMorphology>
  143. <feComposite operator="xor" in="SourceGraphic"/>
  144. </filter>
  145. </defs>
  146.  
  147. </svg>
  148. `;
  149.  
  150.  
  151.  
  152.  
  153. const createStyleTextForTopWin = () => `
  154.  
  155.  
  156. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) {
  157. position:fixed !important;
  158. top: var(--f3-top, 5px) !important;
  159. left: var(--f3-left, calc(60vw + 100px)) !important;
  160. height: var(--f3-h, 60vh) !important;
  161. width: var(--f3-w, 320px) !important;
  162. display:flex !important;
  163. flex-direction: column !important;
  164. padding: 4px;
  165. cursor: all-scroll;
  166. z-index:9999;
  167. box-sizing: border-box !important;
  168. margin:0 !important;
  169. opacity: var(--floating-window-opacity, 1.0) !important;
  170. background: transparent;
  171. background-color: rgba(0, 0, 0, 0.5);
  172. transition: background-color 300ms;
  173. }
  174.  
  175. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover {
  176. background-color: rgba(0, 0, 0, 0.85);
  177.  
  178. }
  179.  
  180. .no-floating[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) {
  181.  
  182. top: -300vh !important;
  183. left: -300vh !important;
  184. }
  185.  
  186.  
  187. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class]{
  188. flex-grow: 0;
  189. flex-shrink:0;
  190. position:static;
  191. cursor: all-scroll;
  192. }
  193. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class] *[class]{
  194. cursor: inherit;
  195. }
  196. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe[class]{
  197. flex-grow: 100;
  198. flex-shrink:0;
  199. height: 0;
  200. position:static;
  201. }
  202.  
  203.  
  204. html{
  205. --fc7-handle-color: #0cb8da;
  206. }
  207. html[dark]{
  208. --fc7-handle-color: #0c74e4;
  209. }
  210.  
  211. :fullscreen .resize-handle {
  212.  
  213. position: absolute !important;
  214. top: 0;
  215. left: 0;
  216. bottom: 0;
  217. background: transparent;
  218. right: 0;
  219. z-index: 999 !important;
  220. border-radius: inherit !important;
  221. box-sizing: border-box !important;
  222. pointer-events:none !important;
  223. visibility: collapse;
  224. border: 4px solid transparent;
  225. border-color: transparent;
  226. transition: border-color 300ms;
  227. }
  228.  
  229. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover .resize-handle {
  230.  
  231. visibility: visible;
  232.  
  233. border-color: var(--fc7-handle-color);
  234. }
  235.  
  236. [moving] {
  237. cursor: all-scroll;
  238. --pointer-events:initial;
  239. }
  240.  
  241. [moving] body {
  242. --pointer-events:none;
  243. }
  244.  
  245. [moving] ytd-live-chat-frame#chat{
  246.  
  247. --pointer-events:initial;
  248. }
  249.  
  250.  
  251. [moving] ytd-live-chat-frame#chat iframe {
  252.  
  253. --pointer-events:none;
  254. }
  255.  
  256.  
  257. [moving="move"] ytd-live-chat-frame#chat {
  258. background-color: var(--yt-spec-general-background-a);
  259.  
  260. }
  261.  
  262.  
  263. [moving="move"] ytd-live-chat-frame#chat iframe {
  264.  
  265. visibility: collapse;
  266. }
  267.  
  268. [moving] * {
  269. pointer-events:var(--pointer-events) !important;
  270.  
  271. }
  272. [moving] *, [moving] [class] {
  273. user-select: none !important;
  274. }
  275.  
  276. :fullscreen tyt-iframe-popup-btn{
  277. display: none !important;
  278. }
  279.  
  280. [moving] tyt-iframe-popup-btn{
  281. display: none !important;
  282. }
  283.  
  284. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame {
  285.  
  286. background: transparent;
  287.  
  288. }
  289.  
  290.  
  291.  
  292. `;
  293.  
  294. const createStyleTextForIframe = () => `
  295.  
  296.  
  297. .youtube-floating-chat-iframe yt-live-chat-docked-message#docked-messages.style-scope.yt-live-chat-item-list-renderer {
  298. margin-top:var(--fc7-top-banner-mt);
  299. transition: margin-top 180ms;
  300. }
  301.  
  302. .youtube-floating-chat-iframe yt-live-chat-banner-manager#live-chat-banner.style-scope.yt-live-chat-item-list-renderer {
  303. margin-top:var(--fc7-top-banner-mt);
  304. transition: margin-top 180ms;
  305. }
  306.  
  307. .youtube-floating-chat-iframe #action-panel.style-scope.yt-live-chat-renderer {
  308. position: fixed;
  309. top: 50%;
  310. transform: translateY(-50%);
  311. }
  312.  
  313. .youtube-floating-chat-iframe yt-live-chat-header-renderer.style-scope.yt-live-chat-renderer {
  314. position: relative;
  315. z-index: 8;
  316. background: rgb(0,0,0);
  317. visibility: var(--fc7-panel-visibility);
  318. }
  319.  
  320.  
  321.  
  322. .youtube-floating-chat-iframe #chat-messages.style-scope.yt-live-chat-renderer.iron-selected > #contents.style-scope.yt-live-chat-renderer{
  323. position: fixed;
  324. z-index: 4;
  325. top: 0;
  326. bottom: 0;
  327. left: 0;
  328. right: 0;
  329. }
  330.  
  331. .youtube-floating-chat-iframe #right-arrow-container.yt-live-chat-ticker-renderer,
  332. .youtube-floating-chat-iframe #left-arrow-container.yt-live-chat-ticker-renderer
  333.  
  334. {
  335. background: transparent;
  336. }
  337.  
  338. .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app {
  339.  
  340. --yt-live-chat-background-color: transparent;
  341. --yt-live-chat-action-panel-background-color: rgba(0, 0, 0, 0.08);
  342. --yt-live-chat-header-background-color: rgba(0, 0, 0, 0.18);
  343. --yt-spec-static-overlay-background-medium: rgba(0, 0, 0, 0.08);
  344. --yt-live-chat-banner-gradient-scrim: transparent;
  345.  
  346. }
  347.  
  348. .youtube-floating-chat-iframe{
  349. --fc7-top-banner-mt: 0px;
  350. --fc7-banner-opacity: 0.86;
  351. --fc7-system-message-opacity: 0.66;
  352. --fc7-system-message-opacity2: 0.66;
  353. --fc7-panel-display: none;
  354. --fc7-panel-visibility: collapse;
  355. --fc7-panel-position: absolute;
  356. }
  357. .youtube-floating-chat-iframe:focus-within,
  358. html:focus-within,
  359. body:focus-within,
  360. yt-live-chat-app:focus-within,
  361. yt-live-chat-renderer.yt-live-chat-app:focus-within
  362. {
  363. --fc7-top-banner-mt: 56px;
  364. --fc7-banner-opacity: 1.0;
  365. --fc7-system-message-opacity: 1.0;
  366. --fc7-system-message-opacity2: 1.00;
  367. --fc7-panel-display: invalid;
  368. --fc7-panel-visibility: invalid;
  369. --fc7-panel-position: absolute;
  370. }
  371.  
  372. .youtube-floating-chat-iframe yt-live-chat-app:hover {
  373. --fc7-top-banner-mt: 56px;
  374. --fc7-banner-opacity: 1.0;
  375. --fc7-system-message-opacity: 1.0;
  376. --fc7-system-message-opacity2: 1.00;
  377. --fc7-panel-display: invalid;
  378. --fc7-panel-visibility: invalid;
  379. --fc7-panel-position: absolute;
  380. }
  381.  
  382.  
  383. .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app #visible-banners > yt-live-chat-banner-renderer {
  384. opacity: var(--fc7-banner-opacity) !important;
  385. }
  386.  
  387.  
  388. .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app yt-live-chat-viewer-engagement-message-renderer {
  389. opacity: var(--fc7-system-message-opacity) !important;
  390. }
  391.  
  392.  
  393. .youtube-floating-chat-iframe yt-live-chat-app yt-live-chat-renderer.yt-live-chat-app yt-live-chat-message-input-renderer {
  394. visibility: var(--fc7-panel-visibility);
  395. position: var(--fc7-panel-position);
  396.  
  397. transform: translateY(-100%);
  398. left: 0;
  399. right: 0;
  400. opacity: 1;
  401. background: rgba(0,0,0,0.86);
  402. }
  403.  
  404.  
  405. /* hide message with input panel hidden */
  406. .youtube-floating-chat-iframe yt-live-chat-app > tp-yt-iron-dropdown.yt-live-chat-app yt-tooltip-renderer[slot="dropdown-content"][position-type="OPEN_POPUP_POSITION_TOP"].yt-live-chat-app {
  407. visibility: var(--fc7-panel-visibility);
  408. }
  409.  
  410.  
  411.  
  412.  
  413. [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track,
  414. [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track {
  415. background-color: var(--ytd-searchbox-legacy-button-color);
  416. }
  417.  
  418. .youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track,
  419. .youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track {
  420. background-color: #fcfcfc;
  421. }
  422.  
  423.  
  424. [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-thumb,
  425. [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-thumb{
  426.  
  427. background-color: var(--ytd-searchbox-legacy-button-color);
  428. border: 2px solid var(--ytd-searchbox-legacy-button-color);
  429.  
  430. }
  431.  
  432.  
  433.  
  434. .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer {
  435. --yt-live-chat-action-panel-gradient-scrim: transparent;
  436. }
  437.  
  438.  
  439. .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer yt-live-chat-action-panel-renderer {
  440. opacity: var(--fc7-system-message-opacity2) !important;
  441. }
  442.  
  443. `;
  444.  
  445. const { isIframe, isTopFrame } = (() => {
  446.  
  447. let isIframe = false, isTopFrame = false;
  448. try {
  449. isIframe = window.document !== top.document
  450. } catch (e) { }
  451.  
  452. try {
  453. isTopFrame = window.document === top.document
  454. } catch (e) { }
  455.  
  456. return { isIframe, isTopFrame };
  457.  
  458. })();
  459.  
  460. if (isIframe ^ isTopFrame) { } else return;
  461.  
  462. if (isTopFrame) {
  463.  
  464.  
  465.  
  466.  
  467. const addCSS = (createStyleText) => {
  468. let text = createStyleText();
  469. let style = document.createElement('style');
  470. style.id = 'rvZ0t';
  471. style.textContent = text;
  472. document.head.appendChild(style);
  473. }
  474.  
  475.  
  476.  
  477. const cleanContext = async (win) => {
  478. const waitFn = requestAnimationFrame; // shall have been binded to window
  479. try {
  480. let mx = 16; // MAX TRIAL
  481. const frameId = 'vanillajs-iframe-v1'
  482. let frame = document.getElementById(frameId);
  483. let removeIframeFn = null;
  484. if (!frame) {
  485. frame = document.createElement('iframe');
  486. frame.id = 'vanillajs-iframe-v1';
  487. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  488. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  489. n.appendChild(frame);
  490. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  491. const root = document.documentElement;
  492. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  493. removeIframeFn = (setTimeout) => {
  494. const removeIframeOnDocumentReady = (e) => {
  495. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  496. win = null;
  497. setTimeout(() => {
  498. n.remove();
  499. n = null;
  500. }, 200);
  501. }
  502. if (document.readyState !== 'loading') {
  503. removeIframeOnDocumentReady();
  504. } else {
  505. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  506. }
  507. }
  508. }
  509. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  510. const fc = frame.contentWindow;
  511. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  512. const { requestAnimationFrame, setTimeout } = fc;
  513. const res = { requestAnimationFrame, setTimeout };
  514. for (let k in res) res[k] = res[k].bind(win); // necessary
  515. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  516. return res;
  517. } catch (e) {
  518. console.warn(e);
  519. return null;
  520. }
  521. };
  522.  
  523. cleanContext(win).then(__CONTEXT__ => {
  524. if (!__CONTEXT__) return null;
  525.  
  526. const { requestAnimationFrame } = __CONTEXT__;
  527.  
  528. let chatWindowWR = null;
  529. let showHideButtonWR = null;
  530.  
  531. /* globals WeakRef:false */
  532.  
  533. /** @type {(o: Object | null) => WeakRef | null} */
  534. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  535.  
  536. /** @type {(wr: Object | null) => Object | null} */
  537. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  538.  
  539. let startX;
  540. let startY;
  541. let startWidth;
  542. let startHeight;
  543.  
  544.  
  545. let edge = 0;
  546.  
  547.  
  548. let initialLeft;
  549. let initialTop;
  550.  
  551. let stopResize;
  552. let stopMove;
  553.  
  554.  
  555. const getXY = (e) => {
  556. let rect = e.target.getBoundingClientRect();
  557. let x = e.clientX - rect.left; //x position within the element.
  558. let y = e.clientY - rect.top; //y position within the element.
  559. return { x, y }
  560. }
  561.  
  562. let beforeEvent = null;
  563.  
  564. function resizeWindow(e) {
  565.  
  566.  
  567. const chatWindow = kRef(chatWindowWR);
  568. if (chatWindow) {
  569.  
  570. const mEdge = edge;
  571.  
  572. if (mEdge == 4 || mEdge == 1) {
  573.  
  574. } else if (mEdge == 8 || mEdge == 16) {
  575. } else {
  576. return;
  577. }
  578.  
  579.  
  580. Promise.resolve(chatWindow).then(chatWindow => {
  581. let rect;
  582.  
  583. if (mEdge == 4 || mEdge == 1 || mEdge == 16) {
  584.  
  585. let newWidth = startWidth + (startX - e.pageX);
  586.  
  587. let newLeft = initialLeft + startWidth - newWidth;
  588. chatWindow.style.setProperty('--f3-w', newWidth + "px");
  589. chatWindow.style.setProperty('--f3-left', newLeft + "px");
  590.  
  591.  
  592.  
  593. let newHeight = startHeight + (startY - e.pageY);
  594.  
  595. let newTop = initialTop + startHeight - newHeight;
  596. chatWindow.style.setProperty('--f3-h', newHeight + "px");
  597. chatWindow.style.setProperty('--f3-top', newTop + "px");
  598.  
  599. rect = {
  600. x: newLeft,
  601. y: newTop,
  602. w: newWidth,
  603.  
  604. h: newHeight,
  605.  
  606.  
  607. };
  608.  
  609.  
  610.  
  611. } else if (mEdge == 8) {
  612.  
  613. let newWidth = startWidth + e.pageX - startX;
  614. let newHeight = startHeight + e.pageY - startY;
  615.  
  616. chatWindow.style.setProperty('--f3-w', newWidth + "px");
  617. chatWindow.style.setProperty('--f3-h', newHeight + "px");
  618.  
  619.  
  620. rect = {
  621. x: initialLeft,
  622. y: initialTop,
  623. w: newWidth,
  624.  
  625. h: newHeight,
  626.  
  627.  
  628. };
  629.  
  630. }
  631.  
  632.  
  633.  
  634. updateOpacity(chatWindow, rect, screen);
  635.  
  636. })
  637.  
  638.  
  639. e.stopImmediatePropagation();
  640. e.stopPropagation();
  641. e.preventDefault();
  642.  
  643.  
  644. }
  645.  
  646. }
  647.  
  648. let isMoved = false;
  649.  
  650. function moveWindow(e) {
  651.  
  652.  
  653.  
  654. const chatWindow = kRef(chatWindowWR);
  655. if (chatWindow) {
  656.  
  657. Promise.resolve(chatWindow).then(chatWindow => {
  658.  
  659.  
  660. let newX = initialLeft + e.pageX - startX;
  661. let newY = initialTop + e.pageY - startY;
  662.  
  663. if (Math.abs(e.pageX - startX) > 10 || Math.abs(e.pageY - startY) > 10) isMoved = true;
  664.  
  665. chatWindow.style.setProperty('--f3-left', newX + "px");
  666. chatWindow.style.setProperty('--f3-top', newY + "px");
  667.  
  668.  
  669.  
  670. updateOpacity(chatWindow, {
  671. x: newX,
  672. y: newY,
  673. w: startWidth,
  674.  
  675. h: startHeight,
  676.  
  677.  
  678. }, screen);
  679.  
  680. });
  681.  
  682.  
  683.  
  684. e.stopImmediatePropagation();
  685. e.stopPropagation();
  686. e.preventDefault();
  687.  
  688. }
  689. }
  690.  
  691.  
  692.  
  693.  
  694. function initializeResize(e) {
  695.  
  696. if (!document.fullscreenElement) return;
  697.  
  698. if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return;
  699.  
  700. if (e.target.id !== 'chat') return;
  701.  
  702.  
  703.  
  704. const { x, y } = getXY(e);
  705. edge = 0;
  706. if (x < 16 && y < 16) { edge = 16; }
  707. else if (x < 16) edge = 4;
  708. else if (y < 16) edge = 1;
  709. else edge = 8;
  710.  
  711. if (edge <= 0) return;
  712.  
  713. startX = e.pageX;
  714. startY = e.pageY;
  715.  
  716. const chatWindow = kRef(chatWindowWR);
  717. if (chatWindow) {
  718.  
  719. Promise.resolve(chatWindow).then(chatWindow => {
  720.  
  721. let rect = chatWindow.getBoundingClientRect();
  722. initialLeft = rect.x;
  723. initialTop = rect.y;
  724.  
  725.  
  726.  
  727. startWidth = rect.width;
  728. startHeight = rect.height;
  729.  
  730.  
  731. chatWindow.style.setProperty('--f3-left', initialLeft + "px");
  732. chatWindow.style.setProperty('--f3-top', initialTop + "px");
  733. chatWindow.style.setProperty('--f3-w', startWidth + "px");
  734. chatWindow.style.setProperty('--f3-h', startHeight + "px");
  735.  
  736. });
  737.  
  738. }
  739.  
  740.  
  741.  
  742.  
  743. document.documentElement.setAttribute('moving', 'resize');
  744.  
  745. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  746. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  747. document.documentElement.removeEventListener("mouseup", stopResize, false);
  748. document.documentElement.removeEventListener("mouseup", stopMove, false);
  749.  
  750. isMoved = false;
  751. document.documentElement.addEventListener("mousemove", resizeWindow);
  752. document.documentElement.addEventListener("mouseup", stopResize);
  753.  
  754. }
  755.  
  756.  
  757. let updateOpacityRid = 0;
  758.  
  759. function updateOpacity(chatWindow, rect, screen) {
  760.  
  761. let tid = ++updateOpacityRid;
  762.  
  763. requestAnimationFrame(() => {
  764.  
  765.  
  766. if (tid !== updateOpacityRid) return;
  767.  
  768. let { x, y, w, h } = rect;
  769. let [left, top, right, bottom] = [x, y, x + w, y + h];
  770.  
  771.  
  772. let opacityW = (Math.min(right, screen.width) - Math.max(0, left)) / w;
  773. let opacityH = (Math.min(bottom, screen.height) - Math.max(0, top)) / h;
  774.  
  775. let opacity = Math.min(opacityW, opacityH);
  776.  
  777. chatWindow.style.setProperty('--floating-window-opacity', Math.round(opacity * 100 * 5, 0) / 5 / 100);
  778.  
  779.  
  780. })
  781.  
  782.  
  783.  
  784.  
  785.  
  786. }
  787.  
  788. function initializeMove(e) {
  789.  
  790. if (!document.fullscreenElement) return;
  791. if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return;
  792.  
  793.  
  794.  
  795. const chatWindow = kRef(chatWindowWR);
  796.  
  797.  
  798.  
  799. startX = e.pageX;
  800. startY = e.pageY;
  801.  
  802.  
  803. if (chatWindow) {
  804.  
  805. Promise.resolve(chatWindow).then(chatWindow => {
  806.  
  807.  
  808. let rect = chatWindow.getBoundingClientRect();
  809. initialLeft = rect.x;
  810. initialTop = rect.y;
  811.  
  812.  
  813.  
  814. startWidth = rect.width;
  815. startHeight = rect.height;
  816.  
  817.  
  818. chatWindow.style.setProperty('--f3-left', initialLeft + "px");
  819. chatWindow.style.setProperty('--f3-top', initialTop + "px");
  820. chatWindow.style.setProperty('--f3-w', startWidth + "px");
  821. chatWindow.style.setProperty('--f3-h', startHeight + "px");
  822.  
  823. })
  824.  
  825.  
  826. }
  827.  
  828.  
  829.  
  830. document.documentElement.setAttribute('moving', 'move');
  831.  
  832. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  833. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  834. document.documentElement.removeEventListener("mouseup", stopResize, false);
  835. document.documentElement.removeEventListener("mouseup", stopMove, false);
  836. isMoved = false;
  837.  
  838. document.documentElement.addEventListener("mousemove", moveWindow, false);
  839. document.documentElement.addEventListener("mouseup", stopMove, false);
  840.  
  841. e.stopImmediatePropagation();
  842. e.stopPropagation();
  843. e.preventDefault();
  844.  
  845. beforeEvent = e;
  846.  
  847. }
  848.  
  849.  
  850. function checkClick(beforeEvent, currentEvent) {
  851.  
  852. const d = currentEvent.timeStamp - beforeEvent.timeStamp;
  853. if (d < 300 && d > 30 && !isMoved) {
  854.  
  855. document.documentElement.classList.add('no-floating');
  856.  
  857. }
  858.  
  859. }
  860.  
  861.  
  862. stopResize = (e) => {
  863.  
  864. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  865. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  866. document.documentElement.removeEventListener("mouseup", stopResize, false);
  867. document.documentElement.removeEventListener("mouseup", stopMove, false);
  868.  
  869. document.documentElement.removeAttribute('moving');
  870.  
  871. e.stopImmediatePropagation();
  872. e.stopPropagation();
  873.  
  874. }
  875.  
  876. stopMove = (e) => {
  877.  
  878. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  879. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  880. document.documentElement.removeEventListener("mouseup", stopResize, false);
  881. document.documentElement.removeEventListener("mouseup", stopMove, false);
  882.  
  883. document.documentElement.removeAttribute('moving');
  884. beforeEvent && checkClick(beforeEvent, e);
  885. beforeEvent = null;
  886.  
  887. e.stopImmediatePropagation();
  888. e.stopPropagation();
  889.  
  890. }
  891.  
  892.  
  893. function reset() {
  894.  
  895. document.documentElement.removeAttribute('moving');
  896. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  897. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  898. document.documentElement.removeEventListener("mouseup", stopResize, false);
  899. document.documentElement.removeEventListener("mouseup", stopMove, false);
  900.  
  901.  
  902. startX = 0;
  903. startY = 0;
  904. startWidth = 0;
  905. startHeight = 0;
  906.  
  907.  
  908. edge = 0;
  909.  
  910.  
  911. initialLeft = 0;
  912. initialTop = 0;
  913.  
  914. beforeEvent = null;
  915.  
  916.  
  917. }
  918.  
  919. function iframeLoaded() {
  920.  
  921. }
  922.  
  923. function iframeFullscreenChanged() {
  924. const iframeDoc = this;
  925.  
  926.  
  927. _lastStyleText = null;
  928.  
  929. if (!document.fullscreenElement) {
  930. activeStyle = false;
  931. iframeDoc.documentElement.classList.remove('youtube-floating-chat-iframe');
  932. } else {
  933. activeStyle = true;
  934. iframeDoc.documentElement.classList.add('youtube-floating-chat-iframe');
  935.  
  936. }
  937.  
  938.  
  939. }
  940.  
  941. let iframeFullscreenChangedBinded = null;
  942.  
  943.  
  944.  
  945. function onMessage(evt) {
  946. if (evt.data === hkey_script) {
  947.  
  948. const iframeWin = evt.source;
  949. if (!iframeWin) return;
  950. const iframeDoc = iframeWin.document;
  951.  
  952.  
  953. function onReady() {
  954.  
  955. iframeDoc.head.appendChild(document.createElement('style')).textContent = createStyleTextForIframe();
  956.  
  957.  
  958.  
  959.  
  960.  
  961.  
  962. const tm = document.createElement('template');
  963. tm.innerHTML=svgDefs();
  964. iframeDoc.body.appendChild(tm.content)
  965.  
  966. if (iframeFullscreenChangedBinded) document.removeEventListener('fullscreenchange', iframeFullscreenChangedBinded, false);
  967. iframeFullscreenChangedBinded = iframeFullscreenChanged.bind(iframeDoc);
  968. document.addEventListener('fullscreenchange', iframeFullscreenChangedBinded, false);
  969.  
  970. iframeFullscreenChangedBinded();
  971.  
  972.  
  973.  
  974.  
  975.  
  976. setInterval(()=>{
  977.  
  978. if(!activeStyle) return;
  979.  
  980.  
  981. let xpathExpression = "//style[text()[contains(., 'userscript-control[floating-chat-iframe]')]]";
  982.  
  983. // Evaluating the XPath expression and getting string value directly
  984. let result = iframeDoc.evaluate(xpathExpression, iframeDoc, null, XPathResult.STRING_TYPE, null);
  985.  
  986. let newText =result && result.stringValue ? result.stringValue: null;
  987.  
  988. if(newText !== _lastStyleText){
  989. _lastStyleText = newText;
  990. // console.log(123)
  991.  
  992. let tid = ++tvc;
  993.  
  994. requestAnimationFrame(()=>{
  995.  
  996. if(tid!==tvc) return;
  997.  
  998.  
  999.  
  1000. let style = iframeWin.getComputedStyle(iframeDoc.documentElement);
  1001.  
  1002. let fc = style.getPropertyValue('--floodcolor');
  1003. if(fc){
  1004.  
  1005. console.log(fc)
  1006.  
  1007.  
  1008. let floodColor03= iframeDoc.querySelector('#floodColor-03');
  1009. floodColor03 && floodColor03.setAttribute('flood-color',fc );
  1010.  
  1011. let floodColor04= iframeDoc.querySelector('#floodColor-04');
  1012. floodColor04&&floodColor04.setAttribute('flood-color',fc );
  1013.  
  1014. iframeDoc.documentElement.setAttribute('hpkns', '')
  1015. }else{
  1016. iframeDoc.documentElement.removeAttribute('hpkns')
  1017.  
  1018. }
  1019.  
  1020.  
  1021.  
  1022.  
  1023. });
  1024.  
  1025.  
  1026.  
  1027. }
  1028.  
  1029.  
  1030.  
  1031. },100);
  1032.  
  1033.  
  1034. }
  1035.  
  1036. Promise.resolve().then(() => {
  1037.  
  1038. if (iframeDoc.readyState !== 'loading') {
  1039. onReady();
  1040. } else {
  1041. iframeWin.addEventListener("DOMContentLoaded", onReady, false);
  1042. }
  1043.  
  1044. });
  1045.  
  1046.  
  1047. }
  1048.  
  1049. }
  1050.  
  1051.  
  1052. function setChat(chat) {
  1053.  
  1054. let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
  1055. if (resizeHandle) return;
  1056.  
  1057.  
  1058. const chatDollar = (chat.inst || chat ).$ || chat.$ || 0;
  1059.  
  1060. let cw = (() => {
  1061. try {
  1062. const { head, body } = chatDollar.chatframe.contentWindow.document;
  1063. return { head, body }
  1064.  
  1065. } catch (e) { return null; }
  1066. })();
  1067.  
  1068. if (!cw) return;
  1069.  
  1070. window.removeEventListener('message', onMessage, false);
  1071. window.addEventListener('message', onMessage, false);
  1072.  
  1073.  
  1074.  
  1075. let script = document.getElementById('rvZ0t') || (document.evaluate("//div[contains(text(), 'userscript-control[enable-customized-floating-window]')]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null) || 0).singleNodeValue;
  1076. if (!script) addCSS(createStyleTextForTopWin);
  1077. /*
  1078. const tm = document.createElement('template');
  1079. tm.innerHTML=svgDefs();
  1080. document.body.appendChild(tm.content)
  1081. */
  1082.  
  1083. if (!document.documentElement.hasAttribute('floating-chat-window')) document.documentElement.setAttribute('floating-chat-window', '');
  1084.  
  1085.  
  1086. chat.setAttribute('allowtransparency', 'true');
  1087.  
  1088.  
  1089.  
  1090. resizeHandle = document.createElement("div");
  1091. resizeHandle.className = "resize-handle";
  1092. chat.appendChild(resizeHandle);
  1093. resizeHandle = null;
  1094.  
  1095. let chatWindow;
  1096. let showHideButton;
  1097.  
  1098. chatWindow = kRef(chatWindowWR);
  1099. showHideButton = kRef(showHideButtonWR);
  1100.  
  1101.  
  1102.  
  1103. if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
  1104. if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
  1105.  
  1106.  
  1107. chatWindow = chat;
  1108. showHideButton = HTMLElement.prototype.querySelector.call(chat, '#show-hide-button');
  1109. chatWindowWR = mWeakRef(chat)
  1110. showHideButtonWR = mWeakRef(showHideButton);
  1111.  
  1112.  
  1113.  
  1114. chatWindow.addEventListener("mousedown", initializeResize, false);
  1115. showHideButton.addEventListener("mousedown", initializeMove, false);
  1116.  
  1117. reset();
  1118.  
  1119. }
  1120.  
  1121.  
  1122. const fullscreenchangePageFn = () => {
  1123. if (!document.fullscreenElement) {
  1124. document.documentElement.classList.remove('no-floating')
  1125. }
  1126. };
  1127.  
  1128. function noChat(chat) {
  1129.  
  1130. let chatWindow;
  1131. let showHideButton;
  1132.  
  1133. chatWindow = kRef(chatWindowWR);
  1134. showHideButton = kRef(showHideButtonWR);
  1135.  
  1136.  
  1137.  
  1138. if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
  1139. if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
  1140.  
  1141.  
  1142. let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
  1143. if (resizeHandle) {
  1144. resizeHandle.remove();
  1145. }
  1146.  
  1147. chat.removeEventListener("mousedown", initializeResize, false);
  1148.  
  1149.  
  1150. showHideButton = HTMLElement.prototype.querySelector.call(chat, '#show-hide-button');
  1151.  
  1152. if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
  1153.  
  1154.  
  1155. reset();
  1156. }
  1157.  
  1158.  
  1159. document.removeEventListener('fullscreenchange', fullscreenchangePageFn, false);
  1160. document.addEventListener('fullscreenchange', fullscreenchangePageFn, false);
  1161. fullscreenchangePageFn();
  1162.  
  1163. customYtElements.whenRegistered('ytd-live-chat-frame', (proto) => {
  1164.  
  1165.  
  1166. proto.attached = ((attached) => (function () { Promise.resolve(this).then(setChat); return attached.apply(this, arguments) }))(proto.attached);
  1167.  
  1168. proto.detached = ((detached) => (function () { Promise.resolve(this).then(noChat); return detached.apply(this, arguments) }))(proto.detached);
  1169.  
  1170. let chat = document.querySelector('ytd-live-chat-frame');
  1171. if (chat) Promise.resolve(chat).then(setChat);
  1172.  
  1173. })
  1174.  
  1175.  
  1176. });
  1177.  
  1178.  
  1179. } else if (isIframe && top === parent) {
  1180.  
  1181.  
  1182.  
  1183. top.postMessage(hkey_script, `${location.protocol}//${location.hostname}`);
  1184.  
  1185.  
  1186.  
  1187.  
  1188. }
  1189.  
  1190.  
  1191.  
  1192.  
  1193.  
  1194. })({ requestAnimationFrame });