您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds buttons to filter out videos by type and/or status. The toggles can be hidden/shown at any time by pressing the button added to the header.
- // ==UserScript==
- // @name YouTube - Toggle videos buttons
- // @description Adds buttons to filter out videos by type and/or status. The toggles can be hidden/shown at any time by pressing the button added to the header.
- // @version 2023.06.13.17.21
- // @author MetalTxus
- // @namespace https://github.com/jesuscc1993
- // @icon https://www.youtube.com/favicon.ico
- // @match *://*.youtube.com/*
- // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
- // @grant GM.getValue
- // @grant GM.setValue
- // ==/UserScript==
- /* globals jQuery */
- (async () => {
- 'use strict';
- const enableDebug = false;
- let currentUrl;
- let videosTotal;
- let buttonsContainer;
- let toggleButtonsButton;
- let toggleLiveButton;
- let toggleShortsButton;
- let toggleUpcomingButton;
- let toggleUploadsButton;
- let toggleWatchedButton;
- let buttonsHidden = await GM.getValue('buttonsHidden', false);
- let liveHidden = await GM.getValue('liveHidden', false);
- let shortsHidden = await GM.getValue('shortsHidden', false);
- let upcomingHidden = await GM.getValue('upcomingHidden', false);
- let uploadsHidden = await GM.getValue('uploadsHidden', false);
- let watchedHidden = await GM.getValue('watchedHidden', false);
- const shouldRenderButton = () => {
- return location.href.match(urlPattern) !== null;
- };
- const shouldFilterByTypeButton = () => {
- return location.href.match(urlWithTypesPattern) !== null;
- };
- const shouldFilterByStatus = () => {
- return true;
- };
- const shouldRunScript = () => {
- const oldUrl = currentUrl;
- currentUrl = location.href.split('?')[0];
- const oldVideosTotal = videosTotal;
- videosTotal = jQuery(videosSelector).length;
- const locationChanged = !!oldUrl && oldUrl !== currentUrl;
- const videosCountChanged = oldVideosTotal !== videosTotal;
- const videosShouldBeHidden =
- (liveHidden ||
- shortsHidden ||
- upcomingHidden ||
- uploadsHidden ||
- watchedHidden) &&
- !!document.querySelectorAll(unprocessedVideosSelectors).length;
- const videosShouldBeShown =
- !(
- liveHidden &&
- shortsHidden &&
- upcomingHidden &&
- uploadsHidden &&
- watchedHidden
- ) && !!document.querySelectorAll(processedVideosSelectors).length;
- const shouldIt =
- shouldRenderButton() &&
- (locationChanged ||
- videosCountChanged ||
- videosShouldBeHidden ||
- videosShouldBeShown);
- if (shouldIt) {
- debug(`Videos should be processed
- locationChanged: ${locationChanged}
- videosCountChanged: ${videosCountChanged}
- videosShouldBeHidden: ${videosShouldBeHidden}
- videosShouldBeShown: ${videosShouldBeShown}`);
- }
- return shouldIt;
- };
- const runButtonTask = () => {
- if (shouldRenderButton()) {
- const buttonsDestinationContainer = jQuery(
- buttonDestinationContainerSelector
- );
- if (
- buttonsDestinationContainer.length &&
- !buttonsDestinationContainer.find(buttonsContainer).length
- ) {
- insertButtons(buttonsDestinationContainer);
- }
- } else {
- buttonsContainer.remove();
- toggleButtonsButton.remove();
- }
- };
- const runVideosTask = () => {
- if (shouldRunScript()) {
- setTimeout(processAllVideos, 150);
- }
- };
- const insertButtons = (buttonDestinationContainer) => {
- toggleLiveButton.off('click').on('click', toggleLiveVideos);
- toggleShortsButton.off('click').on('click', toggleShortsVideos);
- toggleUpcomingButton.off('click').on('click', toggleUpcomingVideos);
- toggleUploadsButton.off('click').on('click', toggleUploadsVideos);
- toggleWatchedButton.off('click').on('click', toggleWatchedVideos);
- setButtonState(toggleLiveButton, liveHidden);
- setButtonState(toggleShortsButton, shortsHidden);
- setButtonState(toggleUpcomingButton, upcomingHidden);
- setButtonState(toggleUploadsButton, uploadsHidden);
- setButtonState(toggleWatchedButton, watchedHidden);
- buttonDestinationContainer.prepend(buttonsContainer);
- toggleButtonsButton.off('click').on('click', toggleButtons);
- jQuery(buttonsToggleDestinationSelector).prepend(toggleButtonsButton);
- };
- const processAllVideos = () => {
- debug(`Processing videos...`);
- if (liveHidden) processLiveVideos();
- if (shortsHidden) processShortsVideos();
- if (upcomingHidden) processUpcomingVideos();
- if (uploadsHidden) processUploadsVideos();
- if (watchedHidden) processWatchedVideos();
- debug(`All videos processed`);
- };
- const toggleLiveVideos = () => {
- liveHidden = !liveHidden;
- GM.setValue('liveHidden', liveHidden);
- processLiveVideos();
- };
- const toggleShortsVideos = () => {
- shortsHidden = !shortsHidden;
- GM.setValue('shortsHidden', shortsHidden);
- processShortsVideos();
- };
- const toggleUpcomingVideos = () => {
- upcomingHidden = !upcomingHidden;
- GM.setValue('upcomingHidden', upcomingHidden);
- processUpcomingVideos();
- };
- const toggleUploadsVideos = () => {
- uploadsHidden = !uploadsHidden;
- GM.setValue('uploadsHidden', uploadsHidden);
- processUploadsVideos();
- };
- const toggleWatchedVideos = () => {
- watchedHidden = !watchedHidden;
- GM.setValue('watchedHidden', watchedHidden);
- processWatchedVideos();
- };
- const toggleButtons = (newValue) => {
- buttonsHidden = typeof newValue == 'boolean' ? newValue : !buttonsHidden;
- GM.setValue('buttonsHidden', buttonsHidden);
- buttonsHidden
- ? buttonsContainer.addClass('hide-buttons')
- : buttonsContainer.removeClass('hide-buttons');
- };
- const processLiveVideos = () => {
- if (shouldFilterByTypeButton()) {
- processVideos(toggleLiveButton, liveHidden, liveVideosSelector);
- }
- };
- const processShortsVideos = () => {
- if (shouldFilterByTypeButton()) {
- processVideos(toggleShortsButton, shortsHidden, shortsVideosSelector);
- }
- };
- const processUpcomingVideos = () => {
- if (shouldFilterByStatus()) {
- processVideos(
- toggleUpcomingButton,
- upcomingHidden,
- upcomingVideosSelector
- );
- }
- };
- const processUploadsVideos = () => {
- if (shouldFilterByTypeButton()) {
- processVideos(toggleUploadsButton, uploadsHidden, uploadsVideosSelector);
- }
- };
- const processWatchedVideos = () => {
- if (shouldFilterByStatus()) {
- processVideos(toggleWatchedButton, watchedHidden, watchedVideosSelector);
- }
- };
- const processVideos = (button, hidden, matchingSelector) => {
- const matchingVideos = jQuery(matchingSelector).parents(videosSelector);
- matchingVideos.toggleClass('mt-hidden', hidden);
- setButtonState(button, hidden);
- };
- const setButtonState = (button, hidden) => {
- button.toggleClass('on', !hidden);
- };
- const debug = enableDebug
- ? (message) => console.debug(`${scriptPrefix} ${message}`)
- : () => {};
- const initialize = () => {
- jQuery('head').append(baseStyle);
- toggleLiveButton = jQuery(toggleVideosButtonTemplate)
- .addClass(`${i18n.live} type`)
- .text(i18n.live);
- toggleShortsButton = jQuery(toggleVideosButtonTemplate)
- .addClass(`${i18n.shorts} type`)
- .text(i18n.shorts);
- toggleUpcomingButton = jQuery(toggleVideosButtonTemplate)
- .addClass(`${i18n.upcoming} status`)
- .text(i18n.upcoming);
- toggleUploadsButton = jQuery(toggleVideosButtonTemplate)
- .addClass(`${i18n.uploads} type`)
- .text(i18n.uploads);
- toggleWatchedButton = jQuery(toggleVideosButtonTemplate)
- .addClass(`${i18n.watched} status`)
- .text(i18n.watched);
- buttonsContainer = jQuery(buttonsContainerTemplate);
- buttonsContainer.append(toggleUpcomingButton);
- buttonsContainer.append(toggleLiveButton);
- buttonsContainer.append(toggleUploadsButton);
- buttonsContainer.append(toggleShortsButton);
- buttonsContainer.append(toggleWatchedButton);
- toggleButtonsButton = jQuery(toggleButtonsButtonTemplate);
- toggleButtons(buttonsHidden);
- setInterval(runButtonTask, 150);
- setInterval(runVideosTask, 1000);
- console.info(`${scriptPrefix} Script initialized.`);
- };
- const scriptPrefix = `[Toggle videos buttons]`;
- const urlPattern =
- /youtube.com(\/?$|\/((channel\/|c\/|@)(\w*)(\/(featured|videos|shorts|streams)|\/?$)|feed\/subscriptions|results|playlist))/;
- const urlWithTypesPattern =
- /youtube.com(\/?$|\/((channel\/|c\/|@)(\w*)(\/(featured)|\/?$)|feed\/subscriptions|results|playlist))/;
- // texts
- const i18n = {
- toggleButtons: 'Toggle video filter buttons',
- live: 'live',
- shorts: 'shorts',
- upcoming: 'upcoming',
- uploads: 'videos',
- watched: 'watched',
- };
- // selectors
- const liveVideosSelector = `
- [role="main"] .badge-style-type-live-now-alternate
- `;
- const shortsVideosSelector = `
- [role="main"] ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"],
- [role="main"] .ytd-thumbnail[href^="/shorts/"]
- `;
- const upcomingVideosSelector = `
- [role="main"] ytd-thumbnail-overlay-time-status-renderer[overlay-style="UPCOMING"]
- `;
- const uploadsVideosSelector = `
- [role="main"] ytd-thumbnail-overlay-time-status-renderer:not([overlay-style="SHORTS"])
- `;
- const watchedVideosSelector = `
- [role="main"] [id="progress"]
- `;
- const buttonDestinationContainerSelector = `
- [page-subtype="channels"][role="main"] #primary > ytd-section-list-renderer,
- [page-subtype="channels"][role="main"] ytd-rich-grid-renderer,
- [page-subtype="home"][role="main"] #primary > ytd-rich-grid-renderer,
- [page-subtype="playlist"][role="main"] ytd-item-section-renderer,
- [page-subtype="subscriptions"][role="main"] ytd-shelf-renderer,
- ytd-search[role="main"] ytd-section-list-renderer
- `;
- const buttonsToggleDestinationSelector = `#masthead #end`;
- const videosSelector = `
- [role="main"] ytd-grid-video-renderer,
- [role="main"] ytd-playlist-video-renderer,
- [role="main"] ytd-rich-item-renderer,
- [role="main"] ytd-video-renderer,
- [role="main"] .ytd-rich-section-renderer[is-shorts],
- [role="main"] ytd-reel-shelf-renderer,
- [role="main"] ytd-reel-item-renderer
- `;
- const unprocessedVideosSelectors = videosSelector
- .replace(/\n\s*/g, '')
- .split(',')
- .map(
- (selector) =>
- `${selector}:not(.mt-hidden) ${watchedVideosSelector}, ${selector}:not(.mt-hidden) ${upcomingVideosSelector}`
- )
- .join(',');
- const processedVideosSelectors = videosSelector
- .replace(/\n\s*/g, '')
- .split(',')
- .map(
- (selector) =>
- `${selector}.mt-hidden ${watchedVideosSelector}, ${selector}.mt-hidden ${upcomingVideosSelector}`
- )
- .join(',');
- // templates
- const toggleVideosButtonTemplate = `
- <tp-yt-paper-button class="ytd-subscribe-button-renderer mt-button mt-toggle-videos-button" />
- `;
- const toggleButtonsButtonTemplate = `
- <tp-yt-paper-button class="mt-button mt-toggle-buttons-button">
- <svg viewBox="0 0 24 24">
- <g>
- <path fill="#FFF" d="M20,7H4V6h16V7z M22,9v12H2V9H22z M15,15l-5-3v6L15,15z M17,3H7v1h10V3z"></path>
- </g>
- </svg>
- <tp-yt-paper-tooltip class="ytd-topbar-menu-button-renderer">
- ${i18n.toggleButtons}
- </tp-yt-paper-tooltip>
- </tp-yt-paper-button>
- `;
- const buttonsContainerTemplate = `
- <div class="mt-toggle-videos-container"></div>
- `;
- // style
- const baseStyle = `
- <style>
- .mt-toggle-videos-container {
- display: flex;
- justify-content: center;
- margin: 0 auto;
- }
- .mt-toggle-videos-container.hide-buttons {
- display: none;
- }
- .mt-button {
- border-radius: 20px !important;
- }
- .mt-toggle-videos-button {
- border-radius: 0 !important;
- margin: 0 !important;
- text-align: center;
- min-width: 112px;
- background: var(--yt-spec-additive-background) !important;
- }
- .mt-toggle-videos-button.on {
- background: var(--yt-spec-10-percent-layer) !important;
- }
- .mt-toggle-videos-button:first-child {
- border-radius: 20px 0 0 20px !important;
- }
- .mt-toggle-videos-button:last-child {
- border-radius: 0 20px 20px 0 !important;
- }
- .mt-toggle-buttons-button {
- background: transparent !important;
- height: 40px;
- margin: 0 8px 0 0;
- min-width: 40px;
- padding: 0 !important;
- }
- .mt-toggle-buttons-button:hover {
- background: var(--yt-spec-10-percent-layer) !important;
- }
- .mt-toggle-buttons-button svg {
- width: 24px;
- }
- .mt-hidden {
- display: none !important;
- }
- [page-subtype="channels"] .mt-toggle-videos-container {
- margin-top: 24px;
- }
- [page-subtype="channels"] ytd-rich-grid-renderer .mt-button.type,
- ytd-rich-grid-renderer[is-shorts-grid] .mt-button {
- background: transparent !important;
- opacity: .1;
- pointer-events: none;
- }
- [page-subtype="playlist"] .mt-toggle-videos-container {
- box-sizing: border-box;
- padding: 0 24px;
- }
- .ytd-search ytd-section-list-renderer .mt-toggle-videos-container {
- margin: 12px 0;
- }
- </style>
- `;
- initialize();
- })();