YouTube用低視聴回数・古い動画の非表示

YouTubeでおすすめに表示される再生回数の少ない動画・投稿日の古い動画を非表示にします。再生回数のしきい値はコード内で変更可能(デフォルト1000回)。古い動画の非表示は1年前の動画から設定可能(デフォルトは無効にしています)。チャンネル内の動画は無視するオプション付き。

目前為 2025-06-24 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouTube用低視聴回数・古い動画の非表示
// @namespace    http://tampermonkey.net/
// @description  YouTubeでおすすめに表示される再生回数の少ない動画・投稿日の古い動画を非表示にします。再生回数のしきい値はコード内で変更可能(デフォルト1000回)。古い動画の非表示は1年前の動画から設定可能(デフォルトは無効にしています)。チャンネル内の動画は無視するオプション付き。
// @author       sun
// @match        *://*.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        GM_registerMenuCommand
// @version      2.0
// ==/UserScript==

const VIEW_THRESHOLD = 1000; //再生回数のしきい値
const AGE_THRESHOLD_YEARS = 0; // y年以上前の動画を非表示(0=無効)
const DISABLE_ON_SUBSCRIPTIONS = true;
const OPTION_STORAGE_KEY = 'YT_LowViewFilter_Settings';
const VIDEO_SELECTORS = "ytd-rich-item-renderer, ytd-compact-video-renderer";
const VIEW_COUNT_SELECTOR = ".inline-metadata-item.style-scope.ytd-video-meta-block";

let menuRegistered = false;

function parseViewCount(text) {
    if (!text) return 0;
    const multipliers = { "K": 1e3, "M": 1e6, "万": 1e4, "億": 1e8 };
    let numText = text.replace(/[^0-9\.KM万億]/g, "");
    let unit = Object.keys(multipliers).find(u => numText.includes(u)) || "";
    numText = numText.replace(unit, "");
    return numText ? parseFloat(numText) * (multipliers[unit] || 1) : 0;
}

function isBadVideo(videoViews) {
    if (!videoViews) return false;
    const viewCount = parseViewCount(videoViews.innerText);
    return viewCount > 0 && viewCount < VIEW_THRESHOLD;
}

function isOldVideo(metadataItems) {
    if (AGE_THRESHOLD_YEARS === 0) return false; // 判定しない

    if (!metadataItems || metadataItems.length < 2) return false;
    const dateText = metadataItems[1].innerText;

    if (/([1-9][0-9]*)\s*(year|年)/i.test(dateText)) {
        const years = parseInt(RegExp.$1, 10);
        return years >= AGE_THRESHOLD_YEARS;
    }
    return false;
}

function isSubscriptionsPage() {
    return location.pathname.includes('/@') && location.pathname.includes('/videos');
}
function isLivePage() {
    return location.pathname.includes('/streams');
}

function getSettings() {
    return JSON.parse(localStorage.getItem(OPTION_STORAGE_KEY)) || { disableOnSubs: DISABLE_ON_SUBSCRIPTIONS };
}

function hideBadVideo(videoElement) {
    if (!videoElement || isLivePage()) return;
    const { disableOnSubs } = getSettings();
    if (disableOnSubs && isSubscriptionsPage()) return;

    const metadataItems = videoElement.querySelectorAll(VIEW_COUNT_SELECTOR);
    if (!metadataItems || metadataItems.length === 0) return;

    const isLowView = isBadVideo(metadataItems[0]);
    const isTooOld = isOldVideo(metadataItems);

    if (isLowView || isTooOld) {
        videoElement.style.display = "none";
    }
}

const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            hideBadVideo(entry.target);
            observer.unobserve(entry.target);
        }
    });
}, { rootMargin: "300px" });

function update() {
    if (isLivePage()) return;
    document.querySelectorAll(VIDEO_SELECTORS).forEach(video => {
        hideBadVideo(video);
        observer.observe(video);
    });
}

function initTampermonkeyMenu() {
    if (menuRegistered) return;
    menuRegistered = true;
    const { disableOnSubs } = getSettings();
    GM_registerMenuCommand(
        `チャンネル内の動画は無視する: ${disableOnSubs ? "ON" : "OFF"}`,
        () => {
            localStorage.setItem(OPTION_STORAGE_KEY, JSON.stringify({ disableOnSubs: !disableOnSubs }));
            window.location.reload();
        }
    );
}

window.addEventListener("load", () => {
    initTampermonkeyMenu();
    update();

    ["yt-navigate-finish", "yt-page-data-updated", "yt-action"].forEach(event => {
        window.addEventListener(event, () => setTimeout(update, 500));
    });

    const mutationObserver = new MutationObserver(mutations => {
        if (isLivePage()) return;
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType !== 1) return;
                if (node.matches?.(VIDEO_SELECTORS)) {
                    hideBadVideo(node);
                    observer.observe(node);
                } else if (node.querySelectorAll) {
                    node.querySelectorAll(VIDEO_SELECTORS).forEach(video => {
                        hideBadVideo(video);
                        observer.observe(video);
                    });
                }
            });
        });
    });
    mutationObserver.observe(document.body, { childList: true, subtree: true });
});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址