Leak Finder Overlay

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

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();