您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a convenient overlay to check for "leaks" on various sites.
// ==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 }); })();