Blacklists posts by Creator ID
// ==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);
})();