☰

Kemono Party Blacklist

Blacklists posts by Creator ID

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램을 먼저 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Kemono Party Blacklist
// @namespace    https://MeusArtis.ca
// @version      2.3
// @author       Meus Artis
// @description  Blacklists posts by Creator ID
// @icon         https://www.google.com/s2/favicons?domain=kemono.cr
// @match        https://coomer.st/*/user/*
// @match        https://coomer.st/artists*
// @match        https://coomer.st/account/favorites/*
// @match        https://coomer.st/posts*
// @match        https://kemono.cr/*/user/*
// @match        https://kemono.cr/artists*
// @match        https://kemono.cr/account/favorites/*
// @match        https://kemono.cr/dms*
// @match        https://kemono.cr/posts*
// @match        https://pawchive.pw/*/user/*
// @match        https://pawchive.pw/artists*
// @match        https://pawchive.pw/favorites*
// @match        https://pawchive.pw/dms*
// @match        https://pawchive.pw/posts*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @license      CC BY-NC-SA 4.0
// @run-at       document-end
// ==/UserScript==
(function () {
    const styleSheet = document.createElement('style');
    styleSheet.textContent = `.creator__blacklist{color:#ddd;font-weight:700;text-shadow:#000 0 0 3px,#000 -1px -1px 0px,#000 1px 1px 0;background-color:transparent;border:transparent}.user-header__blacklist{box-sizing:border-box;font-weight:700;color:#fff;text-shadow:#000 0 0 3px,#000 -1px -1px 0px,#000 1px 1px 0;background-color:transparent;border:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}`;
    document.head.appendChild(styleSheet);
    const ToBlacklist = ['article[data-user]', 'a[data-id]', 'article.dm-card header a[href^="/patreon/user/"]', 'a.user-card[href^="/patreon/user/"]', 'a.user-card[href^="/fanbox/user/"]'].join(',');
    if (GM_getValue('Blacklist') === undefined) {
        alert('Blacklist does not exist, creating a new one'); // Nobody still has the localstorage blacklist... right?
        GM_setValue('Blacklist', '');
    }
    function getBlacklistSet() { // add import/export to PawBrowser after switching to new format
        const Blacklist = GM_getValue('Blacklist', '');
        if (Array.isArray(Blacklist)) return new Set(Blacklist);
        if (!Blacklist) return new Set();
        return new Set(String(Blacklist).split(',').filter(Boolean));
    }
    function saveBlacklist(set) {
        GM_setValue('Blacklist', Array.from(set).join(','));
    }
    function HREFID(href) {
        if (!href) return null;
        const match = href.match(/\/(?:\w+)\/user\/([^/?#]+)/);
        return match ? match[1] : null;
    }
    let blacklistSet = getBlacklistSet();
    function applyBlacklist() {
        blacklistSet = getBlacklistSet();
        if (blacklistSet.size === 0) return;
        const nodes = document.querySelectorAll(ToBlacklist);
        for (const e of nodes) {
            const DataUser = e.getAttribute('data-user');
            if (DataUser && blacklistSet.has(DataUser)) {
                e.style.display = 'none';
            }
            const DataID = e.getAttribute('data-id');
            if (DataID && blacklistSet.has(DataID)) {
                e.style.display = 'none';
            }
            const href = e.getAttribute('href');
            if (!href) continue;
            const ID = HREFID(href);
            if (!ID || !blacklistSet.has(ID)) continue;
            if (e.classList.contains('user-card')) {
                e.style.display = 'none';
            }
            if (e.matches('article.dm-card header a')) {
                const Card = e.closest('article');
                if (Card) Card.style.display = 'none';
            }
        }
    }
    function setupBlacklistButtons() {
        const ButtonArea = document.querySelector('.post__actions') ?? document.querySelector('.user-header__actions');
        if (!ButtonArea) return;
        let Page = location.pathname
        if (Page.match(/^\/\w+\/user\/\w+(\/\w+)?$/)) { // Debugging, see if the script actually fucking loads
           console.log('Creator Page');
        } else if (Page.match(/^\/\w+\/user\/\w+\/post\/(\w|-)+(\/revision\/\d+)?$/)) {
           console.log('Post Page');
        } else if (Page.match(/^\/discord\/server\/\d+(\/d+)?$/)) {
           console.log('Discord Server');
        }
        const match = Page.match(/^\/(\w+)\/user\/(\w+)(\/.+)?$/) ?? Page.match(/^\/(discord)\/server\/(\d+)(\/.+)?$/);
        if (!match) return;
        const [, Platform, ID] = match;
        const isBlacklisted = blacklistSet.has(ID);
        const BlacklistButton = document.createElement('button');
        BlacklistButton.type = 'button';
        BlacklistButton.className = 'creator__blacklist';
        BlacklistButton.innerHTML = isBlacklisted ? '<span class="creator__blacklist-icon">⛒</span><span>Blacklisted</span>' : '<span class="creator__blacklist-icon">⛔</span><span>Blacklist</span>';
        BlacklistButton.addEventListener('click', () => {
            blacklistSet = getBlacklistSet();
            if (blacklistSet.has(ID)) {
                blacklistSet.delete(ID);
                console.log('Creator Unblacklisted');
            } else {
                blacklistSet.add(ID);
                console.log('Creator Blacklisted');
            }
            saveBlacklist(blacklistSet);
            history.back();
        });
        console.log(`${Platform}:${ID}`); // Debugging, will use to prevent ID collisions in v3 (currently only a few dozen in nearly 100K creators)
        ButtonArea.appendChild(BlacklistButton);
    }
    function observeUrlChange(callback) { // Replacement for this fixes the needing-to-refresh problem reported by Naxeti, and reduces loading time by 1.3 seconds per 10K creators blacklisted (40K+ tested, still looking for edge cases)
        let lastUrl = location.href;
        new MutationObserver(() => {
            const currentUrl = location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                callback();
            }
        }).observe(document.body, { childList: true, subtree: true });
    }
    function initializeScript() {
        setTimeout(() => {
            applyBlacklist();
            setupBlacklistButtons();
        }, 100); // Button has inconsistent colours/positioning if loaded instantly, sacrificed initial load on first page of artist search to speed up everywhere else
    }
    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', initializeScript, { once: true });
    } else {
        initializeScript();
    }
    function donate() {
        window.open('https://ko-fi.com/meusartis', '_blank');
    }
    GM_registerMenuCommand('Donate', donate);
    observeUrlChange(initializeScript);
})();