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