🎉 欢迎访问GreasyFork.Org 镜像站!本镜像站由公众号【爱吃馍】搭建,用于分享脚本。联系邮箱📮

Greasy fork 爱吃馍镜像

Tidy Tube

A lightweight script to declutter YouTube by hiding videos for members and videos under a certain view count.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

// ==UserScript==
// @name         Tidy Tube
// @namespace    https://github.com/jaykeny/
// @version      1.4.0
// @description  A lightweight script to declutter YouTube by hiding videos for members and videos under a certain view count.
// @author       JayKeny
// @match        https://www.youtube.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

// Settings
const SETTINGS = {
  hideMembersOnly: true,  // hide member videos
  minViews: 1000,         // hide normal videos under this view count
  hideLiveUnder: 100000,  // hide live streams with fewer viewers than this
  hideOlderThanMonths: 1, // hide videos older than this number of months
  hideWatched: true,      // hide videos with watched progress bar
  hidePlaylists: true,    // Hide playlist blocks in the feed
  hideFeedNudges: true,   // Hide feed nudges like "Looking for something different?"
};

// Translations
const TRANSLATIONS = {
  en: {
    membersOnly: ["Members only", "Exclusive Access"],
    views: /\bviews?\b/i,
    noViews: /no views?/i,
    watching: /([\d,.]+)\s*(K|M)?\s*watching/i,
    ago: /(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago/i,
    units: {
      second: "second",
      minute: "minute",
      hour: "hour",
      day: "day",
      week: "week",
      month: "month",
      year: "year"
    }
  },
  fr: {
    membersOnly: ["Réservé aux membres", "Accès exclusif"],
    views: /\b(vues?)\b/i,
    noViews: /aucune? vue/i,
    watching: /([\d,.]+)\s*(K|M)?\s*(personnes)?\s*(regardent|en direct)/i,
    ago: /il y a\s+(\d+)\s+(seconde|minute|heure|jour|semaine|mois|an|année)s?/i,
    units: {
      seconde: "second",
      minute: "minute",
      heure: "hour",
      jour: "day",
      semaine: "week",
      mois: "month",
      an: "year",
      année: "year",
      ans: "year",
      années: "year"
    }
  }
};

const LANGUAGE = document.documentElement.lang.slice(0, 2) || "en";
const TRANSLATION = TRANSLATIONS[LANGUAGE] || TRANSLATIONS.en;


// Helper function to hide element + parent if applicable
function hideElement(el) {
  const parent = el.closest("ytd-rich-item-renderer") || el;
  parent.style.display = "none";
  parent.style.height = "0";
  parent.style.margin = "0";
  parent.style.padding = "0";
}

// Helper function to check video age
function isOlderThanMonths(text, months) {
  const match = text.match(TRANSLATION.ago);
  if (!match) return false;

  const value = parseInt(match[1], 10);
  const rawUnit = match[2].toLowerCase();
  const unit = TRANSLATION.units[rawUnit] || rawUnit;

  if (unit === "month" && value >= months) return true;
  if (unit === "year") return true;
  if (unit === "week" && value / 4 >= months) return true;
  if (unit === "day" && value / 30 >= months) return true;
  if (unit === "hour" && value / (24 * 30) >= months) return true;
  if (unit === "minute" && value / (60 * 24 * 30) >= months) return true;
  if (unit === "second" && value / (60 * 60 * 24 * 30) >= months) return true;

  return false;
}

// Get view count from the new YouTube layout
function getViewCount(el) {
  const viewText = Array.from(el.querySelectorAll('.yt-core-attributed-string, span[role="text"]'))
    .map(e => e.textContent.trim())
    .find(t => TRANSLATION.views.test(t));

  if (!viewText) return null;

  // Handle "No views"
  if (TRANSLATION.noViews.test(viewText)) return 0;

  // Match number before "views"
  const match = viewText.match(/([\d,.]+)\s*(K|M)?/i);
  if (!match) return null;

  let num = parseFloat(match[1].replace(/,/g, ""));
  const unit = match[2];
  if (unit === "K") num *= 1000;
  else if (unit === "M") num *= 1000000;

  return num;
}

// Checks if video is watched
function isWatched(el) {
  const progressBars = el.querySelectorAll("div.ytThumbnailOverlayProgressBarHostWatchedProgressBar");
  for (let bar of progressBars) {
    const segment = bar.querySelector("div");
    if (segment && parseFloat(segment.style.width) > 0) {
      return true;
    }
  }
  return false;
}

// Core filter logic
function filterVideos() {
  const isChannelVideosPage = location.pathname.includes("/videos");

  // Sections
  document.querySelectorAll("ytd-rich-section-renderer").forEach(function (section) {
    hideElement(section);
  });

  // Video pages
  document.querySelectorAll("yt-lockup-view-model").forEach(function (section) {
    const text = section.textContent;

    // Hide members-only videos
    if (SETTINGS.hideMembersOnly) {
      const isMembersOnly = Array.from(section.querySelectorAll('.yt-badge-shape__text'))
        .some(b => b.textContent.trim().toLowerCase().includes("members only"));

      if (isMembersOnly || text.includes("Members only")) {
        hideElement(section);
        return;
      }
    }

    // Hide videos under minViews
    const views = getViewCount(section.querySelector('.yt-lockup-view-model__metadata') || section);
    if (views !== null && views < SETTINGS.minViews) {
      hideElement(section);
      return;
    }

    // Hide live videos under hideLiveUnder
    const liveMatch = text.match(/([\d,.]+)\s*(K|M)?\s*watching/i);
    if (liveMatch) {
      let num = parseFloat(liveMatch[1].replace(/,/g, ""));
      const unit = liveMatch[2];
      if (unit === "K") num *= 1000;
      else if (unit === "M") num *= 1000000;
      if (num < SETTINGS.hideLiveUnder) {
        hideElement(section);
        return;
      }
    }

    // Hide older videos
    if (SETTINGS.hideOlderThanMonths && isOlderThanMonths(text, SETTINGS.hideOlderThanMonths)) {
      hideElement(section);
      return;
    }

    if (SETTINGS.hideWatched && isWatched(section)) {
      hideElement(section);
      return;
    }
  });

  // Home
  document.querySelectorAll("ytd-rich-item-renderer").forEach(function (el) {
    const text = el.textContent;

    // Channel Videos tab: only hide members-only content
    if (isChannelVideosPage && SETTINGS.hideMembersOnly) {
      if (TRANSLATION.membersOnly.some(term => text.toLowerCase().includes(term.toLowerCase())) || el.querySelector(".sponsorThumbnailLabelVisible")) {
        hideElement(el);
      }
      return;
    }

    // Hide playlists
    if (SETTINGS.hidePlaylists) {
      const hasPlaylistLink = Array.from(el.querySelectorAll('a')).some(a => a.href.includes('?list=') || a.href.includes('&list='));
      
      if (hasPlaylistLink) {
        hideElement(el);
        return;
      }
    }

    // Hide "feed nudge" suggestion sections like "Looking for something different?"
    if (SETTINGS.hideFeedNudges) {
      if (el.querySelector('ytd-feed-nudge-renderer')) {
        hideElement(el);
        return;
      }
    }

    // Hide members-only videos
    if (SETTINGS.hideMembersOnly) {
      if (TRANSLATION.membersOnly.some(term => text.toLowerCase().includes(term.toLowerCase())) || el.querySelector(".sponsorThumbnailLabelVisible")) {
        hideElement(el);
        return;
      }
    }

    // Hide videos under minViews
    const views = getViewCount(el);

    if (views !== null && views < SETTINGS.minViews) {
      hideElement(el);
      return;
    }

    // Hide live videos under hideLiveUnder
    const liveMatch = text.match(TRANSLATION.watching);

    if (liveMatch) {
      let num = parseFloat(liveMatch[1].replace(/,/g, ""));
      const unit = liveMatch[2];
      if (unit === "K") num *= 1000;
      else if (unit === "M") num *= 1000000;
      if (num < SETTINGS.hideLiveUnder) {
        hideElement(el);
        return;
      }
    }

    // Hide videos older than specified months
    if (SETTINGS.hideOlderThanMonths && isOlderThanMonths(text, SETTINGS.hideOlderThanMonths)) {
      hideElement(el);
      return;
    }

    // Hide watched videos
    if (SETTINGS.hideWatched && isWatched(el)) {
      hideElement(el);
      return;
    }
  });
}

// Run once and then watch for DOM changes
filterVideos();

const observer = new MutationObserver((mutations) => {
  for (let mutation of mutations) {
    if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
      filterVideos();
    }
  }
});
observer.observe(document.body, { childList: true, subtree: true });