YouTube Enhancer (Loop & Screenshot Button)

Integrating loop and screenshot buttons into the video and shorts player to enhance user functionality.

目前為 2024-12-14 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name YouTube Enhancer (Loop & Screenshot Button)
  3. // @description Integrating loop and screenshot buttons into the video and shorts player to enhance user functionality.
  4. // @icon https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png
  5. // @version 1.4
  6. // @author exyezed
  7. // @namespace https://github.com/exyezed/youtube-enhancer/
  8. // @supportURL https://github.com/exyezed/youtube-enhancer/issues
  9. // @license MIT
  10. // @match https://www.youtube.com/*
  11. // @grant GM_xmlhttpRequest
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. const YouTubeEnhancerLoopScreenshotConfig = {
  18. screenshotFormat: "png",
  19. extension: 'png',
  20. screenshotFunctionality: 2,
  21. clickDuration: 200
  22. };
  23.  
  24. const YouTubeEnhancerLoopScreenshotCSS = `
  25. a.YouTubeEnhancerLoopScreenshot-loop-button,
  26. a.YouTubeEnhancerLoopScreenshot-screenshot-button {
  27. text-align: center;
  28. position: relative;
  29. display: flex;
  30. align-items: center;
  31. justify-content: center;
  32. width: 48px;
  33. height: 48px;
  34. }
  35.  
  36. a.YouTubeEnhancerLoopScreenshot-loop-button svg,
  37. a.YouTubeEnhancerLoopScreenshot-screenshot-button svg {
  38. width: 24px;
  39. height: 24px;
  40. vertical-align: middle;
  41. }
  42.  
  43. a.YouTubeEnhancerLoopScreenshot-loop-button.active svg {
  44. fill: url(#buttonGradient);
  45. }
  46.  
  47. .YouTubeEnhancerLoopScreenshot-screenshot-button {
  48. position: relative;
  49. display: flex;
  50. align-items: center;
  51. justify-content: center;
  52. width: 48px;
  53. height: 48px;
  54. }
  55.  
  56. .YouTubeEnhancerLoopScreenshot-screenshot-button .icon-container {
  57. position: absolute;
  58. top: 50%;
  59. left: 50%;
  60. transform: translate(-50%, -50%);
  61. width: 24px;
  62. height: 24px;
  63. }
  64.  
  65. .YouTubeEnhancerLoopScreenshot-screenshot-button .default-icon,
  66. .YouTubeEnhancerLoopScreenshot-screenshot-button .hover-icon {
  67. position: absolute;
  68. top: 0;
  69. left: 0;
  70. width: 100%;
  71. height: 100%;
  72. transition: opacity 0.2s ease;
  73. }
  74.  
  75. .YouTubeEnhancerLoopScreenshot-screenshot-button .default-icon {
  76. opacity: 1;
  77. }
  78.  
  79. .YouTubeEnhancerLoopScreenshot-screenshot-button .hover-icon {
  80. opacity: 0;
  81. }
  82.  
  83. .YouTubeEnhancerLoopScreenshot-screenshot-button:hover .default-icon {
  84. opacity: 0;
  85. }
  86.  
  87. .YouTubeEnhancerLoopScreenshot-screenshot-button:hover .hover-icon {
  88. opacity: 1;
  89. }
  90.  
  91. .YouTubeEnhancerLoopScreenshot-screenshot-button.clicked .hover-icon {
  92. fill: url(#buttonGradient);
  93. }
  94.  
  95. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button {
  96. display: flex;
  97. align-items: center;
  98. justify-content: center;
  99. margin-top: 16px;
  100. width: 48px;
  101. height: 48px;
  102. border-radius: 50%;
  103. cursor: pointer;
  104. transition: background-color 0.3s;
  105. }
  106.  
  107. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg {
  108. width: 24px;
  109. height: 24px;
  110. transition: fill 0.1s ease;
  111. }
  112.  
  113. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg path {
  114. transition: fill 0.1s ease;
  115. }
  116.  
  117. .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button.clicked svg path {
  118. fill: url(#shortsButtonGradient) !important;
  119. }
  120.  
  121. html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button {
  122. background-color: rgba(255, 255, 255, 0.1);
  123. }
  124.  
  125. html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button:hover {
  126. background-color: rgba(255, 255, 255, 0.2);
  127. }
  128.  
  129. html[dark] .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg path {
  130. fill: white;
  131. }
  132.  
  133. html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button {
  134. background-color: rgba(0, 0, 0, 0.05);
  135. }
  136.  
  137. html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button:hover {
  138. background-color: rgba(0, 0, 0, 0.1);
  139. }
  140.  
  141. html:not([dark]) .YouTubeEnhancerLoopScreenshot-shorts-screenshot-button svg path {
  142. fill: #030303;
  143. }
  144. `;
  145. const YouTubeEnhancerLoopScreenshotUtils = {
  146. addStyle(styleString) {
  147. const style = document.createElement('style');
  148. style.textContent = styleString;
  149. document.head.append(style);
  150. },
  151.  
  152. getYouTubeVideoLoopSVG() {
  153. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  154. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  155. svg.setAttribute('height', '24px');
  156. svg.setAttribute('viewBox', '0 -960 960 960');
  157. svg.setAttribute('width', '24px');
  158. svg.setAttribute('fill', '#e8eaed');
  159. const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  160. const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
  161. gradient.setAttribute('id', 'buttonGradient');
  162. gradient.setAttribute('x1', '0%');
  163. gradient.setAttribute('y1', '0%');
  164. gradient.setAttribute('x2', '100%');
  165. gradient.setAttribute('y2', '100%');
  166. const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  167. stop1.setAttribute('offset', '0%');
  168. stop1.setAttribute('style', 'stop-color:#f03');
  169. const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  170. stop2.setAttribute('offset', '100%');
  171. stop2.setAttribute('style', 'stop-color:#ff2791');
  172. gradient.appendChild(stop1);
  173. gradient.appendChild(stop2);
  174. defs.appendChild(gradient);
  175. svg.appendChild(defs);
  176. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  177. path.setAttribute('d', 'M220-260q-92 0-156-64T0-480q0-92 64-156t156-64q37 0 71 13t61 37l68 62-60 54-62-56q-16-14-36-22t-42-8q-58 0-99 41t-41 99q0 58 41 99t99 41q22 0 42-8t36-22l310-280q27-24 61-37t71-13q92 0 156 64t64 156q0 92-64 156t-156 64q-37 0-71-13t-61-37l-68-62 60-54 62 56q16 14 36 22t42 8q58 0 99-41t41-99q0-58-41-99t-99-41q-22 0-42 8t-36 22L352-310q-27 24-61 37t-71 13Z');
  178. svg.appendChild(path);
  179. return svg;
  180. },
  181.  
  182. getYouTubeVideoScreenshotSVG() {
  183. const container = document.createElement('div');
  184. container.className = 'icon-container';
  185. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  186. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  187. svg.setAttribute('height', '24px');
  188. svg.setAttribute('viewBox', '0 -960 960 960');
  189. svg.setAttribute('width', '24px');
  190. svg.setAttribute('fill', '#e8eaed');
  191. svg.classList.add('default-icon');
  192. const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  193. const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
  194. gradient.setAttribute('id', 'buttonGradient');
  195. gradient.setAttribute('x1', '0%');
  196. gradient.setAttribute('y1', '0%');
  197. gradient.setAttribute('x2', '100%');
  198. gradient.setAttribute('y2', '100%');
  199. const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  200. stop1.setAttribute('offset', '0%');
  201. stop1.setAttribute('style', 'stop-color:#f03');
  202. const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  203. stop2.setAttribute('offset', '100%');
  204. stop2.setAttribute('style', 'stop-color:#ff2791');
  205. gradient.appendChild(stop1);
  206. gradient.appendChild(stop2);
  207. defs.appendChild(gradient);
  208. svg.appendChild(defs);
  209. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  210. path.setAttribute('d', 'M240-280h480L570-480 450-320l-90-120-120 160Zm-80 160q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z');
  211. svg.appendChild(path);
  212. const hoverSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  213. hoverSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  214. hoverSvg.setAttribute('height', '24px');
  215. hoverSvg.setAttribute('viewBox', '0 0 24 24');
  216. hoverSvg.setAttribute('width', '24px');
  217. hoverSvg.setAttribute('fill', '#e8eaed');
  218. hoverSvg.classList.add('hover-icon');
  219. const hoverPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  220. hoverPath.setAttribute('d', 'M20 5h-3.17l-1.24-1.35A2 2 0 0 0 14.12 3H9.88c-.56 0-1.1.24-1.47.65L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m-3 12H7a.5.5 0 0 1-.4-.8l2-2.67c.2-.27.6-.27.8 0L11.25 16l2.6-3.47c.2-.27.6-.27.8 0l2.75 3.67a.5.5 0 0 1-.4.8');
  221. hoverSvg.appendChild(defs.cloneNode(true));
  222. hoverSvg.appendChild(hoverPath);
  223. container.appendChild(svg);
  224. container.appendChild(hoverSvg);
  225. return container;
  226. },
  227.  
  228. getShortsScreenshotSVG() {
  229. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  230. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  231. svg.setAttribute('height', '24px');
  232. svg.setAttribute('viewBox', '0 0 24 24');
  233. svg.setAttribute('width', '24px');
  234. const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  235. const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
  236. gradient.setAttribute('id', 'shortsButtonGradient');
  237. gradient.setAttribute('x1', '0%');
  238. gradient.setAttribute('y1', '0%');
  239. gradient.setAttribute('x2', '100%');
  240. gradient.setAttribute('y2', '100%');
  241. const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  242. stop1.setAttribute('offset', '0%');
  243. stop1.setAttribute('style', 'stop-color:#f03');
  244. const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  245. stop2.setAttribute('offset', '100%');
  246. stop2.setAttribute('style', 'stop-color:#ff2791');
  247. gradient.appendChild(stop1);
  248. gradient.appendChild(stop2);
  249. defs.appendChild(gradient);
  250. svg.appendChild(defs);
  251. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  252. path.setAttribute('d', 'M20 5h-3.17l-1.24-1.35A2 2 0 0 0 14.12 3H9.88c-.56 0-1.1.24-1.47.65L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2m-3 12H7a.5.5 0 0 1-.4-.8l2-2.67c.2-.27.6-.27.8 0L11.25 16l2.6-3.47c.2-.27.6-.27.8 0l2.75 3.67a.5.5 0 0 1-.4.8');
  253. svg.appendChild(path);
  254. return svg;
  255. },
  256.  
  257. getVideoId() {
  258. const urlParams = new URLSearchParams(window.location.search);
  259. return urlParams.get('v') || window.location.pathname.split('/').pop();
  260. },
  261.  
  262. getVideoTitle(callback) {
  263. const videoId = this.getVideoId();
  264. GM_xmlhttpRequest({
  265. method: "GET",
  266. url: `https://exyezed.vercel.app/api/video/${videoId}`,
  267. onload: function(response) {
  268. if (response.status === 200) {
  269. const data = JSON.parse(response.responseText);
  270. callback(data.title);
  271. } else {
  272. callback('YouTube Video');
  273. }
  274. },
  275. onerror: function() {
  276. callback('YouTube Video');
  277. }
  278. });
  279. },
  280.  
  281. formatTime(time) {
  282. const date = new Date();
  283. const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
  284. const timeString = [
  285. Math.floor(time / 3600),
  286. Math.floor((time % 3600) / 60),
  287. Math.floor(time % 60)
  288. ].map(v => v.toString().padStart(2, '0')).join('-');
  289. return `${dateString} ${timeString}`;
  290. },
  291.  
  292. async copyToClipboard(blob) {
  293. const clipboardItem = new ClipboardItem({ "image/png": blob });
  294. await navigator.clipboard.write([clipboardItem]);
  295. },
  296.  
  297. downloadScreenshot(blob, filename) {
  298. const url = URL.createObjectURL(blob);
  299. const a = document.createElement('a');
  300. a.style.display = 'none';
  301. a.href = url;
  302. a.download = filename;
  303. document.body.appendChild(a);
  304. a.click();
  305. document.body.removeChild(a);
  306. URL.revokeObjectURL(url);
  307. },
  308.  
  309. captureScreenshot(player) {
  310. if (!player) return;
  311. const canvas = document.createElement("canvas");
  312. canvas.width = player.videoWidth;
  313. canvas.height = player.videoHeight;
  314. canvas.getContext('2d').drawImage(player, 0, 0, canvas.width, canvas.height);
  315. this.getVideoTitle((title) => {
  316. const time = player.currentTime;
  317. const filename = `${title} ${this.formatTime(time)}.${YouTubeEnhancerLoopScreenshotConfig.extension}`;
  318. canvas.toBlob(async (blob) => {
  319. const { screenshotFunctionality } = YouTubeEnhancerLoopScreenshotConfig;
  320. if (screenshotFunctionality >= 1) {
  321. await this.copyToClipboard(blob);
  322. }
  323. if (screenshotFunctionality !== 1) {
  324. this.downloadScreenshot(blob, filename);
  325. }
  326. }, `image/${YouTubeEnhancerLoopScreenshotConfig.screenshotFormat}`);
  327. });
  328. }
  329. };
  330.  
  331. const YouTubeEnhancerLoopScreenshotRegularVideo = {
  332. init() {
  333. this.waitForControls().then(() => {
  334. this.insertLoopElement();
  335. this.insertScreenshotElement();
  336. this.addObserver();
  337. this.addContextMenuListener();
  338. }).catch(error => {
  339. console.error('Failed to initialize YouTube Enhancer:', error);
  340. });
  341. },
  342.  
  343. waitForControls() {
  344. return new Promise((resolve, reject) => {
  345. let attempts = 0;
  346. const maxAttempts = 50;
  347. const checkControls = () => {
  348. const controls = document.querySelector('div.ytp-left-controls');
  349. if (controls) {
  350. resolve(controls);
  351. } else if (attempts >= maxAttempts) {
  352. reject(new Error('Controls not found after maximum attempts'));
  353. } else {
  354. attempts++;
  355. setTimeout(checkControls, 100);
  356. }
  357. };
  358. checkControls();
  359. });
  360. },
  361.  
  362. insertLoopElement() {
  363. const controls = document.querySelector('div.ytp-left-controls');
  364. if (!controls) return;
  365.  
  366. if (document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button')) return;
  367.  
  368. const newButton = document.createElement('a');
  369. newButton.classList.add('ytp-button', 'YouTubeEnhancerLoopScreenshot-loop-button');
  370. newButton.title = 'Loop Video';
  371. newButton.appendChild(YouTubeEnhancerLoopScreenshotUtils.getYouTubeVideoLoopSVG());
  372. newButton.addEventListener('click', this.toggleLoopState);
  373.  
  374. controls.appendChild(newButton);
  375. },
  376.  
  377. insertScreenshotElement() {
  378. const controls = document.querySelector('div.ytp-left-controls');
  379. if (!controls) return;
  380.  
  381. if (document.querySelector('.YouTubeEnhancerLoopScreenshot-screenshot-button')) return;
  382.  
  383. const newButton = document.createElement('a');
  384. newButton.classList.add('ytp-button', 'YouTubeEnhancerLoopScreenshot-screenshot-button');
  385. newButton.title = 'Take Screenshot';
  386. newButton.appendChild(YouTubeEnhancerLoopScreenshotUtils.getYouTubeVideoScreenshotSVG());
  387. newButton.addEventListener('click', this.handleScreenshotClick);
  388.  
  389. const loopButton = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button');
  390. if (loopButton) {
  391. loopButton.parentNode.insertBefore(newButton, loopButton.nextSibling);
  392. } else {
  393. controls.appendChild(newButton);
  394. }
  395. },
  396.  
  397. toggleLoopState() {
  398. const video = document.querySelector('video');
  399. video.loop = !video.loop;
  400. if (video.loop) video.play();
  401.  
  402. YouTubeEnhancerLoopScreenshotRegularVideo.updateToggleControls();
  403. },
  404.  
  405. updateToggleControls() {
  406. const youtubeVideoLoop = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button');
  407. youtubeVideoLoop.classList.toggle('active');
  408. youtubeVideoLoop.setAttribute('title', this.isActive() ? 'Stop Looping' : 'Loop Video');
  409. },
  410.  
  411. isActive() {
  412. const youtubeVideoLoop = document.querySelector('.YouTubeEnhancerLoopScreenshot-loop-button');
  413. return youtubeVideoLoop.classList.contains('active');
  414. },
  415.  
  416. addObserver() {
  417. const video = document.querySelector('video');
  418. new MutationObserver((mutations) => {
  419. mutations.forEach(() => {
  420. if ((video.getAttribute('loop') === null && this.isActive()) ||
  421. (video.getAttribute('loop') !== null && !this.isActive())) this.updateToggleControls();
  422. });
  423. }).observe(video, { attributes: true, attributeFilter: ['loop'] });
  424. },
  425.  
  426. addContextMenuListener() {
  427. const video = document.querySelector('video');
  428. video.addEventListener('contextmenu', () => {
  429. setTimeout(() => {
  430. const checkbox = document.querySelector('[role=menuitemcheckbox]');
  431. checkbox.setAttribute('aria-checked', this.isActive());
  432. checkbox.addEventListener('click', this.toggleLoopState);
  433. }, 50);
  434. });
  435. },
  436.  
  437. handleScreenshotClick(event) {
  438. const button = event.currentTarget;
  439. button.classList.add('clicked');
  440. setTimeout(() => {
  441. button.classList.remove('clicked');
  442. }, YouTubeEnhancerLoopScreenshotConfig.clickDuration);
  443.  
  444. const player = document.querySelector('video');
  445. YouTubeEnhancerLoopScreenshotUtils.captureScreenshot(player);
  446. }
  447. };
  448.  
  449. const YouTubeEnhancerLoopScreenshotShorts = {
  450. init() {
  451. this.insertScreenshotElement();
  452. },
  453. insertScreenshotElement() {
  454. const shortsContainer = document.querySelector('ytd-reel-video-renderer[is-active] #actions');
  455. if (shortsContainer && !shortsContainer.querySelector('.YouTubeEnhancerLoopScreenshot-shorts-screenshot-button')) {
  456. const iconDiv = document.createElement('div');
  457. iconDiv.className = 'YouTubeEnhancerLoopScreenshot-shorts-screenshot-button';
  458. iconDiv.appendChild(YouTubeEnhancerLoopScreenshotUtils.getShortsScreenshotSVG());
  459. const customShortsIcon = shortsContainer.querySelector('#custom-shorts-icon');
  460. if (customShortsIcon) {
  461. customShortsIcon.parentNode.insertBefore(iconDiv, customShortsIcon);
  462. } else {
  463. shortsContainer.insertBefore(iconDiv, shortsContainer.firstChild);
  464. }
  465. iconDiv.addEventListener('click', (event) => {
  466. const button = event.currentTarget;
  467. button.classList.add('clicked');
  468. const path = button.querySelector('svg path');
  469. if (path) {
  470. path.style.fill = 'url(#shortsButtonGradient)';
  471. }
  472. setTimeout(() => {
  473. button.classList.remove('clicked');
  474. if (path) {
  475. path.style.fill = '';
  476. }
  477. }, YouTubeEnhancerLoopScreenshotConfig.clickDuration);
  478. this.captureScreenshot();
  479. });
  480. }
  481. },
  482.  
  483. captureScreenshot() {
  484. const player = document.querySelector('ytd-reel-video-renderer[is-active] video');
  485. YouTubeEnhancerLoopScreenshotUtils.captureScreenshot(player);
  486. }
  487. };
  488.  
  489. const YouTubeEnhancerLoopScreenshotTheme = {
  490. init() {
  491. this.updateStyles();
  492. this.addObserver();
  493. },
  494.  
  495. updateStyles() {
  496. const isDarkTheme = document.documentElement.hasAttribute('dark');
  497. document.documentElement.classList.toggle('dark-theme', isDarkTheme);
  498. },
  499.  
  500. addObserver() {
  501. const observer = new MutationObserver(() => this.updateStyles());
  502. observer.observe(document.documentElement, {
  503. attributes: true,
  504. attributeFilter: ['dark']
  505. });
  506. }
  507. };
  508.  
  509. function YouTubeEnhancerLoopScreenshotInit() {
  510. YouTubeEnhancerLoopScreenshotUtils.addStyle(YouTubeEnhancerLoopScreenshotCSS);
  511. waitForVideo().then(initializeWhenReady);
  512. }
  513.  
  514. function waitForVideo() {
  515. return new Promise((resolve) => {
  516. const checkVideo = () => {
  517. if (document.querySelector('video')) {
  518. resolve();
  519. } else {
  520. setTimeout(checkVideo, 100);
  521. }
  522. };
  523. checkVideo();
  524. });
  525. }
  526.  
  527. function initializeWhenReady() {
  528. YouTubeEnhancerLoopScreenshotInitializeFeatures();
  529. }
  530.  
  531. function YouTubeEnhancerLoopScreenshotInitializeFeatures() {
  532. YouTubeEnhancerLoopScreenshotRegularVideo.init();
  533. YouTubeEnhancerLoopScreenshotTheme.init();
  534. YouTubeEnhancerLoopScreenshotInitializeShortsFeatures();
  535. }
  536.  
  537. function YouTubeEnhancerLoopScreenshotInitializeShortsFeatures() {
  538. if (window.location.pathname.includes('/shorts/')) {
  539. setTimeout(YouTubeEnhancerLoopScreenshotShorts.init.bind(YouTubeEnhancerLoopScreenshotShorts), 500);
  540. }
  541. }
  542.  
  543. const YouTubeEnhancerLoopScreenshotShortsObserver = new MutationObserver((mutations) => {
  544. for (let mutation of mutations) {
  545. if (mutation.type === 'childList') {
  546. YouTubeEnhancerLoopScreenshotInitializeShortsFeatures();
  547. }
  548. }
  549. });
  550.  
  551. YouTubeEnhancerLoopScreenshotShortsObserver.observe(document.body, { childList: true, subtree: true });
  552.  
  553. window.addEventListener('yt-navigate-finish', YouTubeEnhancerLoopScreenshotInitializeShortsFeatures);
  554.  
  555. document.addEventListener('yt-action', function(event) {
  556. if (event.detail && event.detail.actionName === 'yt-reload-continuation-items-command') {
  557. YouTubeEnhancerLoopScreenshotInitializeShortsFeatures();
  558. }
  559. });
  560.  
  561. YouTubeEnhancerLoopScreenshotInit();
  562. console.log('YouTube Enhancer (Loop & Screenshot Button) is running');
  563. })();