Roblox FriendList Follow Scraper (PH Theme With Remes Hot Updates)

the best in the west

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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