YouTube: Floating Chat Window on Fullscreen

To make floating chat window on fullscreen

当前为 2024-03-04 提交的版本,查看 最新版本

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