Sleazy Fork is available in English.
Hides inaccurate counts for Character and Copyright tags, and shows exact and accurate clean counts which are aligned with the user's blacklist. Good for getting AI slop-less statistics.
// ==UserScript==
// @name Rule34 Blacklist Tag Count Fix
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Hides inaccurate counts for Character and Copyright tags, and shows exact and accurate clean counts which are aligned with the user's blacklist. Good for getting AI slop-less statistics.
// @author rushia816
// @match *://rule34.xxx/*
// @match *://rule34.xxx/index.php*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ==========================================
// CONFIGURATION
// ==========================================
const HIDE_UNSEARCHED = true;
const CACHE_KEY = 'rule34_clean_counts';
const INIT_DELAY_MS = 100;
const CACHE_TTL_MS = 600000; // 10 Minutes
const CORRECTION_INTERVAL_MS = 10;
// AI Autocomplete Filter Configuration
const FILTER_AI_AUTOCOMPLETE = true; // Set to false to disable
const AI_KEYWORDS = [
'ai_generated', 'ai_art', 'midjourney', 'stable_diffusion',
'dalle', 'novelai', 'waifu_diffusion', 'deepdream', 'gan',
'neural_network', 'machine_learning', 'synthetic', 'generated_by_ai'
];
// ==========================================
const TARGET_TYPES = ['tag-type-character', 'tag-type-copyright'];
const processingQueue = new Set();
let isProcessing = false;
let acInterval = null;
let calculatedCounts = {};
function loadCache() {
try {
const stored = localStorage.getItem(CACHE_KEY);
if (stored) {
calculatedCounts = JSON.parse(stored);
} else {
calculatedCounts = {};
}
} catch (e) {
calculatedCounts = {};
}
}
function saveCache() {
try {
localStorage.setItem(CACHE_KEY, JSON.stringify(calculatedCounts));
} catch (e) {}
}
// Initialize cache on load
loadCache();
// ==========================================
// INSTANT HIDE CSS (Sidebar Only)
// ==========================================
const style = document.createElement('style');
style.innerHTML = `
li.tag-type-character .tag-count,
li.tag-type-copyright .tag-count {
display: none !important;
visibility: hidden !important;
}
li.tag-type-character .tag-count::after,
li.tag-type-copyright .tag-count::after {
content: "...";
color: #888;
font-style: italic;
font-weight: normal;
visibility: visible !important;
}
li.tag-type-character .tag-count.revealed,
li.tag-type-copyright .tag-count.revealed {
display: inline !important;
visibility: visible !important;
}
li.tag-type-character .tag-count.revealed::after,
li.tag-type-copyright .tag-count.revealed::after {
content: "" !important;
visibility: hidden !important;
}
`;
if (document.head) {
document.head.appendChild(style);
} else {
document.addEventListener('DOMContentLoaded', () => {
if (document.head) document.head.appendChild(style);
});
}
// ==========================================
// HELPER FUNCTIONS
// ==========================================
function revealCount(element, number) {
if (!element) return;
element.classList.add('revealed');
element.textContent = number;
element.style.color = "";
element.style.fontStyle = "";
element.style.fontWeight = "";
}
function isAiTag(tagName) {
if (!FILTER_AI_AUTOCOMPLETE) return false;
const lowerName = tagName.toLowerCase();
return AI_KEYWORDS.some(keyword => lowerName.includes(keyword));
}
// ==========================================
// BRUTE-FORCE AUTOCOMPLETE CORRECTION
// ==========================================
function correctAutocomplete() {
const ul = document.querySelector('.awesomplete > ul');
if (!ul || ul.children.length === 0) return;
const items = ul.querySelectorAll('li');
items.forEach(item => {
if (!TARGET_TYPES.some(type => item.classList.contains(type))) return;
const text = item.textContent;
// AI Filter
if (FILTER_AI_AUTOCOMPLETE) {
const matchName = text.match(/^(.+)\s*\(/);
if (matchName) {
const tagName = matchName[1].trim();
if (isAiTag(tagName)) {
item.style.display = 'none';
return;
}
}
}
const numMatch = text.match(/\((\d+)\)$/);
const maskedMatch = text.match(/\(\.\.\.\)$/);
if (numMatch) {
const dirtyCount = parseInt(numMatch[1], 10);
const matchName = text.match(/^(.+)\s*\(\d+\)$/);
if (matchName) {
const tagName = matchName[1].trim();
const cachedData = calculatedCounts[tagName];
const now = Date.now();
const isFresh = cachedData && cachedData.count && (now - cachedData.timestamp < CACHE_TTL_MS);
if (cachedData && cachedData.count) {
if (isFresh && cachedData.count === dirtyCount) {
item.textContent = `${tagName} (...)`;
} else {
item.textContent = `${tagName} (${cachedData.count})`;
}
} else {
item.textContent = `${tagName} (...)`;
}
}
} else if (maskedMatch) {
const matchName = text.match(/^(.+)\s*\(\.\.\.\)$/);
if (matchName) {
const tagName = matchName[1].trim();
const cachedData = calculatedCounts[tagName];
if (cachedData && cachedData.count) {
item.textContent = `${tagName} (${cachedData.count})`;
}
}
}
});
}
// ==========================================
// SIDEBAR PROCESSING
// ==========================================
function getActiveTagElements() {
const tagItems = document.querySelectorAll('li.tag-type-character, li.tag-type-copyright');
if (tagItems.length === 0) return {};
const urlParams = new URLSearchParams(window.location.search);
const isTagsPage = urlParams.get('page') === 'tags';
const isListPage = urlParams.get('page') === 'post' && urlParams.get('s') === 'list';
// FIXED: Replaced optional chaining (?.) with standard check for compatibility
const searchInput = document.querySelector('input[name="tags"]');
const currentTagsStr = searchInput ? searchInput.value : '';
const currentTags = currentTagsStr.split('+').map(t => t.trim());
const activeTagMap = {};
tagItems.forEach(item => {
const tagLink = item.querySelector('a[href*="tags="]');
if (!tagLink) return;
const href = tagLink.getAttribute('href');
const tagParam = href.split('tags=')[1].split('&')[0];
let itemTagName = decodeURIComponent(tagParam).replace(/\+/g, ' ');
const isActive = currentTags.includes(itemTagName);
const countSpan = item.querySelector('.tag-count');
if (!countSpan) return;
// 1. Check Cache (with Stale Check)
const cachedData = calculatedCounts[itemTagName];
const now = Date.now();
const isStale = cachedData && cachedData.count && (now - cachedData.timestamp >= CACHE_TTL_MS);
const hasValidCache = cachedData && cachedData.count;
// 1. Check Cache
if (hasValidCache) {
revealCount(countSpan, cachedData.count);
if (isStale && isActive) {
if (!processingQueue.has(itemTagName)) {
calculateTagCount(itemTagName, (tag, count) => {
updateDisplay(tag, count);
});
}
}
return;
}
// 2. Tags Page (No pagination available)
if (isTagsPage) return;
// 3. Unsearched Tags
if (!isActive) {
if (HIDE_UNSEARCHED) return;
return;
}
activeTagMap[itemTagName] = { countSpan, parentItem: item };
});
return activeTagMap;
}
function processSidebarTags() {
if (isProcessing) return;
isProcessing = true;
setTimeout(() => { isProcessing = false; }, 10);
getActiveTagElements();
}
// ==========================================
// OBSERVERS
// ==========================================
function setupObservers() {
if (!document.body) return;
const observer = new MutationObserver((mutations) => {
if (isProcessing) return;
isProcessing = true;
setTimeout(() => { isProcessing = false; }, 50);
processSidebarTags();
});
observer.observe(document.body, { childList: true, subtree: true });
const searchInput = document.querySelector('input[name="tags"]');
if (searchInput) {
searchInput.addEventListener('input', () => {
processSidebarTags();
correctAutocomplete();
});
}
}
// ==========================================
// CALCULATION LOGIC
// ==========================================
/**
* Calculates the exact clean count by loading the last page in a hidden iframe
*/
function calculateTagCount(tagName, callback) {
if (processingQueue.has(tagName)) return;
processingQueue.add(tagName);
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('page') !== 'post' || urlParams.get('s') !== 'list') {
processingQueue.delete(tagName);
return;
}
const lastPageLink = document.querySelector('a[alt="last page"]');
if (!lastPageLink) {
const currentPosts = document.querySelectorAll('.thumb, .post-list .post-item').length;
callback(tagName, currentPosts);
processingQueue.delete(tagName);
return;
}
const lastHref = lastPageLink.getAttribute('href');
const urlParamsLast = new URLSearchParams(lastHref.split('?')[1]);
const lastPid = parseInt(urlParamsLast.get('pid'), 10);
if (isNaN(lastPid)) {
processingQueue.delete(tagName);
return;
}
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.onload = function() {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const lastPagePosts = iframeDoc.querySelectorAll('.thumb, .post-list .post-item').length;
const totalClean = lastPid + lastPagePosts;
callback(tagName, totalClean);
} catch (e) {
console.error("[Calc] Iframe error:", e);
} finally {
if (document.body.contains(iframe)) document.body.removeChild(iframe);
processingQueue.delete(tagName);
}
};
iframe.onerror = function() {
processingQueue.delete(tagName);
};
iframe.src = lastHref;
}
/**
* Updates the display (sidebar and autocomplete) with the calculated count
* Saves the count with a timestamp to the cache
*/
function updateDisplay(tagName, cleanCount) {
// Save with timestamp
calculatedCounts[tagName] = { count: cleanCount, timestamp: Date.now() }; saveCache();
// Reveal Sidebar (if still active)
const activeTags = getActiveTagElements();
if (activeTags[tagName]) {
revealCount(activeTags[tagName].countSpan, cleanCount);
} else {
const tagItems = document.querySelectorAll('li.tag-type-character, li.tag-type-copyright');
tagItems.forEach(item => {
const tagLink = item.querySelector('a[href*="tags="]');
if (!tagLink) return;
const href = tagLink.getAttribute('href');
const tagParam = href.split('tags=')[1].split('&')[0];
let itemTagName = decodeURIComponent(tagParam).replace(/\+/g, ' ');
if (itemTagName === tagName) {
const countSpan = item.querySelector('.tag-count');
if (countSpan) revealCount(countSpan, cleanCount);
}
});
}
correctAutocomplete();
}
/**
* Triggers calculation for all active tags in the current search
*/
function processActiveTags() {
const urlParams = new URLSearchParams(window.location.search);
if (!tagsParam) return;
if (urlParams.get('page') !== 'post' || urlParams.get('s') !== 'list') return;
const activeTagMap = getActiveTagElements();
Object.keys(activeTagMap).forEach(tagName => {
calculateTagCount(tagName, (tag, count) => {
updateDisplay(tag, count);
});
});
}
// ==========================================
// INITIALIZATION
// ==========================================
// Start the script with minimal delay
setTimeout(() => {
setupObservers();
processSidebarTags();
processActiveTags();
acInterval = setInterval(correctAutocomplete, CORRECTION_INTERVAL_MS);
setTimeout(() => {
processSidebarTags();
processActiveTags();
}, 500);
}, INIT_DELAY_MS);
// Navigation Handler: Re-run logic when URL changes
let lastUrl = location.href;
const navObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
loadCache();
setTimeout(() => {
processSidebarTags();
correctAutocomplete();
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('page') === 'post' && urlParams.get('s') === 'list') {
processActiveTags();
}
}, 100);
}
});
if (document.body) {
navObserver.observe(document.body, { subtree: true, childList: true });
}
})();