YouTube - Toggle videos buttons

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

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

  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.13.18.34
  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. let buttonsContainer;
  22. let buttonsRow;
  23. let toggleUpcomingButton;
  24. let toggleWatchedButton;
  25. let totalVideoCount;
  26.  
  27. let upcomingHidden = await GM.getValue('upcomingHidden', false);
  28. let watchedHidden = await GM.getValue('watchedHidden', false);
  29.  
  30. const shouldRenderButton = () => {
  31. return location.href.match(urlPattern) !== null;
  32. };
  33.  
  34. const shouldRunScript = () => {
  35. return document.querySelectorAll(unprocessedVideosSelectors).length;
  36. };
  37.  
  38. const runButtonTask = () => {
  39. if (shouldRenderButton()) {
  40. const buttonDestinationContainer = jQuery(
  41. buttonDestinationContainerSelector
  42. ).first();
  43.  
  44. if (
  45. buttonDestinationContainer.length &&
  46. !buttonDestinationContainer.find(buttonsContainer).length
  47. ) {
  48. insertButtons(buttonDestinationContainer);
  49. }
  50. } else {
  51. buttonsContainer.remove();
  52. }
  53. };
  54.  
  55. const runVideosTask = () => {
  56. if (shouldRunScript()) {
  57. setTimeout(processAllVideos, 150);
  58. }
  59. };
  60.  
  61. const insertButtons = (buttonDestinationContainer) => {
  62. toggleWatchedButton
  63. .off('click')
  64. .on('click', toggleWatchedVideos)
  65. .text(i18n.hideWatched);
  66.  
  67. toggleUpcomingButton
  68. .off('click')
  69. .on('click', toggleUpcomingVideos)
  70. .text(i18n.hideUpcoming);
  71.  
  72. buttonDestinationContainer.prepend(buttonsContainer);
  73. };
  74.  
  75. const processAllVideos = () => {
  76. if (upcomingHidden) processUpcomingVideos();
  77. if (watchedHidden) processWatchedVideos();
  78. };
  79.  
  80. const toggleWatchedVideos = () => {
  81. watchedHidden = !watchedHidden;
  82. GM.setValue('watchedHidden', watchedHidden);
  83. processWatchedVideos();
  84. };
  85.  
  86. const toggleUpcomingVideos = () => {
  87. upcomingHidden = !upcomingHidden;
  88. GM.setValue('upcomingHidden', upcomingHidden);
  89. processUpcomingVideos();
  90. };
  91.  
  92. const processWatchedVideos = () => {
  93. processVideos(
  94. watchedHidden,
  95. watchedVideosSelector,
  96. toggleWatchedButton,
  97. watchedHidden ? i18n.showWatched : i18n.hideWatched
  98. );
  99. };
  100.  
  101. const processUpcomingVideos = () => {
  102. processVideos(
  103. upcomingHidden,
  104. upcomingVideosSelector,
  105. toggleUpcomingButton,
  106. upcomingHidden ? i18n.showUpcoming : i18n.hideUpcoming
  107. );
  108. };
  109.  
  110. const processVideos = (hide, matchingSelector, button, text) => {
  111. const matchingVideos = jQuery(matchingSelector).parents(videosSelector);
  112. hide
  113. ? matchingVideos.addClass('mt-hidden')
  114. : matchingVideos.removeClass('mt-hidden');
  115.  
  116. const matchingVideosCount = matchingVideos && matchingVideos.length;
  117. button.text(
  118. text
  119. .replace(/\{\s*matchingVideos\s*\}/g, matchingVideosCount)
  120. .replace(/\{\s*totalCount\s*\}/g, totalVideoCount)
  121. );
  122. };
  123.  
  124. const initialize = () => {
  125. jQuery('head').append(baseStyle);
  126.  
  127. toggleWatchedButton = jQuery(buttonTemplate);
  128. toggleUpcomingButton = jQuery(buttonTemplate);
  129.  
  130. buttonsRow = jQuery(buttonsRowTemplate);
  131. buttonsRow.append(toggleWatchedButton);
  132. buttonsRow.append(toggleUpcomingButton);
  133.  
  134. buttonsContainer = jQuery(buttonsContainerTemplate);
  135. buttonsContainer.append(buttonsRow);
  136.  
  137. setInterval(runButtonTask, 150);
  138. setInterval(runVideosTask, 1000);
  139.  
  140. console.info(
  141. `Script "Toggle videos buttons" ready for use on ${location.origin}`
  142. );
  143. };
  144.  
  145. const urlPattern =
  146. /youtube.com\/((channel\/|c\/|@)(.*)\/videos|feed\/subscriptions|results|playlist)/;
  147.  
  148. // texts
  149. const i18n = {
  150. hide: 'Hide',
  151. show: 'Show',
  152. watched: 'watched',
  153. upcoming: 'upcoming',
  154. buttonParams: '({ matchingVideos })',
  155. };
  156.  
  157. i18n.hideWatched = `${i18n.hide} ${i18n.watched}`;
  158. i18n.hideUpcoming = `${i18n.hide} ${i18n.upcoming}`;
  159.  
  160. i18n.showWatched = `${i18n.show} ${i18n.watched} ${i18n.buttonParams}`;
  161. i18n.showUpcoming = `${i18n.show} ${i18n.upcoming} ${i18n.buttonParams}`;
  162.  
  163. // selectors
  164. const watchedVideosSelector = `[id="progress"]`;
  165.  
  166. const upcomingVideosSelector = `[overlay-style="UPCOMING"]`;
  167.  
  168. const bothVideosSelector = `${watchedVideosSelector}, ${upcomingVideosSelector}`;
  169.  
  170. const buttonDestinationContainerSelector = `
  171. [page-subtype="channels"][role="main"] ytd-rich-grid-renderer,
  172. [page-subtype="playlist"][role="main"] ytd-item-section-renderer,
  173. [page-subtype="subscriptions"][role="main"] ytd-section-list-renderer,
  174. ytd-search[role="main"] ytd-section-list-renderer
  175. `;
  176.  
  177. const videosSelector = `
  178. [page-subtype="channels"][role="main"] ytd-rich-item-renderer,
  179. [page-subtype="playlist"][role="main"] ytd-playlist-video-renderer,
  180. [page-subtype="subscriptions"][role="main"] ytd-grid-video-renderer,
  181. ytd-search[role="main"] ytd-video-renderer
  182. `;
  183.  
  184. const unprocessedVideosSelectors = videosSelector
  185. .split(',')
  186. .map(
  187. (selector) =>
  188. `${selector}:not(.mt-hidden) ${watchedVideosSelector},\n${selector}:not(.mt-hidden) ${upcomingVideosSelector}`
  189. )
  190. .join(',');
  191.  
  192. // templates
  193. const buttonTemplate = `
  194. <tp-yt-paper-button class="style-scope ytd-subscribe-button-renderer mt-toggle-videos-Button" />
  195. `;
  196.  
  197. const buttonsContainerTemplate = `
  198. <div class="mt-toggle-Videos-container"></div>
  199. `;
  200.  
  201. const buttonsRowTemplate = `
  202. <div class="mt-toggle-videos-Buttons-row"></div>
  203. `;
  204.  
  205. // style
  206. const baseStyle = `
  207. <style>
  208. .mt-toggle-Videos-container {
  209. width: 100%;
  210. }
  211.  
  212. .mt-toggle-videos-Buttons-row {
  213. display: flex;
  214. grid-gap: 8px;
  215. }
  216.  
  217. .mt-toggle-videos-Button {
  218. border-radius: 8px !important;
  219. flex: 1;
  220. margin: 0 !important;
  221. }
  222.  
  223. .mt-hidden {
  224. display: none !important;
  225. }
  226.  
  227. [page-subtype="channels"] .mt-toggle-Videos-container,
  228. [page-subtype="subscriptions"] .mt-toggle-Videos-container {
  229. margin-top: 24px;
  230. }
  231.  
  232. .ytd-search ytd-section-list-renderer .mt-toggle-Videos-container {
  233. margin: 12px 0;
  234. }
  235. </style>
  236. `;
  237.  
  238. initialize();
  239. })();