YouTube - Toggle videos buttons

Adds buttons to hide watched and/or upcoming videos from the subscription page / channel videos tab.

当前为 2023-03-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube - Toggle videos buttons
  3. // @description Adds buttons to hide watched and/or upcoming videos from the subscription page / channel videos tab.
  4. // @version 2023.03.26.19.59
  5. // @author MetalTxus
  6. // @namespace https://github.com/jesuscc1993
  7.  
  8. // @icon https://www.youtube.com/favicon.ico
  9. // @match *://*.youtube.com/*
  10. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
  11.  
  12. // @grant GM.getValue
  13. // @grant GM.setValue
  14. // ==/UserScript==
  15.  
  16. /* globals jQuery */
  17.  
  18. (async () => {
  19. 'use strict';
  20.  
  21. const enableDebug = true;
  22.  
  23. let buttonsContainer;
  24. let buttonsRow;
  25. let toggleUpcomingButton;
  26. let toggleWatchedButton;
  27. let videosTotal;
  28.  
  29. let upcomingHidden = await GM.getValue('upcomingHidden', false);
  30. let watchedHidden = await GM.getValue('watchedHidden', false);
  31.  
  32. const shouldRenderButton = () => {
  33. return location.href.match(urlPattern) !== null;
  34. };
  35.  
  36. const shouldRunScript = () => {
  37. return document.querySelectorAll(unprocessedVideosSelectors).length;
  38. };
  39.  
  40. const runButtonTask = () => {
  41. if (shouldRenderButton()) {
  42. const buttonDestinationContainer = jQuery(
  43. buttonDestinationContainerSelector
  44. ).first();
  45.  
  46. if (
  47. buttonDestinationContainer.length &&
  48. !buttonDestinationContainer.find(buttonsContainer).length
  49. ) {
  50. insertButtons(buttonDestinationContainer);
  51. }
  52. } else {
  53. buttonsContainer.remove();
  54. }
  55. };
  56.  
  57. const runVideosTask = () => {
  58. if (shouldRunScript()) {
  59. setTimeout(processAllVideos, 150);
  60. }
  61. };
  62.  
  63. const insertButtons = (buttonDestinationContainer) => {
  64. toggleWatchedButton.off('click').on('click', toggleWatchedVideos);
  65. toggleUpcomingButton.off('click').on('click', toggleUpcomingVideos);
  66.  
  67. setButtonText(
  68. toggleWatchedButton,
  69. watchedHidden ? i18n.showWatched : i18n.hideWatched
  70. );
  71.  
  72. setButtonText(
  73. toggleUpcomingButton,
  74. upcomingHidden ? i18n.showUpcoming : i18n.hideUpcoming
  75. );
  76.  
  77. buttonDestinationContainer.prepend(buttonsContainer);
  78. };
  79.  
  80. const processAllVideos = () => {
  81. const allVideos = jQuery(videosSelector);
  82. videosTotal = allVideos.length;
  83.  
  84. debug(`Processing videos...`);
  85. if (upcomingHidden) processUpcomingVideos();
  86. if (watchedHidden) processWatchedVideos();
  87. debug(`All videos processed`);
  88.  
  89. allVideos.addClass('mt-processed');
  90. };
  91.  
  92. const toggleWatchedVideos = () => {
  93. watchedHidden = !watchedHidden;
  94. GM.setValue('watchedHidden', watchedHidden);
  95. processWatchedVideos();
  96. };
  97.  
  98. const toggleUpcomingVideos = () => {
  99. upcomingHidden = !upcomingHidden;
  100. GM.setValue('upcomingHidden', upcomingHidden);
  101. processUpcomingVideos();
  102. };
  103.  
  104. const processWatchedVideos = () => {
  105. processVideos(
  106. watchedHidden,
  107. watchedVideosSelector,
  108. toggleWatchedButton,
  109. watchedHidden ? i18n.showWatched : i18n.hideWatched
  110. );
  111. };
  112.  
  113. const processUpcomingVideos = () => {
  114. processVideos(
  115. upcomingHidden,
  116. upcomingVideosSelector,
  117. toggleUpcomingButton,
  118. upcomingHidden ? i18n.showUpcoming : i18n.hideUpcoming
  119. );
  120. };
  121.  
  122. const processVideos = (hide, matchingSelector, button, text) => {
  123. const matchingVideos = jQuery(matchingSelector).parents(videosSelector);
  124. hide
  125. ? matchingVideos.addClass('mt-hidden')
  126. : matchingVideos.removeClass('mt-hidden');
  127.  
  128. const matchingVideosCount = matchingVideos && matchingVideos.length;
  129. setButtonText(button, text, { matchingVideosCount });
  130. };
  131.  
  132. const setButtonText = (button, text, params) => {
  133. const suffix = params?.matchingVideosCount !== undefined ? `(${params.matchingVideosCount} / ${videosTotal})` : '';
  134. button.text(`${text} ${suffix}`);
  135. };
  136.  
  137. const debug = enableDebug ?
  138. (message) => console.debug(`${scriptPrefix} ${message}`) :
  139. () => {};
  140.  
  141. const initialize = () => {
  142. jQuery('head').append(baseStyle);
  143.  
  144. toggleWatchedButton = jQuery(buttonTemplate);
  145. toggleUpcomingButton = jQuery(buttonTemplate);
  146.  
  147. buttonsRow = jQuery(buttonsRowTemplate);
  148. buttonsRow.append(toggleWatchedButton);
  149. buttonsRow.append(toggleUpcomingButton);
  150.  
  151. buttonsContainer = jQuery(buttonsContainerTemplate);
  152. buttonsContainer.append(buttonsRow);
  153.  
  154. setInterval(runButtonTask, 150);
  155. setInterval(runVideosTask, 1000);
  156.  
  157. console.info(`${scriptPrefix} Script initialized.`);
  158. };
  159.  
  160. const scriptPrefix = `[Toggle videos buttons]`;
  161.  
  162. const urlPattern =
  163. /youtube.com\/((channel\/|c\/|@)(.*)\/videos|feed\/subscriptions|results|playlist)/;
  164.  
  165. // texts
  166. const i18n = {
  167. hide: 'Hide',
  168. show: 'Show',
  169. watched: 'watched',
  170. upcoming: 'upcoming'
  171. };
  172.  
  173. i18n.hideWatched = `${i18n.hide} ${i18n.watched}`;
  174. i18n.hideUpcoming = `${i18n.hide} ${i18n.upcoming}`;
  175.  
  176. i18n.showWatched = `${i18n.show} ${i18n.watched}`;
  177. i18n.showUpcoming = `${i18n.show} ${i18n.upcoming}`;
  178.  
  179. // selectors
  180. const watchedVideosSelector = `[id="progress"]`;
  181.  
  182. const upcomingVideosSelector = `[overlay-style="UPCOMING"]`;
  183.  
  184. const buttonDestinationContainerSelector = `
  185. [page-subtype="channels"][role="main"] ytd-rich-grid-renderer,
  186. [page-subtype="playlist"][role="main"] ytd-item-section-renderer,
  187. [page-subtype="subscriptions"][role="main"] ytd-section-list-renderer,
  188. ytd-search[role="main"] ytd-section-list-renderer
  189. `;
  190.  
  191. const videosSelector = `
  192. [page-subtype="channels"][role="main"] ytd-rich-item-renderer,
  193. [page-subtype="playlist"][role="main"] ytd-playlist-video-renderer,
  194. [page-subtype="subscriptions"][role="main"] ytd-grid-video-renderer,
  195. ytd-search[role="main"] ytd-video-renderer
  196. `;
  197.  
  198. /*const unprocessedVideosSelectors = videosSelector
  199. .split(',')
  200. .map(
  201. (selector) =>
  202. `${selector}:not(.mt-hidden) ${watchedVideosSelector},\n${selector}:not(.mt-hidden) ${upcomingVideosSelector}`
  203. )
  204. .join(',');*/
  205.  
  206. const unprocessedVideosSelectors = videosSelector
  207. .split(',')
  208. .map((selector) => `${selector}:not(.mt-processed)`)
  209. .join(',');
  210.  
  211. // templates
  212. const buttonTemplate = `
  213. <tp-yt-paper-button class="style-scope ytd-subscribe-button-renderer mt-toggle-videos-button" />
  214. `;
  215.  
  216. const buttonsContainerTemplate = `
  217. <div class="mt-toggle-videos-container"></div>
  218. `;
  219.  
  220. const buttonsRowTemplate = `
  221. <div class="mt-toggle-videos-buttons-row"></div>
  222. `;
  223.  
  224. // style
  225. const baseStyle = `
  226. <style>
  227. .mt-toggle-videos-container {
  228. width: 100%;
  229. }
  230.  
  231. .mt-toggle-videos-buttons-row {
  232. display: flex;
  233. grid-gap: 8px;
  234. }
  235.  
  236. .mt-toggle-videos-button {
  237. border-radius: 20px !important;
  238. flex: 1;
  239. margin: 0 !important;
  240. }
  241.  
  242. .mt-hidden {
  243. display: none !important;
  244. }
  245.  
  246. [page-subtype="channels"] .mt-toggle-videos-container,
  247. [page-subtype="subscriptions"] .mt-toggle-videos-container {
  248. margin-top: 24px;
  249. }
  250.  
  251. [page-subtype="playlist"] .mt-toggle-videos-container {
  252. box-sizing: border-box;
  253. padding: 0 24px;
  254. }
  255.  
  256. .ytd-search ytd-section-list-renderer .mt-toggle-videos-container {
  257. margin: 12px 0;
  258. }
  259. </style>
  260. `;
  261.  
  262. initialize();
  263. })();