Eporner Zippser Filter

Hides watched videos and excludes videos by title phrases on Eporner.

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

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();
    }

})();