Auto Scroll YouTube Shorts

Auto scroll YouTube Shorts with an ON/OFF switch and Config option

  1. // ==UserScript==
  2. // @name Auto Scroll YouTube Shorts
  3. // @namespace https://gf.qytechs.cn/users/1308345-hellfiveosborn
  4. // @homepageURL https://gf.qytechs.cn/scripts/498319
  5. // @supportURL https://gf.qytechs.cn/scripts/498319/feedback
  6. // @version 1.0
  7. // @description Auto scroll YouTube Shorts with an ON/OFF switch and Config option
  8. // @date 2024-06-19
  9. // @author HellFive Osborn
  10. // @match *://*.youtube.com/*
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  12. // @grant GM_addStyle
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_unregisterMenuCommand
  17. // @grant GM_xmlhttpRequest
  18. // @compatible chrome
  19. // @compatible firefox
  20. // @compatible edge
  21. // @compatible brave
  22. // @compatible kiwi
  23. // @license MIT
  24. // ==/UserScript==
  25.  
  26. (function () {
  27. 'use strict';
  28.  
  29. // Init CONFIG
  30. const config = {
  31. appName: 'Auto Scroll YouTube Shorts',
  32. appSymbol: '📺',
  33. keyPrefix: 'autoscrollytshorts',
  34. greasyForkURL: 'https://gf.qytechs.cn/scripts/498319-auto-scroll-youtube-shorts',
  35. updateUrl: 'https://update.gf.qytechs.cn/scripts/498319/Auto%20Scroll%20YouTube%20Shorts.meta.js'
  36. };
  37.  
  38. // Load TailwindCSS
  39. const tailwindCSS = `
  40. @import url('https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css');
  41. `;
  42. GM_addStyle(tailwindCSS);
  43.  
  44. // Register the Config menu command
  45. const idMenu = GM_registerMenuCommand('Config', showConfigInterface);
  46. let autoScrollInterval;
  47.  
  48. function showConfigInterface() {
  49. // Check if the interface already exists
  50. if (document.getElementById('autoScrollInterface')) {
  51. return;
  52. }
  53.  
  54. // Create the interface
  55. const interfaceHTML = `
  56. <div id="autoScrollInterface" class="fixed bottom-4 right-4 bg-gray-800 p-4 rounded-lg shadow-lg z-50 text-center">
  57. <div class="flex justify-between items-center mb-2 relative">
  58. <h1 class="text-gray-300 font-medium">YouTube Shorts Auto Scroll</h1>
  59. <button id="closeButton" class="text-gray-100 hover:bg-red-300 p-0 lh-0 bg-red-400 mt-0 absolute -right-5 -top-6 rounded-full w-5 h-5 leading-none">&times;</button>
  60. </div>
  61. <label class="flex items-center cursor-pointer justify-center">
  62. <div class="relative">
  63. <input id="autoScrollToggle" type="checkbox" class="sr-only" />
  64. <div class="block bg-gray-600 w-14 h-8 rounded-full"></div>
  65. <div id="toggleDot" class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"></div>
  66. </div>
  67. <div class="ml-3 text-gray-300 font-medium">Auto Scroll</div>
  68. </label>
  69. <div id="updateMessage" class="mt-2 text-gray-300"></div>
  70. <button id="updateButton" class="hidden mt-2 bg-blue-500 text-white p-2 rounded-full w-full">Update now 🚀</button>
  71. </div>
  72. `;
  73. document.body.insertAdjacentHTML('beforeend', interfaceHTML);
  74.  
  75. // Toggle functionality
  76. const autoScrollToggle = document.getElementById('autoScrollToggle');
  77. const toggleDot = document.getElementById('toggleDot');
  78. const closeButton = document.getElementById('closeButton');
  79. closeButton.addEventListener('click', () => {
  80. document.getElementById('autoScrollInterface').remove();
  81. });
  82.  
  83. autoScrollToggle.addEventListener('change', function () {
  84. if (this.checked) {
  85. toggleDot.style.transform = 'translateX(100%)';
  86. startAutoScroll();
  87. GM_setValue('autoScroll', 'on');
  88. closeButton.style.display = 'none';
  89. } else {
  90. toggleDot.style.transform = 'translateX(0)';
  91. stopAutoScroll();
  92. GM_setValue('autoScroll', 'off');
  93. closeButton.style.display = 'block';
  94. }
  95. });
  96.  
  97. // Initialize based on GM_getValue
  98. const autoScrollStatus = GM_getValue('autoScroll', 'off');
  99. if (autoScrollStatus === 'on') {
  100. autoScrollToggle.checked = true;
  101. toggleDot.style.transform = 'translateX(100%)';
  102. startAutoScroll();
  103. closeButton.style.display = 'none';
  104. } else {
  105. autoScrollToggle.checked = false;
  106. toggleDot.style.transform = 'translateX(0)';
  107. stopAutoScroll();
  108. closeButton.style.display = 'block';
  109. }
  110.  
  111. // Handle visibility change
  112. document.addEventListener('visibilitychange', function () {
  113. if (autoScrollToggle.checked) {
  114. if (document.visibilityState === 'visible' || document.pictureInPictureElement) {
  115. startAutoScroll();
  116. } else {
  117. stopAutoScroll();
  118. }
  119. }
  120. });
  121.  
  122. // Check for updates
  123. updateCheck();
  124. }
  125.  
  126. function startAutoScroll() {
  127. console.log('Auto scroll started');
  128. autoScrollInterval = setInterval(() => {
  129. const video = document.querySelector('video');
  130. if (video) {
  131. video.loop = false; // Ensure loop is disabled
  132. if (video.duration > 0 && video.currentTime >= video.duration - 0.5) {
  133. console.log('Video finished, scrolling to next');
  134. goToNextShort();
  135. }
  136. }
  137. }, 1000); // Check every second
  138. }
  139.  
  140. function stopAutoScroll() {
  141. console.log('Auto scroll stopped');
  142. clearInterval(autoScrollInterval);
  143. }
  144.  
  145. function goToNextShort() {
  146. const nextButtonContainer = document.querySelector('.navigation-container #navigation-button-down');
  147. if (nextButtonContainer) {
  148. const nextButton = nextButtonContainer.querySelector('button');
  149. if (nextButton) {
  150. nextButton.click();
  151. console.log('Clicked next button');
  152. } else {
  153. console.log('Next button not found inside container');
  154. }
  155. } else {
  156. console.log('Next button container not found');
  157. }
  158. }
  159.  
  160. // Function to handle keydown events
  161. function handleKeyDown(event) {
  162. if (event.key === 'ArrowDown') {
  163. const video = document.querySelector('video');
  164. if (video) {
  165. video.currentTime = video.duration; // Skip to the end of the video
  166. }
  167. }
  168. }
  169.  
  170. // Update check
  171. function updateCheck() {
  172. const currentVer = GM_info.script.version;
  173. GM_xmlhttpRequest({
  174. method: 'GET',
  175. url: config.updateUrl + '?t=' + Date.now(),
  176. headers: { 'Cache-Control': 'no-cache' },
  177. onload: response => {
  178. const latestVer = /@version +(.*)/.exec(response.responseText)[1];
  179. console.log('[Auto Scroll Youtube Shorts]', 'Current version:', currentVer, 'Latest:', latestVer)
  180. if (isOutdatedVersion(currentVer, latestVer)) {
  181. const updateMessage = document.getElementById('updateMessage');
  182. const updateButton = document.getElementById('updateButton');
  183. updateMessage.innerHTML = `There is an update available: v${latestVer}`;
  184. updateButton.classList.remove('hidden');
  185. updateButton.addEventListener('click', () => {
  186. window.open(config.greasyForkURL, '_blank');
  187. });
  188. }
  189. }
  190. });
  191. }
  192.  
  193. function isOutdatedVersion(currentVer, latestVer) {
  194. const current = currentVer.split('.').map(Number);
  195. const latest = latestVer.split('.').map(Number);
  196. for (let i = 0; i < current.length; i++) {
  197. if (latest[i] > current[i]) return true;
  198. if (latest[i] < current[i]) return false;
  199. }
  200. return false;
  201. }
  202.  
  203. // Add event listener for keydown events
  204. document.addEventListener('keydown', handleKeyDown);
  205.  
  206. // Cleanup on script disable
  207. function cleanup() {
  208. stopAutoScroll();
  209. const interfaceElement = document.getElementById('autoScrollInterface');
  210. if (interfaceElement) {
  211. interfaceElement.remove();
  212. }
  213. //GM_unregisterMenuCommand(idMenu);
  214. }
  215.  
  216. // Observe script disable
  217. const observer = new MutationObserver(mutations => {
  218. for (const mutation of mutations) {
  219. if (mutation.removedNodes) {
  220. for (const node of mutation.removedNodes) {
  221. if (node.nodeType === 1 && node.id === 'autoScrollInterface') {
  222. cleanup();
  223. }
  224. }
  225. }
  226. }
  227. });
  228.  
  229. observer.observe(document.body, { childList: true });
  230. })();

QingJ © 2025

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