Sleazy Fork is available in English.
Hides watched videos and excludes videos by title phrases on Eporner.
// ==UserScript==
// @name Eporner Zippser Filter
// @namespace http://tampermonkey.net/
// @version 18.0
// @description Hides watched videos and excludes videos by title phrases on Eporner.
// @author https://github.com/zippser
// @match https://www.eporner.com/*
// @grant GM_addStyle
// @license GPL-3.0
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// --- CONFIGURATION ---
// --- Enter keywords separated by comma to hide videos ---
const KEYWORDS_TO_BAN = ['fart', 'trans', 'findom',
'foot'];
const videoContainerSelector = '.mb';
const watchedIndicatorSelector = 'i[title^="Watched on"]';
// Keeping the specific wrapper, but prioritizing body scan if it fails
const feedWrapperSelector = '#vidresults';
const TITLE_LINK_SELECTOR = '.mbunder .mbtit a';
const WATCHED_MARKER_CLASS = 'ai-hidden-watched';
const KEYWORD_MARKER_CLASS = 'ai-keyword-banned';
let isWatchedHidden = true;
let styleElement;
let mainObserver;
// --- Style Injection Setup ---
function setupStyleSheet() {
if (styleElement) return;
styleElement = document.createElement('style');
styleElement.id = 'ai-keyword-ban-styles-v17';
styleElement.textContent = `
.${KEYWORD_MARKER_CLASS},
.${WATCHED_MARKER_CLASS} {
display: none !important;
}
`;
document.head.appendChild(styleElement);
}
// --- Utility Functions ---
function shouldHideDueToKeyword(container) {
if (KEYWORDS_TO_BAN.length === 0) return false;
const titleLink = container.querySelector(TITLE_LINK_SELECTOR);
if (titleLink && titleLink.innerText) {
const textToCheck = titleLink.innerText.toLowerCase();
for (const keyword of KEYWORDS_TO_BAN) {
if (textToCheck.includes(keyword)) {
return true;
}
}
}
return false;
}
// --- UI Functions (Simplified for this test run) ---
function createToggleButton() {
if (document.getElementById('ai-toggle-videos-btn')) return;
const button = document.createElement('button');
button.id = 'ai-toggle-videos-btn';
button.style.cssText = `position: fixed !important; top: 5px !important; right: 280px !important; z-index: 2147483647 !important; padding: 12px 20px !important; font-size: 16px !important; font-weight: bold !important; background-color: #28a745 !important; color: white !important; border: 1px solid #333 !important; border-radius: 8px !important; cursor: pointer !important; box-shadow: 0 8px 16px rgba(0,0,0,0.5) !important; transition: all 0.3s ease !important;`;
button.onclick = toggleVideoVisibility;
document.documentElement.appendChild(button);
button.onmouseover = () => button.style.backgroundColor = '#218838';
button.onmouseout = () => button.style.backgroundColor = '#28a745';
button.textContent = isWatchedHidden ? 'Show' : 'Hide';
}
function toggleVideoVisibility() {
isWatchedHidden = !isWatchedHidden;
processDOM(document.body); // Process the entire body when toggling
const button = document.getElementById('ai-toggle-videos-btn');
if (button) button.textContent = isWatchedHidden ? 'Show' : 'Hide';
}
// --- Core Processing Logic ---
function processElement(container) {
if (!container || !container.classList.contains('mb')) return;
const isWatched = container.querySelector(watchedIndicatorSelector);
const isBanned = shouldHideDueToKeyword(container);
let shouldHide = false;
// 1. Keyword Enforcement
if (isBanned) {
container.classList.add(KEYWORD_MARKER_CLASS);
container.classList.remove(WATCHED_MARKER_CLASS);
shouldHide = true;
} else {
container.classList.remove(KEYWORD_MARKER_CLASS);
// 2. Watched Enforcement
if (isWatched && isWatchedHidden) {
container.classList.add(WATCHED_MARKER_CLASS);
shouldHide = true;
} else {
container.classList.remove(WATCHED_MARKER_CLASS);
}
}
// 3. Apply the display property based on the determined visibility
if (shouldHide) {
container.style.setProperty('display', 'none', 'important');
} else {
container.style.removeProperty('display');
}
}
function processDOM(targetNode) {
// If a specific target node is provided (like document.body on initial load), use it.
// Otherwise, try the specific wrapper, falling back to the entire body if the wrapper fails or doesn't exist.
const rootNode = targetNode || document.querySelector(feedWrapperSelector) || document.body;
if (!rootNode) return;
// Process all matching containers found within the designated root node
rootNode.querySelectorAll(videoContainerSelector).forEach(processElement);
}
// --- Main Initialization ---
function initializeScript() {
setupStyleSheet();
createToggleButton();
// Initial comprehensive scan of the entire body, which covers all page types (home, tag, search)
processDOM(document.body);
// Set up a MutationObserver to monitor for changes in the DOM that could introduce new video containers
if (!mainObserver) {
mainObserver = new MutationObserver((mutationsList) => {
let needsProcessing = false;
for (const mutation of mutationsList) {
// If any nodes were added or removed that could affect the feed area
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
needsProcessing = true;
break;
}
}
if (needsProcessing) {
// Re-process the entire body structure to catch newly injected items
processDOM(document.body);
}
});
// Observe the entire body for structural changes
const config = { childList: true, subtree: true };
mainObserver.observe(document.body, config);
}
}
if (document.readyState === "loading") {
document.addEventListener('DOMContentLoaded', initializeScript);
} else {
initializeScript();
}
})();