您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
the best in the west
// ==UserScript== // @name Roblox FriendList Follow Scraper (PH Theme With Remes Hot Updates) // @namespace http://tampermonkey.net/ // @version 2.2 // @description the best in the west // @author p_dh // @match https://www.roblox.com/users/friends // @icon https://www.google.com/s2/favicons?sz=64&domain=roblox.com // @grant GM_xmlhttpRequest // @grant GM_download // @connect roblox.com // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; const FOLLOWERS_API = 'https://friends.roblox.com/v1/users/%userId%/followers/count'; const USERNAME_API = 'https://users.roblox.com/v1/users/%userId%'; const PROCESSING_DELAY = 200; const BATCH_SIZE = 5; let isProcessing = false; let isPaused = false; let currentPage = 1; let totalPages = 0; let totalUsers = 0; let validUsers = []; let startTime = null; let checksPerSecond = 0; let checksCount = 0; function createUI() { const overlay = document.createElement('div'); overlay.style = ` position: fixed; top: 20px; right: 20px; background: #000; color: #ff9900; padding: 15px; border-radius: 8px; z-index: 99999; box-shadow: 0 4px 6px rgba(255, 153, 0, 0.3); font-family: 'Arial', sans-serif; width: 320px; cursor: move; border: 2px solid #ff9900; `; overlay.innerHTML = ` <div style="position: relative;"> <div style="display: flex; align-items: center; gap: 10px;"> <img src="https://upload.wikimedia.org/wikipedia/commons/6/60/Logo_.svg" alt="Failed To Load The Sigma Logo" width="200"> <h3 style="margin:0; font-size:18px; color: white; text-transform: uppercase;">Freaky Scraper 🍑</h3> </div> <div class="progress-container" style="background:#333; height:4px; border-radius:2px; margin-top:10px;"> <div class="progress-bar" style="height:100%; background:#ff9900; width:0; transition:width 0.3s"></div> </div> <div class="status" style="margin-top:10px; font-size:14px; color:#ddd;">Status: Ready to Go 😏</div> <div class="results" style="margin-top:10px; max-height:200px; overflow-y:auto; font-size:14px; color:white;"></div> <div class="timer" style="margin-top:10px; font-size:12px; color:#ff9900;">Time Elapsed: 0s</div> <div class="checks-per-second" style="margin-top:5px; font-size:12px; color:#ff9900;">Requests/Sec: 0</div> <button id="pauseButton" style="margin-top:10px; padding:8px 15px; background:#ffcc00; color:#000; font-size:14px; border:none; border-radius:4px; cursor:pointer; display:none;">⏸ Pause</button> <button id="exportButton" style="margin-top:10px; padding:8px 15px; background:#ff9900; color:#000; font-size:14px; border:none; border-radius:4px; cursor:pointer; display:none;">📥 Export Results</button> <div style="margin-top:10px; font-size:12px; color:#ff9900; text-align:center;">Made for my baddie Reme by p_dh 😘🔥</div> <button id="closeButton" style="position: absolute; top: -10px; right: -10px; background: #ff4444; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; font-size: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center;">X</button> </div> `; document.body.appendChild(overlay); const closeButton = overlay.querySelector('#closeButton'); closeButton.addEventListener('click', () => { overlay.remove(); }); let isDragging = false; let offsetX, offsetY; overlay.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - overlay.getBoundingClientRect().left; offsetY = e.clientY - overlay.getBoundingClientRect().top; }); document.addEventListener('mousemove', (e) => { if (isDragging) { overlay.style.left = `${e.clientX - offsetX}px`; overlay.style.top = `${e.clientY - offsetY}px`; } }); document.addEventListener('mouseup', () => { isDragging = false; }); return overlay; } async function fetchUsername(userId) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: USERNAME_API.replace('%userId%', userId), headers: { 'Accept': 'application/json' }, onload: (response) => { try { const data = JSON.parse(response.responseText); resolve(data.name || `User${userId}`); } catch { resolve(`User${userId}`); } }, onerror: () => resolve(`User${userId}`) }); }); } async function fetchFollowerCount(userId) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: FOLLOWERS_API.replace('%userId%', userId), headers: { 'Accept': 'application/json' }, onload: (response) => { try { const data = JSON.parse(response.responseText); resolve(data.count || 0); } catch { resolve(0); } }, onerror: () => resolve(0) }); }); } function exportResults() { const sortedUsers = validUsers.sort((a, b) => b.followers - a.followers); const content = sortedUsers.map(user => `${user.username} (${user.followers})`).join('\n'); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); GM_download({ url: url, name: 'valid_users.txt', saveAs: true, onload: () => URL.revokeObjectURL(url), onerror: (e) => console.error('Download failed:', e) }); } function formatTimeElapsed(seconds) { const minutes = Math.floor(seconds / 60); const secs = seconds % 60; return `${minutes}m ${secs}s`; } async function processRequests() { if (isProcessing) return; isProcessing = true; const overlay = createUI(); const progressBar = overlay.querySelector('.progress-bar'); const statusContainer = overlay.querySelector('.status'); const resultsContainer = overlay.querySelector('.results'); const timerContainer = overlay.querySelector('.timer'); const checksPerSecondContainer = overlay.querySelector('.checks-per-second'); const pauseButton = overlay.querySelector('#pauseButton'); const exportButton = overlay.querySelector('#exportButton'); pauseButton.style.display = 'inline-block'; exportButton.style.display = 'none'; startTime = Date.now(); const timerInterval = setInterval(() => { const elapsed = Math.floor((Date.now() - startTime) / 1000); timerContainer.textContent = `Time Elapsed: ${formatTimeElapsed(elapsed)}`; }, 1000); const checksInterval = setInterval(() => { checksPerSecond = checksCount; checksCount = 0; checksPerSecondContainer.textContent = `Requests/Sec: ${checksPerSecond}`; }, 1000); pauseButton.addEventListener('click', () => { isPaused = !isPaused; pauseButton.textContent = isPaused ? 'Resume' : 'Pause'; exportButton.style.display = isPaused ? 'inline-block' : 'none'; exportButton.textContent = 'Download Current Results'; }); exportButton.addEventListener('click', exportResults); try { while (true) { if (isPaused) { await new Promise(r => setTimeout(r, 100)); continue; } const requests = Array.from(document.querySelectorAll('.list-item.avatar-card:not(.disabled)')); totalUsers = requests.length; for (let i = 0; i < totalUsers; i += BATCH_SIZE) { if (isPaused) break; const batch = requests.slice(i, i + BATCH_SIZE); const batchPromises = batch.map(async (request, index) => { const profileLink = request.querySelector('.avatar-card-link')?.href; if (!profileLink || profileLink.includes('/banned-users/')) return; const userIdMatch = profileLink.match(/\/users\/(\d+)\//); if (!userIdMatch) return; const userId = userIdMatch[1]; const [username, followers] = await Promise.all([ fetchUsername(userId), fetchFollowerCount(userId) ]); checksCount++; statusContainer.textContent = `Checking: ${username} (${i + index + 1}/${totalUsers})`; if (followers > 1000) { validUsers.push({ username, followers }); resultsContainer.innerHTML += `<div>${username} (${followers})</div>`; } progressBar.style.width = `${((i + index + 1) / totalUsers) * 100}%`; }); await Promise.all(batchPromises); await new Promise(r => setTimeout(r, PROCESSING_DELAY)); } const nextButton = document.querySelector('.btn-generic-right-sm'); if (!nextButton || nextButton.disabled) { break; } nextButton.click(); currentPage++; await waitForPageLoad(); } } catch (error) { statusContainer.innerHTML = `<span style="color:#ff4444">Error: ${error.message}</span>`; } finally { clearInterval(timerInterval); clearInterval(checksInterval); isProcessing = false; isPaused = false; pauseButton.style.display = 'none'; exportButton.style.display = 'inline-block'; exportButton.textContent = 'Export Results'; } } function waitForPageLoad() { return new Promise((resolve) => { const observer = new MutationObserver((mutations, obs) => { const requests = document.querySelectorAll('.list-item.avatar-card'); if (requests.length > 0) { obs.disconnect(); resolve(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } const startButton = document.createElement('button'); startButton.textContent = 'Start Scraping 🍑'; startButton.style = ` position: fixed; bottom: 20px; right: 20px; padding: 10px 20px; background: #ff9900; color: #000; font-weight: bold; border: none; border-radius: 4px; cursor: pointer; z-index: 99999; `; startButton.addEventListener('click', processRequests); document.body.appendChild(startButton); })();