YouTube - Toggle videos buttons

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

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

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