Greasy Fork

Greasy Fork is available in English.

Bangumi 隐藏NSFW条目

隐藏 Bangumi 上的 NSFW 条目

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Bangumi 隐藏NSFW条目
// @version      3.3
// @description  隐藏 Bangumi 上的 NSFW 条目
// @author       墨云
// @match        https://bangumi.tv/*
// @match        https://chii.in/*
// @match        https://bgm.tv/*
// @grant        none
// @namespace    http://greasyfork.icu/users/1354622
// ==/UserScript==

(function() {
    'use strict';

    if (window.location.pathname.startsWith('/subject/')) {
        return;
    }

    const SETTING_KEY = 'bangumi_hide_nsfw_mode';
    const CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000;

    async function isSubjectChecked(subjectId) {
        const cacheKey = `nsfw_cache_${subjectId}`;
        const cachedData = localStorage.getItem(cacheKey);

        if (cachedData) {
            try {
                const data = JSON.parse(cachedData);
                if (Date.now() < data.timestamp + CACHE_EXPIRY) {
                    return data.value;
                }
            } catch (e) {
                localStorage.removeItem(cacheKey);
            }
        }

        try {
            const response = await fetch(`/subject/${subjectId}`);
            const text = await response.text();

            const parser = new DOMParser();
            const doc = parser.parseFromString(text, 'text/html');

            const isChecked = !!doc.querySelector('a[href*="/tag/R18"]');

            if (!isChecked) {
                console.warn(`R18 tag not found on page for subjectId: ${subjectId}`);
            }

            const dataToCache = {
                value: isChecked,
                timestamp: Date.now()
            };
            localStorage.setItem(cacheKey, JSON.stringify(dataToCache));
            return isChecked;
        } catch (error) {
            console.error('Failed to fetch subject detail:', error, 'for subjectId:', subjectId);
            const dataToCache = {
                value: false,
                timestamp: Date.now()
            };
            localStorage.setItem(cacheKey, JSON.stringify(dataToCache));
            return false;
        }
    }

    function getSubjectIdFromLink(link) {
        const url = link.href;
        const match = url.match(/\/subject\/(\d+)/);
        return match ? match[1] : null;
    }

    async function applyMode(mode, rootElement = document) {
        const links = rootElement.querySelectorAll('a[href*="/subject/"]');
        const promises = [];

        for (const link of links) {
            const subjectId = getSubjectIdFromLink(link);
            if (subjectId) {
                promises.push(isSubjectChecked(subjectId).then(isChecked => {
                    if (isChecked) {

                        let containerToHide = link.closest('.mainItem, .subject_grid_list > li, .item, .subject-item, .clearit, .info_item, li.line_list, li.clearit, .home_subject_list > li, .list > li, .grid > li');
                        if (!containerToHide) {
                             containerToHide = link.closest('[id^="item_"]');
                        }
                        if (!containerToHide) {
                            containerToHide = link.closest('li');
                        }
                        
                        if (!containerToHide) {
                            console.warn('Could not find a container to hide for the link:', link.href);
                        }

                        if (containerToHide) {
                            containerToHide.style.display = (mode === 'hide') ? 'none' : '';
                        }
                    }
                }));
            }
        }
        await Promise.all(promises);
    }

    function addNSFWSetting() {
        if (typeof chiiLib === 'undefined' || typeof chiiLib.ukagaka === 'undefined') {
            return;
        }
        
        chiiLib.ukagaka.addGeneralConfig({
            title: 'NSFW',
            name: 'nsfwMode',
            type: 'radio',
            defaultValue: 'off',
            getCurrentValue: function() { return $.cookie(SETTING_KEY) || 'off'; },
            onChange: function(value) {
                $.cookie(SETTING_KEY, value, {expires: 30, path: '/'});
                applyMode(value);
            },
            options: [
                { value: 'off', label: '显示' },
                { value: 'hide', label: '隐藏' }
            ]
        });
    }

    async function init() {

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                addNSFWSetting();
                const currentMode = $.cookie(SETTING_KEY) || 'off';
                applyMode(currentMode);
            });
        } else {
            addNSFWSetting();
            const currentMode = $.cookie(SETTING_KEY) || 'off';
            applyMode(currentMode);
        }

        const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach((node) => {

                        if (node.nodeType === Node.ELEMENT_NODE && node.querySelector('a[href*="/subject/"]')) {
                            console.log('Detected new entries being loaded. Applying settings...');

                            applyMode($.cookie(SETTING_KEY) || 'off', node);
                        }
                    });
                }
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    init();
})();