Leak Finder Overlay

Adds a convenient overlay to check for "leaks" on various sites.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Leak Finder Overlay
// @namespace    http://tampermonkey.net/
// @version      0.9
// @description  Adds a convenient overlay to check for "leaks" on various sites.
// @author       You (updated by AI)
// @match        https://onlyfans.com/*
// @match        https://fansly.com/*
// @match        https://fantrie.com/*
// @match        *://*.coomer.st/*
// @match        *://*.fapello.com/*
// @match        *://*.topfapgirls1.com/*
// @grant        GM.xmlHttpRequest
// @grant        GM_addStyle
// @license      Unlicense
// ==/UserScript==

(function () {
    'use strict';

    // --- Configuration ---
    const COOMER_DOMAIN = 'coomer.st';
    const MAX_RETRIES = 3;

    // --- UI Creation ---
    function createOverlay(username) {
        // Inject styles
        GM_addStyle(`
            #leak-finder-overlay {
                position: fixed;
                bottom: 20px;
                right: 20px;
                width: 220px;
                background-color: #1a1a1a;
                border: 1px solid #444;
                border-radius: 8px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.5);
                color: #f0f0f0;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                font-size: 14px;
                z-index: 9999;
                overflow: hidden;
            }
            #leak-finder-overlay-header {
                padding: 10px;
                background-color: #2a2a2a;
                cursor: pointer;
                user-select: none;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            #leak-finder-overlay-header h3 {
                margin: 0;
                font-size: 1em;
                font-weight: bold;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                padding-right: 5px;
            }
            #leak-finder-overlay-list {
                list-style: none;
                padding: 5px 10px;
                margin: 0;
                max-height: 300px;
                overflow-y: auto;
                transition: all 0.3s ease;
            }
            #leak-finder-overlay.collapsed #leak-finder-overlay-list {
                padding-top: 0;
                padding-bottom: 0;
                max-height: 0;
            }
            #leak-finder-overlay-list li {
                margin: 5px 0;
            }
            #leak-finder-overlay-list a {
                text-decoration: none;
                display: block;
                padding: 4px;
                border-radius: 4px;
                transition: background-color 0.2s, color 0.2s;
            }
            #leak-finder-overlay-list a:hover {
                background-color: #333;
            }
            #leak-finder-overlay-list a.found {
                color: #4CAF50; /* Green for found */
                font-weight: bold;
            }
            #leak-finder-overlay-list a.not-found {
                color: #ff4d4d; /* Red for not found */
            }
            #leak-finder-overlay-list a.pending {
                color: #ffc107; /* Yellow for pending */
            }
        `);

        const overlay = document.createElement('div');
        overlay.id = 'leak-finder-overlay';

        const header = document.createElement('div');
        header.id = 'leak-finder-overlay-header';
        header.innerHTML = `<h3>Checking: ${username}</h3><span id="leak-finder-toggle">▼</span>`;

        const list = document.createElement('ul');
        list.id = 'leak-finder-overlay-list';

        overlay.appendChild(header);
        overlay.appendChild(list);
        document.body.appendChild(overlay);

        header.addEventListener('click', () => {
            overlay.classList.toggle('collapsed');
            document.getElementById('leak-finder-toggle').textContent = overlay.classList.contains('collapsed') ? '▶' : '▼';
        });

        return list;
    }

    function addLink(listElement, siteName, url, note = '') {
        const li = document.createElement('li');
        const a = document.createElement('a');
        const dataSite = siteName.toLowerCase() + (note ? '-' + note.toLowerCase() : '');

        a.href = url;
        a.textContent = siteName + (note ? ` (${note})` : '');
        a.target = '_blank';
        a.rel = 'noopener noreferrer';
        a.classList.add('pending');
        a.dataset.site = dataSite;

        li.appendChild(a);
        listElement.appendChild(li);
    }

    function updateLinkStatus(siteName, note, found) {
        const dataSite = siteName.toLowerCase() + (note ? '-' + note.toLowerCase() : '');
        const link = document.querySelector(`[data-site="${dataSite}"]`);
        if (link) {
            link.classList.remove('pending');
            link.classList.add(found ? 'found' : 'not-found');
        }
    }


    // --- Core Logic ---
    function getUsernameFromUrl() {
        const hostname = window.location.hostname;
        const pathname = window.location.pathname;
        const ignoredPaths = ['my', 'home', 'settings', 'notifications', 'lists', 'chats', 'bookmarks', 'explore'];

        if (hostname.includes('onlyfans.com') || hostname.includes('fantrie.com')) {
            const parts = pathname.split('/');
            if (parts.length > 1 && parts[1] && !ignoredPaths.includes(parts[1])) {
                return parts[1];
            }
        }

        if (hostname.includes('fansly.com')) {
            const matches = pathname.match(/^\/(?:profile\/)?([^/]+)/);
            if (matches && matches[1] && !ignoredPaths.includes(matches[1])) {
                return matches[1];
            }
        }
        return null;
    }

    function createRequest(url, headers, onSuccess, onError, retryCount = 0) {
        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            headers: headers,
            onload(response) {
                onSuccess(response);
            },
            onerror(response) {
                if (retryCount < MAX_RETRIES) {
                    console.log(`Retrying... Attempt ${retryCount + 1} for ${url}`);
                    setTimeout(() => createRequest(url, headers, onSuccess, onError, retryCount + 1), 1000);
                } else {
                    console.error(`Max retries reached for ${url}`);
                    onError(response);
                }
            },
        });
    }

    // --- Site Checkers ---
    function checkCoomer(service, username, displayName = 'Coomer', note = '') {
        const profileUrl = `https://${COOMER_DOMAIN}/${service}/user/${username}`;
        // *** FIX: Added /profile to the API endpoint ***
        const apiUrl = `https://${COOMER_DOMAIN}/api/v1/${service}/user/${username}/profile`;
        // *** FIX: Added 'Accept' header for coomer requests ***
        const coomerHeaders = { 'Accept': 'text/css' };

        addLink(document.getElementById('leak-finder-overlay-list'), displayName, profileUrl, note);
        createRequest(apiUrl, coomerHeaders,
            (response) => updateLinkStatus(displayName, note, response.status === 200),
            () => updateLinkStatus(displayName, note, false)
        );
    }

    function checkFapello(username, note = '') {
        const profileUrl = `https://fapello.com/${username}/`;
        addLink(document.getElementById('leak-finder-overlay-list'), 'Fapello', profileUrl, note);
        createRequest(profileUrl, {},
            (response) => {
                const found = response.status === 200 && response.finalUrl.startsWith(profileUrl);
                updateLinkStatus('Fapello', note, found);
            },
            () => updateLinkStatus('Fapello', note, false)
        );
    }

    function checkLeakNudes(username, note = '') {
        const profileUrl = `https://leaknudes.com/model/${username}`;
        addLink(document.getElementById('leak-finder-overlay-list'), 'LeakNudes', profileUrl, note);
        createRequest(profileUrl, {},
            (response) => {
                const found = response.status === 200 && response.finalUrl === profileUrl;
                updateLinkStatus('LeakNudes', note, found);
            },
            () => updateLinkStatus('LeakNudes', note, false)
        );
    }

    function checkTopFapGirls(username, note = '') {
        const searchUrl = `https://www.topfapgirls1.com/search/?q=${username}`;
        const dataSite = 'topfapgirls' + (note ? '-' + note.toLowerCase() : '');
        addLink(document.getElementById('leak-finder-overlay-list'), 'TopFapGirls', searchUrl, note);

        createRequest(searchUrl, {},
            (response) => {
                const found = response.status === 200 && !response.finalUrl.includes("?q=");
                updateLinkStatus('TopFapGirls', note, found);
                if (found) {
                    const link = document.querySelector(`[data-site="${dataSite}"]`);
                    if (link) link.href = response.finalUrl;
                }
            },
            () => updateLinkStatus('TopFapGirls', note, false)
        );
    }

    // --- Platform-specific Initializers ---
    function runChecks(username) {
        const oldOverlay = document.getElementById('leak-finder-overlay');
        if (oldOverlay) oldOverlay.remove();

        const listElement = createOverlay(username);
        const hostname = window.location.hostname;

        if (hostname.includes('onlyfans.com')) {
            checkCoomer('onlyfans', username);
            checkFapello(username);
            checkLeakNudes(username);
            checkTopFapGirls(username);
            if (username.includes('_')) {
                checkFapello(username.replace(/_/g, '-'), 'alt');
            }
        } else if (hostname.includes('fansly.com')) {
            const coomerNote = 'Fansly ID';
            const coomerDataSite = 'coomer-' + coomerNote.toLowerCase().replace(' ', '');
            addLink(listElement, 'Coomer', `https://${COOMER_DOMAIN}/`, coomerNote);

            const fanslyApiUrl = `https://apiv3.fansly.com/api/v1/account?usernames=${username}`;
            createRequest(fanslyApiUrl, {},
                (response) => {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data.success && data.response.length > 0) {
                            const id = data.response[0].id;
                            // *** FIX: Added /profile to the API endpoint ***
                            const coomerApiUrl = `https://${COOMER_DOMAIN}/api/v1/fansly/user/${id}/profile`;
                            const coomerProfileUrl = `https://${COOMER_DOMAIN}/fansly/user/${id}`;
                            const link = document.querySelector(`[data-site="${coomerDataSite}"]`);
                            if (link) link.href = coomerProfileUrl;

                            // *** FIX: Added 'Accept' header for coomer requests ***
                            const coomerHeaders = { 'Accept': 'text/css' };

                            createRequest(coomerApiUrl, coomerHeaders,
                                (coomerRes) => updateLinkStatus('Coomer', coomerNote, coomerRes.status === 200),
                                () => updateLinkStatus('Coomer', coomerNote, false)
                            );
                        } else {
                            updateLinkStatus('Coomer', coomerNote, false);
                        }
                    } catch (e) {
                        updateLinkStatus('Coomer', coomerNote, false);
                    }
                },
                () => updateLinkStatus('Coomer', coomerNote, false)
            );

            checkFapello(username);
            checkLeakNudes(username);
            checkTopFapGirls(username);
            if (username.includes('_')) {
                checkFapello(username.replace(/_/g, '-'), 'alt');
            }

            setTimeout(() => {
                const instaElement = document.querySelector('a[href*="instagram.com"]');
                if (instaElement) {
                    const instaMatch = instaElement.href.match(/instagram\.com\/([a-zA-Z0-9_.]+)/);
                    if (instaMatch && instaMatch[1]) {
                        const instaHandle = instaMatch[1];
                        checkFapello(instaHandle, 'Insta');
                        checkTopFapGirls(instaHandle, 'Insta');
                    }
                }
            }, 2000);

        } else if (hostname.includes('fantrie.com')) {
            checkFapello(username);
            checkLeakNudes(username);
            checkTopFapGirls(username);
        }
    }

    // --- Main Execution ---
    let lastUrl = location.href;
    let lastUsername = null;

    function main() {
        const username = getUsernameFromUrl();
        const overlay = document.getElementById('leak-finder-overlay');

        if (username && username !== lastUsername) {
            lastUsername = username;
            setTimeout(() => runChecks(username), 500);
        } else if (username && overlay && overlay.style.display === 'none') {
             overlay.style.display = 'block';
        } else if (!username && overlay) {
            overlay.style.display = 'none';
            lastUsername = null;
        }
    }

    // Initial run
    setTimeout(main, 1000);

    // Rerun on URL changes for SPAs
    new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            main();
        }
    }).observe(document.body, { subtree: true, childList: true });

})();