Stash Check

Combines Kemono Browser and Leak Finder Overlay with verified archive checkers and query engines from the FMHY NSFW Leaks list.

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

// ==UserScript==
// @name         Stash Check
// @namespace    stash-check
// @version      1.0
// @description  Combines Kemono Browser and Leak Finder Overlay with verified archive checkers and query engines from the FMHY NSFW Leaks list.
// @author       brainbaker
// @license      MIT
// @match        *://*.patreon.com/*
// @match        *://*.fanbox.cc/*
// @match        *://*.pixiv.net/*
// @match        *://*.discord.com/*
// @match        *://*.fantia.jp/*
// @match        *://*.boosty.to/*
// @match        *://*.dlsite.com/*
// @match        *://*.gumroad.com/*
// @match        *://*.subscribestar.com/*
// @match        *://*.subscribestar.adult/*
// @match        *://*.onlyfans.com/*
// @match        *://*.fansly.com/*
// @match        *://*.candfans.com/*
// @match        *://*.fantrie.com/*
// @connect      kemono.cr
// @connect      coomer.st
// @connect      pawchive.st
// @connect      pawchive.pw
// @connect      fansly.com
// @connect      fapello.com
// @connect      leaknudes.com
// @connect      hotleak.vip
// @connect      nudostar.tv
// @connect      faponic.com
// @grant        GM.xmlHttpRequest
// @grant        GM_xmlhttpRequest
// @grant        GM.openInTab
// @grant        GM_openInTab
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    // --- Polyfills & Helpers ---
    const gmXHR = (typeof GM !== 'undefined' && GM.xmlHttpRequest) ? GM.xmlHttpRequest : (typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : null);
    const gmOpenTab = (typeof GM !== 'undefined' && GM.openInTab) ? GM.openInTab : (typeof GM_openInTab !== 'undefined' ? GM_openInTab : window.open);

    if (!gmXHR) {
        console.error('[Stash Check] GM.xmlHttpRequest is required for live status verification.');
    }

    const MAX_RETRIES = 2;
    const REQUEST_TIMEOUT = 10000;
    let currentCheckSessionId = 0;

    // SFW Favoring platforms
    const SFW_PLATFORMS = ['patreon', 'discord', 'fanbox', 'pixiv', 'fantia', 'boosty', 'dlsite', 'gumroad', 'subscribestar'];

    // --- Utility Functions ---
    function select(query, attribute = null) {
        const el = document.querySelector(query);
        return attribute ? el?.getAttribute(attribute) : el?.innerHTML;
    }

    function extract(string, prefix, suffix) {
        if (string == null) return null;
        let begIndex = string.indexOf(prefix);
        if (begIndex === -1) return null;
        else begIndex += prefix.length;
        let endIndex = string.indexOf(suffix, begIndex);
        if (endIndex === -1) endIndex = undefined;
        return string.slice(begIndex, endIndex);
    }

    function extractNextUrlPath(prefix, suffix = "/") {
        return extract(window.location.pathname, (prefix === "/") ? "/" : `/${prefix}/`, suffix);
    }

    function concatOrFalsy(...args) {
        for (let arg of args) {
            if (!arg) return arg;
        }
        return args.join("");
    }

    function compileURL({ domains, service, userID = null, postID = null } = {}) {
        let obj = {};
        for (let domain of domains) {
            let redirectURL = `https://${domain}/${service}`;
            if (userID) {
                redirectURL += `/user/${userID}`;
                if (postID) {
                    redirectURL += `/post/${postID}`;
                }
            } else continue;
            obj[domain.split(".").at(-2).toUpperCase()] = redirectURL;
        }
        return obj;
    }

    // --- Platform Extraction Methods ---
    function extractURLFromPatreon() {
        return compileURL({
            domains: ["kemono.cr", "pawchive.st"],
            service: "patreon",
            userID: extract(select("#__NEXT_DATA__"), '"creator":{"data":{"id":"', '"') ??
                extract(select("body"), '\\"https://www.patreon.com/api/user/', "\\"),
            postID: extractNextUrlPath("posts")?.split("-")?.at(-1)
        });
    }

    function extractURLFromFanbox() {
        return compileURL({
            domains: ["kemono.cr", "pawchive.st"],
            service: "fanbox",
            userID: extract(select('meta[property="og:image"]', "content"), "/creator/", "/") ??
                extract(select(".styled__StyledUserIcon-sc-1upaq18-10[style]", "style"), "/user/", "/") ??
                extract(select('a[href^="https://www.pixiv.net/users/"]', "href"), "/users/", "/"),
            postID: extractNextUrlPath("posts")
        });
    }

    function extractURLFromPixiv() {
        return compileURL({
            domains: ["kemono.cr", "pawchive.st"],
            service: "fanbox",
            userID: extractNextUrlPath("users") ??
                select("button[data-gtm-user-id]", "data-gtm-user-id") ??
                select("a.user-details-icon[href]", "href")?.split("/").at(-1)
        });
    }

    function extractURLFromDiscord() {
        const pathname = window.location.pathname.split("/");
        const serverID = /\d/.test(pathname[2]) ? `${pathname[2]}/${pathname?.[3] ?? ""}` : null;
        return serverID ? {
            "KEMONO": `https://kemono.cr/discord/server/${serverID}`
        } : {};
    }

    function extractURLFromFantia() {
        return compileURL({
            domains: ["kemono.cr"],
            service: "fantia",
            userID: extractNextUrlPath("fanclubs") ?? extract(select(".fanclub-header > a[href]", "href"), "/fanclubs/", "/"),
            postID: extractNextUrlPath("posts") ?? extractNextUrlPath("products")
        });
    }

    function extractURLFromBoosty() {
        return compileURL({
            domains: ["kemono.cr"],
            service: "boosty",
            userID: extractNextUrlPath("/"),
            postID: extractNextUrlPath("posts")
        });
    }

    function extractURLFromDlsite() {
        return compileURL({
            domains: ["kemono.cr"],
            service: "dlsite",
            userID: extractNextUrlPath("maker_id", ".html") ?? extract(select(".maker_name[itemprop=brand] > a", "href"), "/maker_id/", "."),
            postID: concatOrFalsy("RE", extractNextUrlPath("product_id")?.replace(/\D/g, ""))
        });
    }

    function extractURLFromGumroad() {
        const json = select("div#app[data-page]", "data-page");
        return compileURL({
            domains: ["kemono.cr"],
            service: "gumroad",
            userID: extract(json, '"external_id":"', '"') ?? extract(json, '"seller":{"id":"', '"'),
            postID: select('meta[property="product:retailer_item_id"]', "content")
        });
    }

    function extractURLFromSubscribeStar() {
        return compileURL({
            domains: ["kemono.cr"],
            service: "subscribestar",
            userID: select('img[data-type="avatar"][alt]', "alt")?.toLowerCase(),
            postID: extractNextUrlPath("posts")
        });
    }

    function extractURLFromOnlyFans() {
        return compileURL({
            domains: ["coomer.st"],
            service: "onlyfans",
            userID: select("#content .g-avatar[href]", "href")?.split("/")[1],
            postID: select("div.b-post:not(.is-not-post-page)", "id")?.replace(/\D/g, "")
        });
    }

    async function extractURLFromFansly() {
        let userID = null;
        const userName = select("div.feed-item-name a.username-wrapper", "href")?.split("/").at(-1) ??
            select("meta[property='og:title']", "content")?.slice(10) ??
            window.location.pathname.match(/^\/(?:profile\/)?([^/]+)/)?.[1];

        if (userName && !['explore', 'home', 'notifications', 'messages', 'settings'].includes(userName)) {
            try {
                const res = await new Promise((resolve) => {
                    makeRequest({ method: "GET", url: `https://apiv3.fansly.com/api/v1/account?usernames=${userName}`, onload: resolve, onerror: () => resolve(null) });
                });
                if (res && res.status === 200) {
                    userID = JSON.parse(res.responseText)?.response?.[0]?.id ?? null;
                }
            } catch (e) {}
        }
        return compileURL({
            domains: ["coomer.st"],
            service: "fansly",
            userID: userID ?? userName,
            postID: extractNextUrlPath("post")
        });
    }

    function extractURLFromCandFans() {
        return compileURL({
            domains: ["coomer.st"],
            service: "candfans",
            userID: extract(select("meta[property='og:image']", "content"), "/user/", "/") ?? window.location.pathname.split("/")[1],
            postID: extractNextUrlPath("show")
        });
    }

    function extractURLFromFantrie() {
        const ignored = ['my', 'home', 'settings', 'notifications', 'lists', 'chats', 'bookmarks', 'explore'];
        const parts = window.location.pathname.split('/');
        const username = (parts.length > 1 && parts[1] && !ignored.includes(parts[1])) ? parts[1] : null;
        return compileURL({
            domains: ["coomer.st"],
            service: "onlyfans",
            userID: username
        });
    }

    // --- Clean Display Name Extractor ---
    function getCleanDisplayName(service, handle) {
        let name = handle;
        if (service === 'patreon') {
            const h1 = document.querySelector('h1')?.textContent?.trim();
            if (h1 && h1.length < 40) return h1;
            const ogTitle = select('meta[property="og:title"]', 'content');
            if (ogTitle) {
                let clean = ogTitle.replace(/^Get more from\s+/i, '').replace(/\s+on Patreon$/i, '').replace(/\s*\|\s*Patreon$/i, '').trim();
                if (clean && clean.length < 35) return clean;
            }
            const parts = window.location.pathname.split('/').filter(p => p && !['posts', 'about', 'membership', 'collection', 'community', 'home', 'user'].includes(p.toLowerCase()));
            if (parts.length > 0 && !/\d{6,}/.test(parts[0])) return parts[0];
        } else if (service === 'gumroad') {
            const sub = window.location.hostname.split('.')[0];
            if (sub && !['www', 'app', 'gumroad', 'discover'].includes(sub.toLowerCase())) {
                return sub;
            }
            const ogTitle = select('meta[property="og:title"]', 'content');
            if (ogTitle) {
                let clean = ogTitle.replace(/^(Subscribe to|Buy from|Support|Follow)\s+/i, '').split('|')[0].trim();
                if (clean && clean.length < 30) return clean;
            }
        } else if (service === 'fanbox') {
            const h1 = document.querySelector('h1')?.textContent?.trim();
            if (h1 && h1.length < 35) return h1;
        }
        return name || service;
    }

    // --- Inject Styles ---
    function injectStyles() {
        if (document.getElementById('klf-ultimate-styles')) return;
        const style = document.createElement('style');
        style.id = 'klf-ultimate-styles';
        style.textContent = `
            #klf-overlay {
                position: fixed;
                bottom: 20px;
                right: 20px;
                width: 290px;
                background-color: #161618;
                border: 1px solid #38383e;
                border-radius: 10px;
                box-shadow: 0 8px 24px rgba(0, 0, 0, 0.75);
                color: #f0f0f0;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                font-size: 13px;
                z-index: 9999999;
                overflow: hidden;
                transition: width 0.2s ease, opacity 0.2s ease;
                user-select: none;
            }
            #klf-overlay-header {
                padding: 10px 12px;
                background: linear-gradient(135deg, #28282e, #1f1f24);
                border-bottom: 1px solid #38383e;
                cursor: grab;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            #klf-overlay-header:active {
                cursor: grabbing;
            }
            #klf-overlay-header .klf-title {
                margin: 0;
                font-size: 13px;
                font-weight: 700;
                color: #fff;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                display: flex;
                align-items: center;
                gap: 6px;
            }
            #klf-overlay-header .klf-controls {
                display: flex;
                align-items: center;
                gap: 8px;
            }
            .klf-nsfw-toggle {
                display: flex;
                align-items: center;
                gap: 4px;
                font-size: 10px;
                font-weight: 700;
                color: #ffaa00;
                cursor: pointer;
                background: rgba(255, 170, 0, 0.15);
                padding: 2px 6px;
                border-radius: 4px;
                border: 1px solid rgba(255, 170, 0, 0.35);
                text-transform: uppercase;
                letter-spacing: 0.3px;
            }
            .klf-nsfw-toggle input {
                cursor: pointer;
                margin: 0;
            }
            #klf-overlay-header .klf-btn-icon {
                background: rgba(255, 255, 255, 0.1);
                border: none;
                border-radius: 4px;
                color: #ddd;
                width: 22px;
                height: 22px;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                font-size: 11px;
                transition: background 0.2s, color 0.2s;
            }
            #klf-overlay-header .klf-btn-icon:hover {
                background: #ff4757;
                color: #fff;
            }
            #klf-overlay-body {
                max-height: 420px;
                overflow-y: auto;
                transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            }
            #klf-overlay.collapsed #klf-overlay-body {
                max-height: 0 !important;
                overflow: hidden;
            }
            #klf-overlay-body::-webkit-scrollbar {
                width: 5px;
            }
            #klf-overlay-body::-webkit-scrollbar-track {
                background: #1a1a1c;
            }
            #klf-overlay-body::-webkit-scrollbar-thumb {
                background: #444;
                border-radius: 3px;
            }
            .klf-section {
                border-bottom: 1px solid #28282e;
            }
            .klf-section:last-child {
                border-bottom: none;
            }
            .klf-section-title {
                padding: 6px 12px;
                background-color: #1e1e22;
                font-size: 11px;
                font-weight: 600;
                text-transform: uppercase;
                letter-spacing: 0.5px;
                color: #999;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            .klf-list {
                list-style: none;
                padding: 4px 8px;
                margin: 0;
            }
            .klf-list li {
                margin: 2px 0;
            }
            .klf-list li.klf-hidden-sfw {
                display: none !important;
            }
            .klf-list a {
                text-decoration: none;
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 6px 8px;
                border-radius: 6px;
                color: #ccc;
                transition: background 0.15s, color 0.15s, transform 0.1s;
            }
            .klf-list a:hover {
                background-color: #2a2a30;
                color: #fff;
                transform: translateX(2px);
            }
            .klf-site-name {
                display: flex;
                align-items: center;
                gap: 6px;
                font-weight: 500;
            }
            .klf-note {
                font-size: 10px;
                color: #888;
            }
            .klf-badge {
                padding: 2px 6px;
                border-radius: 4px;
                font-size: 10px;
                font-weight: 700;
                text-transform: uppercase;
                letter-spacing: 0.3px;
            }
            .klf-badge.found {
                background-color: rgba(46, 213, 115, 0.2);
                color: #2ed573;
                border: 1px solid rgba(46, 213, 115, 0.4);
            }
            .klf-badge.incomplete {
                background-color: rgba(255, 171, 0, 0.2);
                color: #ffab00;
                border: 1px solid rgba(255, 171, 0, 0.4);
            }
            .klf-badge.not-found, .klf-badge.missing {
                background-color: rgba(255, 71, 87, 0.15);
                color: #ff4757;
                border: 1px solid rgba(255, 71, 87, 0.3);
            }
            .klf-badge.pending {
                background-color: rgba(164, 176, 190, 0.15);
                color: #a4b0be;
                border: 1px solid rgba(164, 176, 190, 0.3);
            }
            .klf-badge.search {
                background-color: rgba(30, 144, 255, 0.15);
                color: #1e90ff;
                border: 1px solid rgba(30, 144, 255, 0.3);
            }
            .klf-toolbar {
                padding: 8px 10px;
                background-color: #1c1c20;
                border-top: 1px solid #28282e;
                display: flex;
                gap: 6px;
            }
            .klf-btn {
                flex: 1;
                background: #2f3542;
                color: #fff;
                border: 1px solid #444;
                border-radius: 5px;
                padding: 6px 4px;
                font-size: 11px;
                font-weight: 600;
                cursor: pointer;
                text-align: center;
                transition: background 0.2s, border-color 0.2s;
            }
            .klf-btn:hover {
                background: #3742fa;
                border-color: #5352ed;
            }
        `;
        document.head.appendChild(style);
    }

    // --- Overlay UI Creation & Dragging ---
    function createOrGetOverlay(displayName, service) {
        injectStyles();
        let overlay = document.getElementById('klf-overlay');
        const isSfwPlatform = SFW_PLATFORMS.includes((service || '').toLowerCase());

        // On SFW platforms, always default NSFW option to unchecked/disabled on page load
        let showNsfw = isSfwPlatform ? false : true;

        if (!overlay) {
            overlay = document.createElement('div');
            overlay.id = 'klf-overlay';
            overlay.className = 'collapsed';
            overlay.innerHTML = `
                <div id="klf-overlay-header">
                    <h3 class="klf-title"><span id="klf-service-icon">⚡</span> <span id="klf-username-display">${displayName}</span></h3>
                    <div class="klf-controls">
                        <button class="klf-btn-icon" id="klf-toggle-collapse" title="Toggle Expand">◀</button>
                    </div>
                </div>
                <div id="klf-overlay-body">
                    <div class="klf-section" id="klf-sec-live">
                        <div class="klf-section-title">
                            <span>Live Leak Checks</span>
                            <label class="klf-nsfw-toggle" id="klf-nsfw-holder" style="display: none;" title="Toggle adult/NSFW leak sources">
                                <input type="checkbox" id="klf-nsfw-checkbox" ${showNsfw ? 'checked' : ''}>
                                <span>NSFW</span>
                            </label>
                        </div>
                        <ul class="klf-list" id="klf-list-live"></ul>
                    </div>
                    <div class="klf-section" id="klf-sec-search">
                        <div class="klf-section-title">
                            <span>Other Sites & Forums</span>
                        </div>
                        <ul class="klf-list" id="klf-list-search"></ul>
                    </div>
                </div>
                <div class="klf-toolbar">
                    <button class="klf-btn" id="klf-btn-open-found" title="Open all verified/found links in new tabs">Open All Found</button>
                    <button class="klf-btn" id="klf-btn-search-all" title="Open all search query links in new tabs">Search All Sites</button>
                </div>
            `;
            document.body.appendChild(overlay);

            const header = overlay.querySelector('#klf-overlay-header');
            const toggleBtn = overlay.querySelector('#klf-toggle-collapse');
            const nsfwCheckbox = overlay.querySelector('#klf-nsfw-checkbox');

            nsfwCheckbox.addEventListener('change', (e) => {
                applySfwFilter(service);
            });

            header.addEventListener('click', (e) => {
                if (e.target.closest('button') || e.target.closest('label') || e.target.closest('input')) return;
                if (header._didMove) return;
                overlay.classList.toggle('collapsed');
                toggleBtn.textContent = overlay.classList.contains('collapsed') ? '◀' : '▼';
            });

            toggleBtn.addEventListener('click', () => {
                overlay.classList.toggle('collapsed');
                toggleBtn.textContent = overlay.classList.contains('collapsed') ? '◀' : '▼';
            });

            overlay.querySelector('#klf-btn-open-found').addEventListener('click', () => {
                const foundLinks = overlay.querySelectorAll('#klf-list-live li:not(.klf-hidden-sfw) a[data-status="found"], #klf-list-live li:not(.klf-hidden-sfw) a[data-status="incomplete"]');
                if (foundLinks.length === 0) {
                    alert('No verified found leaks yet!');
                    return;
                }
                foundLinks.forEach((a) => {
                    if (a.href) gmOpenTab(a.href, true);
                });
            });

            overlay.querySelector('#klf-btn-search-all').addEventListener('click', () => {
                const searchLinks = overlay.querySelectorAll('#klf-list-search li:not(.klf-hidden-sfw) a');
                if (searchLinks.length === 0) {
                    alert('No search links visible!');
                    return;
                }
                searchLinks.forEach((a) => {
                    if (a.href) gmOpenTab(a.href, true);
                });
            });

            makeDraggable(overlay, header);
        } else {
            document.getElementById('klf-username-display').textContent = displayName;
            document.getElementById('klf-list-live').innerHTML = '';
            document.getElementById('klf-list-search').innerHTML = '';
            const nsfwCheckbox = document.getElementById('klf-nsfw-checkbox');
            if (nsfwCheckbox) nsfwCheckbox.checked = showNsfw;
        }

        const nsfwHolder = document.getElementById('klf-nsfw-holder');
        if (nsfwHolder) {
            nsfwHolder.style.display = isSfwPlatform ? 'flex' : 'none';
        }

        overlay.style.display = 'block';
        return overlay;
    }

    function makeDraggable(overlay, header) {
        let isDragging = false;
        let startX, startY, initialRight, initialBottom;

        header.addEventListener('mousedown', (e) => {
            if (e.target.closest('button') || e.target.closest('label') || e.target.closest('input')) return;
            isDragging = true;
            header._didMove = false;
            startX = e.clientX;
            startY = e.clientY;
            const rect = overlay.getBoundingClientRect();
            initialRight = window.innerWidth - rect.right;
            initialBottom = window.innerHeight - rect.bottom;
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
                header._didMove = true;
            }
            let newRight = initialRight - dx;
            let newBottom = initialBottom - dy;

            newRight = Math.max(10, Math.min(window.innerWidth - overlay.offsetWidth - 10, newRight));
            newBottom = Math.max(10, Math.min(window.innerHeight - overlay.offsetHeight - 10, newBottom));

            overlay.style.right = `${newRight}px`;
            overlay.style.bottom = `${newBottom}px`;
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
        });
    }

    function applySfwFilter(service) {
        const isSfwPlatform = SFW_PLATFORMS.includes((service || '').toLowerCase());
        const checkbox = document.getElementById('klf-nsfw-checkbox');
        const showNsfw = checkbox ? checkbox.checked : !isSfwPlatform;

        const allListItems = document.querySelectorAll('#klf-list-live li, #klf-list-search li');
        allListItems.forEach(li => {
            const a = li.querySelector('a');
            const siteKey = a ? (a.dataset.sitekey || '') : '';
            if (isSfwPlatform && !showNsfw) {
                if (siteKey.startsWith('kemono') || siteKey.startsWith('pawchive')) {
                    li.classList.remove('klf-hidden-sfw');
                } else {
                    li.classList.add('klf-hidden-sfw');
                }
            } else {
                li.classList.remove('klf-hidden-sfw');
            }
        });

        const searchSec = document.getElementById('klf-sec-search');
        if (searchSec) {
            const visibleSearch = searchSec.querySelectorAll('li:not(.klf-hidden-sfw)').length;
            searchSec.style.display = visibleSearch > 0 ? 'block' : 'none';
        }

        const searchAllBtn = document.getElementById('klf-btn-search-all');
        if (searchAllBtn) {
            searchAllBtn.style.display = (isSfwPlatform && !showNsfw) ? 'none' : 'block';
        }
    }

    function addLinkItem(sectionId, siteName, url, note = '', initialStatus = 'search', badgeText = null) {
        const list = document.getElementById(`klf-list-${sectionId}`);
        if (!list) return null;

        const li = document.createElement('li');
        const a = document.createElement('a');
        const siteKey = siteName.toLowerCase().replace(/[^a-z0-9]/g, '') + (note ? '_' + note.toLowerCase().replace(/[^a-z0-9]/g, '') : '');

        a.href = url;
        a.target = '_blank';
        a.rel = 'noopener noreferrer';
        a.dataset.sitekey = siteKey;
        a.dataset.status = initialStatus;

        const displayNote = note ? ` <span class="klf-note">(${note})</span>` : '';
        const badge = badgeText || initialStatus.toUpperCase();

        a.innerHTML = `
            <span class="klf-site-name">${siteName}${displayNote}</span>
            <span class="klf-badge ${initialStatus}">${badge}</span>
        `;

        a.addEventListener('click', (e) => {
            if (e.ctrlKey) {
                e.preventDefault();
                gmOpenTab(url, true);
            }
        });

        li.appendChild(a);
        list.appendChild(li);
        return { a, li };
    }

    function updateLinkStatus(siteName, note, status, customBadgeText = null) {
        const siteKey = siteName.toLowerCase().replace(/[^a-z0-9]/g, '') + (note ? '_' + note.toLowerCase().replace(/[^a-z0-9]/g, '') : '');
        const a = document.querySelector(`a[data-sitekey="${siteKey}"]`);
        if (a) {
            a.dataset.status = status;
            const badge = a.querySelector('.klf-badge');
            if (badge) {
                badge.className = `klf-badge ${status}`;
                badge.textContent = customBadgeText || status.toUpperCase();
            }
        }
    }

    function makeRequest(details, retryCount = 0, sessionId = currentCheckSessionId) {
        if (!gmXHR) return;
        gmXHR({
            method: details.method || 'GET',
            url: details.url,
            headers: details.headers || { 'Accept': 'text/css' },
            timeout: REQUEST_TIMEOUT,
            onload(res) {
                if (sessionId !== currentCheckSessionId) return;
                if (details.onload) details.onload(res);
            },
            onerror(err) {
                if (sessionId !== currentCheckSessionId) return;
                if (retryCount < MAX_RETRIES) {
                    setTimeout(() => makeRequest(details, retryCount + 1, sessionId), 1000);
                } else if (details.onerror) {
                    details.onerror(err);
                }
            },
            ontimeout() {
                if (sessionId !== currentCheckSessionId) return;
                if (retryCount < MAX_RETRIES) {
                    setTimeout(() => makeRequest(details, retryCount + 1, sessionId), 1000);
                } else if (details.onerror) {
                    details.onerror({ status: 408 });
                }
            }
        });
    }

    function verifyArchiveEndpoint(siteName, url, note, sessionId) {
        try {
            const parsedUrl = new URL(url);
            let checkUrl = null;
            let isPost = parsedUrl.pathname.includes('/post/');

            if (parsedUrl.pathname.startsWith('/discord/server/')) {
                const serverId = parsedUrl.pathname.split('/')[3];
                checkUrl = `https://${parsedUrl.hostname}/api/v1/discord/server/${serverId}`;
            } else {
                checkUrl = `https://${parsedUrl.hostname}/api/v1${parsedUrl.pathname}${isPost ? '' : '/profile'}`;
            }

            makeRequest({
                method: 'GET',
                url: checkUrl,
                headers: { 'Accept': 'text/css' },
                onload(res) {
                    if (sessionId !== currentCheckSessionId) return;
                    if (parsedUrl.pathname.startsWith('/discord/server/')) {
                        if (res.status === 200) {
                            updateLinkStatus(siteName, note, 'found', 'FOUND');
                        } else {
                            updateLinkStatus(siteName, note, 'missing', 'MISSING');
                        }
                    } else {
                        if (res.status === 200 || res.status === 202) {
                            updateLinkStatus(siteName, note, isPost ? 'incomplete' : 'found', isPost ? 'POST FOUND' : 'FOUND');
                        } else if (res.status === 404) {
                            updateLinkStatus(siteName, note, 'missing', 'NOT FOUND');
                        } else {
                            updateLinkStatus(siteName, note, 'missing', `ERR ${res.status}`);
                        }
                    }
                },
                onerror() { updateLinkStatus(siteName, note, 'missing', 'OFFLINE'); }
            }, 0, sessionId);
        } catch (e) {
            updateLinkStatus(siteName, note, 'missing', 'ERROR');
        }
    }

    // --- Live Leak Checks: kemono, coomer, hotleak, nudostar, fapello, leaknudes, faponic ---
    function runLiveLeakChecks(handle, service, archives, sessionId) {
        // 1. Kemono (STRICTLY only for Kemono platforms: patreon, discord, fantia, boosty, gumroad, subscribestar, fanbox, pixiv, dlsite)
        if (archives["KEMONO"]) {
            addLinkItem('live', 'Kemono', archives["KEMONO"], '', 'pending', 'CHECKING');
            verifyArchiveEndpoint('Kemono', archives["KEMONO"], '', sessionId);
        }
        if (archives["PAWCHIVE"]) {
            addLinkItem('live', 'Pawchive', archives["PAWCHIVE"], '', 'pending', 'CHECKING');
            makeRequest({
                method: 'GET',
                url: archives["PAWCHIVE"],
                headers: { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' },
                onload(res) {
                    const finalUrl = res.finalUrl || '';
                    const isFound = res.status === 200 && !finalUrl.includes('/artists');
                    updateLinkStatus('Pawchive', '', isFound ? 'found' : 'missing', isFound ? 'FOUND' : 'MISSING');
                }
            }, 0, sessionId);
        }

        // 2. Coomer (STRICTLY only for Coomer platforms: onlyfans, fansly, candfans, fantrie)
        if (archives["COOMER"]) {
            addLinkItem('live', 'Coomer', archives["COOMER"], '', 'pending', 'CHECKING');
            verifyArchiveEndpoint('Coomer', archives["COOMER"], '', sessionId);
        }

        // 3. Hotleak
        const hotleakUrl = `https://hotleak.vip/${handle}`;
        addLinkItem('live', 'Hotleak', hotleakUrl, '', 'pending', 'CHECKING');
        makeRequest({
            method: 'HEAD',
            url: hotleakUrl,
            onload(res) {
                updateLinkStatus('Hotleak', '', res.status === 200 ? 'found' : 'not-found', res.status === 200 ? 'FOUND' : 'NOT FOUND');
            },
            onerror() { updateLinkStatus('Hotleak', '', 'not-found', 'OFFLINE'); }
        }, 0, sessionId);

        // 4. Nudostar
        const nudostarUrl = `https://nudostar.tv/models/${handle}/`;
        addLinkItem('live', 'Nudostar', nudostarUrl, '', 'pending', 'CHECKING');
        makeRequest({
            method: 'HEAD',
            url: nudostarUrl,
            onload(res) {
                updateLinkStatus('Nudostar', '', res.status === 200 ? 'found' : 'not-found', res.status === 200 ? 'FOUND' : 'NOT FOUND');
            },
            onerror() { updateLinkStatus('Nudostar', '', 'not-found', 'OFFLINE'); }
        }, 0, sessionId);

        // 5. Fapello
        const fapelloUrl = `https://fapello.com/${handle}/`;
        addLinkItem('live', 'Fapello', fapelloUrl, '', 'pending', 'CHECKING');
        makeRequest({
            method: 'GET',
            url: fapelloUrl,
            onload(res) {
                const isFound = res.status === 200 && (res.finalUrl || '').includes(handle);
                updateLinkStatus('Fapello', '', isFound ? 'found' : 'not-found', isFound ? 'FOUND' : 'NOT FOUND');
            },
            onerror() { updateLinkStatus('Fapello', '', 'not-found', 'OFFLINE'); }
        }, 0, sessionId);

        if (handle.includes('_')) {
            const altUser = handle.replace(/_/g, '-');
            const fapelloAltUrl = `https://fapello.com/${altUser}/`;
            addLinkItem('live', 'Fapello', fapelloAltUrl, 'Alt Handle', 'pending', 'CHECKING');
            makeRequest({
                method: 'GET',
                url: fapelloAltUrl,
                onload(res) {
                    const isFound = res.status === 200 && (res.finalUrl || '').includes(altUser);
                    updateLinkStatus('Fapello', 'Alt Handle', isFound ? 'found' : 'not-found', isFound ? 'FOUND' : 'NOT FOUND');
                }
            }, 0, sessionId);
        }

        // 6. LeakNudes
        const leakNudesUrl = `https://leaknudes.com/model/${handle}`;
        addLinkItem('live', 'LeakNudes', leakNudesUrl, '', 'pending', 'CHECKING');
        makeRequest({
            method: 'GET',
            url: leakNudesUrl,
            onload(res) {
                const isFound = res.status === 200 && res.finalUrl === leakNudesUrl;
                updateLinkStatus('LeakNudes', '', isFound ? 'found' : 'not-found', isFound ? 'FOUND' : 'NOT FOUND');
            },
            onerror() { updateLinkStatus('LeakNudes', '', 'not-found', 'OFFLINE'); }
        }, 0, sessionId);

        // 7. Faponic
        const faponicUrl = `https://faponic.com/${handle}/`;
        addLinkItem('live', 'Faponic', faponicUrl, '', 'pending', 'CHECKING');
        makeRequest({
            method: 'GET',
            url: faponicUrl,
            onload(res) {
                const isFound = res.status === 200 && !(res.responseText || '').includes('<title>404 | Faponic</title>');
                updateLinkStatus('Faponic', '', isFound ? 'found' : 'not-found', isFound ? 'FOUND' : 'NOT FOUND');
            },
            onerror() { updateLinkStatus('Faponic', '', 'not-found', 'OFFLINE'); }
        }, 0, sessionId);
    }

    // --- Populate Other Sites & Forums ---
    function populateOtherSitesAndForums(handle) {
        const u = encodeURIComponent(handle);
        
        const searchSites = [
            { name: 'Bunkr Albums', url: `https://balbums.st/?search=${u}`, note: 'Album Query' },
            { name: 'TopFapGirls', url: `https://www.topfapgirls1.com/search/?q=${u}`, note: 'Search' },
            { name: 'EroThots', url: `https://erothots.co/search/?q=${u}`, note: 'Media Query' },
            { name: 'OSosedki', url: `https://ososedki.com/search?q=${u}`, note: 'Archive Query' },
            { name: 'RedditPlug', url: `https://redditplug.com/?s=${u}`, note: 'Mega/Bypass' },
            { name: 'TurboVid Library', url: `https://turbovid.cr/library?q=${u}`, note: 'Video Query' },
            { name: 'xxxTube', url: `https://x-x-x.tube/search/${u}/`, note: 'Tube Query' },
            { name: 'ThotHub', url: `https://thothub.to/search/${u}/`, note: 'KVS Query' },
            { name: 'NobodyHome', url: `https://nobodyhome.tv/index.php?search=${u}`, note: 'Protected Query' },
            { name: 'OnlyFans421', url: `https://duckduckgo.com/?q=site%3Arentry.org+${u}`, note: 'Rentry Bypass' },
            
            // Forums requiring signup placed at the lowest positions
            { name: 'SimpCity Forum', url: `https://simpcity.cr/search/search?keywords=${u}`, note: 'Requires Signup' },
            { name: 'ViperGirls Forum', url: `https://vipergirls.to/search.php?do=process&query=${u}`, note: 'Requires Signup' }
        ];

        searchSites.forEach(site => {
            addLinkItem('search', site.name, site.url, site.note, 'search', 'SEARCH');
        });
    }

    // --- Detect Creator Data & Archives ---
    async function getCreatorData() {
        const host = window.location.hostname;
        let archives = {};
        let handle = null;
        let service = null;

        if (host.includes('onlyfans.com') || host.includes('fantrie.com')) {
            service = host.includes('onlyfans') ? 'onlyfans' : 'fantrie';
            archives = extractURLFromOnlyFans();
            if (service === 'fantrie') {
                archives = extractURLFromFantrie();
            }
            const ignored = ['my', 'home', 'settings', 'notifications', 'lists', 'chats', 'bookmarks', 'explore', 'action'];
            const parts = window.location.pathname.split('/');
            handle = (parts.length > 1 && parts[1] && !ignored.includes(parts[1])) ? parts[1] : null;
            if (!handle && archives["COOMER"]) handle = archives["COOMER"].split('/').at(-1);
        } else if (host.includes('fansly.com')) {
            service = 'fansly';
            archives = await extractURLFromFansly();
            const ignored = ['explore', 'home', 'notifications', 'messages', 'settings'];
            const matches = window.location.pathname.match(/^\/(?:profile\/)?([^/]+)/);
            handle = (matches && matches[1] && !ignored.includes(matches[1])) ? matches[1] : null;
            if (!handle && archives["COOMER"]) handle = archives["COOMER"].split('/').at(-1);
        } else if (host.includes('patreon.com')) {
            service = 'patreon';
            archives = extractURLFromPatreon();
            handle = archives["KEMONO"]?.split('/').at(-1) || extractNextUrlPath("user") || window.location.pathname.split('/').at(-1);
        } else if (host.includes('fanbox.cc')) {
            service = 'fanbox';
            archives = extractURLFromFanbox();
            handle = host.split('.')[0] !== 'www' ? host.split('.')[0] : extractNextUrlPath('@');
        } else if (host.includes('pixiv.net')) {
            service = 'pixiv';
            archives = extractURLFromPixiv();
            handle = extractNextUrlPath("users") || archives["KEMONO"]?.split('/').at(-1);
        } else if (host.includes('discord.com')) {
            service = 'discord';
            archives = extractURLFromDiscord();
            handle = archives["KEMONO"]?.split('/').at(-1);
        } else if (host.includes('fantia.jp')) {
            service = 'fantia';
            archives = extractURLFromFantia();
            handle = extractNextUrlPath("fanclubs");
        } else if (host.includes('boosty.to')) {
            service = 'boosty';
            archives = extractURLFromBoosty();
            handle = extractNextUrlPath("/");
        } else if (host.includes('dlsite.com')) {
            service = 'dlsite';
            archives = extractURLFromDlsite();
            handle = extractNextUrlPath("maker_id", ".html");
        } else if (host.includes('gumroad.com')) {
            service = 'gumroad';
            archives = extractURLFromGumroad();
            handle = select('meta[property="og:title"]', "content") || "Gumroad Creator";
        } else if (host.includes('subscribestar.com') || host.includes('subscribestar.adult')) {
            service = 'subscribestar';
            archives = extractURLFromSubscribeStar();
            handle = select('img[data-type="avatar"][alt]', "alt")?.toLowerCase() || window.location.pathname.split('/')[1];
        } else if (host.includes('candfans.com')) {
            service = 'candfans';
            archives = extractURLFromCandFans();
            handle = window.location.pathname.split('/')[1];
        }

        if (!handle || handle === 'www') return null;
        const displayName = getCleanDisplayName(service, handle);
        return { handle, displayName, service, archives };
    }

    // --- Main Loop ---
    let lastCheckedHandle = null;
    let lastUrl = location.href;

    async function mainLoop() {
        const data = await getCreatorData();
        const overlay = document.getElementById('klf-overlay');

        if (data && data.handle && data.handle !== lastCheckedHandle) {
            lastCheckedHandle = data.handle;
            currentCheckSessionId++;
            const sessionId = currentCheckSessionId;

            createOrGetOverlay(data.displayName, data.service);

            // 1. Run Live Leak Checks
            runLiveLeakChecks(data.handle, data.service, data.archives, sessionId);

            // 2. Populate Other Sites & Forums
            populateOtherSitesAndForums(data.handle);

            // 3. Apply SFW / NSFW filter
            applySfwFilter(data.service);

        } else if (data && data.handle && overlay && overlay.style.display === 'none') {
            overlay.style.display = 'block';
        } else if (!data && overlay) {
            overlay.style.display = 'none';
            lastCheckedHandle = null;
        }
    }

    setTimeout(mainLoop, 1000);

    new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            setTimeout(mainLoop, 800);
        }
    }).observe(document.body, { subtree: true, childList: true });

})();