Roblox Friends/Followers/Following Bypass (Old/New Layout Support?)

Adds Friends/Followers/Following buttons back to UK users.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Roblox Friends/Followers/Following Bypass (Old/New Layout Support?)
// @namespace    http://tampermonkey.net/
// @version      3.6
// @description  Adds Friends/Followers/Following buttons back to UK users.
// @match        https://www.roblox.com/users/*/profile*
// @grant        GM_xmlhttpRequest
// @license MIT
// ==/UserScript==

(() => {
    "use strict";

    /* ============================================
       BASIC HELPERS
    ============================================ */

    const $ = s => document.querySelector(s);
    const $$ = s => Array.from(document.querySelectorAll(s));

    // Recursive Shadow DOM selector
    const deepQuerySelector = (root, selector) => {
        let found = root.querySelector(selector);
        if (found) return found;

        const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
        let node;

        while ((node = walker.nextNode())) {
            if (node.shadowRoot) {
                found = deepQuerySelector(node.shadowRoot, selector);
                if (found) return found;
            }
        }
        return null;
    };

    /* ============================================
       MODAL SYSTEM
    ============================================ */

    const modalCSS = `
        .friends-modal {
            position: fixed;
            top: 50%; left: 50%;
            transform: translate(-50%, -50%);
            width: 400px;
            background: #2d2d2d;
            border-radius: 8px;
            z-index: 999999;
            color: white;
            font-family: 'Gotham SSm', Arial, sans-serif;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
        }
        .modal-header {
            padding: 16px;
            display: flex;
            justify-content: space-between;
            border-bottom: 1px solid #444;
        }
        .modal-close {
            cursor: pointer;
            font-size: 22px;
        }
        .modal-title {
            font-size: 18px;
            font-weight: 600;
        }
        .friends-list {
            max-height: 60vh;
            overflow-y: auto;
        }
        .friend-item {
            padding: 12px 16px;
            cursor: pointer;
            transition: background 0.15s;
        }
        .friend-item:hover {
            background: #3a3a3a;
        }
        .friend-name {
            font-weight: 500;
        }
        .overlay {
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background: rgba(0,0,0,0.55);
            z-index: 999998;
        }
        .loading {
            padding: 20px;
            text-align: center;
            color: #888;
        }
    `;

    const injectModalCSS = () => {
        if ($("#friends-modal-style")) return;
        const style = document.createElement("style");
        style.id = "friends-modal-style";
        style.textContent = modalCSS;
        document.head.appendChild(style);
    };

    const closeModal = () => {
        $(".overlay")?.remove();
        $(".friends-modal")?.remove();
    };

    const createModal = title => {
        injectModalCSS();

        const overlay = document.createElement("div");
        overlay.className = "overlay";
        overlay.onclick = closeModal;

        const modal = document.createElement("div");
        modal.className = "friends-modal";
        modal.innerHTML = `
            <div class="modal-header">
                <div class="modal-title">${title}</div>
                <div class="modal-close">×</div>
            </div>
            <div class="friends-list">
                <div class="loading">Loading...</div>
            </div>
        `;
        modal.querySelector(".modal-close").onclick = closeModal;

        document.body.append(overlay, modal);
        return modal;
    };

    /* ============================================
       API HELPERS
    ============================================ */

    const fetchUsers = async ids => {
        if (!ids.length) return [];
        const r = await fetch("https://users.roblox.com/v1/users", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ userIds: ids, excludeBannedUsers: false })
        });
        const j = await r.json();
        return j.data || [];
    };

    const fetchRelations = async (userId, type) => {
        const out = [];
        let cursor = null;
        for (let i = 0; i < 5; i++) {
            const url = `https://friends.roblox.com/v1/users/${userId}/${type}${cursor ? `?cursor=${cursor}` : ""}`;
            const r = await fetch(url);
            const j = await r.json();
            if (!j.data?.length) break;
            out.push(...j.data);
            cursor = j.nextPageCursor;
            if (!cursor) break;
        }
        return out;
    };

    /* ============================================
       BUTTON CREATION
    ============================================ */

    const createButton = (text, color, hover) => {
        const btn = document.createElement("div");
        btn.textContent = text;
        btn.style.cssText = `
            display: inline-flex;
            align-items: center;
            justify-content: center;
            padding: 6px 14px;
            background: ${color};
            color: white;
            border-radius: 9999px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all .15s ease;
        `;
        btn.onmouseenter = () => {
            btn.style.background = hover;
            btn.style.transform = "translateY(-1px)";
        };
        btn.onmouseleave = () => {
            btn.style.background = color;
            btn.style.transform = "none";
        };
        return btn;
    };

    /* ============================================
       SHOW FRIENDS
    ============================================ */

    const showFriends = async userId => {
        const modal = createModal("Friends");
        const list = modal.querySelector(".friends-list");

        const r = await fetch(`https://friends.roblox.com/v1/users/${userId}/friends`);
        const data = await r.json();

        list.innerHTML = "";

        data.data.forEach(u => {
            const item = document.createElement("div");
            item.className = "friend-item";

            if (u.id === -1 || !u.name) {
                item.innerHTML = `<span class="friend-name" style="opacity:0.6; font-size:14px;">(Hidden User)</span>`;
                list.appendChild(item);
                return;
            }

            item.innerHTML = `<span class="friend-name">${u.displayName || u.name}</span>`;
            item.onclick = () => window.open(`https://www.roblox.com/users/${u.id}/profile`);
            list.appendChild(item);
        });
    };

    /* ============================================
       SHOW FOLLOWERS / FOLLOWING
    ============================================ */

    const showRelationModal = async (userId, type, title) => {
        const modal = createModal(title);
        const list = modal.querySelector(".friends-list");

        const rel = await fetchRelations(userId, type);
        if (!rel.length) {
            list.innerHTML = `<div class="loading">No ${title.toLowerCase()}</div>`;
            return;
        }

        list.innerHTML = "";

        for (let i = 0; i < rel.length; i += 100) {
            const batch = rel.slice(i, i + 100);
            const users = await fetchUsers(batch.map(r => r.id));

            users.forEach(u => {
                const item = document.createElement("div");
                item.className = "friend-item";

                if (u.id === -1 || !u.name) {
                    item.innerHTML = `<span class="friend-name" style="opacity:0.6; font-size:14px;">(Hidden User)</span>`;
                    list.appendChild(item);
                    return;
                }

                item.innerHTML = `<span class="friend-name">${u.displayName || u.name}</span>`;
                item.onclick = () => window.open(`https://www.roblox.com/users/${u.id}/profile`);
                list.appendChild(item);
            });
        }
    };

    /* ============================================
       INJECTION TARGET
    ============================================ */

    const detectLayout = () => {
        const title = deepQuerySelector(document, ".profile-header-title-container");
        const oldStats = deepQuerySelector(document, "ul.profile-header-social-counts");

        return {
            title,
            isOld: !!oldStats
        };
    };

    /* ============================================
       INJECT BUTTONS
    ============================================ */

    const injectButtons = () => {
        const userId = location.pathname.split("/")[2];

        const { title, isOld } = detectLayout();
        if (!title) return;

        if (title.querySelector(".friends-list-button-container")) return;

        const container = document.createElement("div");
        container.className = "friends-list-button-container";
        container.style.cssText = `
            display: inline-flex;
            gap: 6px;
            margin-left: 12px;
            vertical-align: middle;
        `;

        const smallStyle = isOld ? "padding: 4px 11px; font-size: 13px; border-radius: 10px;" : "";

        const b1 = createButton("Friends", "#ff69b4", "#e0529c");
        const b2 = createButton("Followers", "#5865F2", "#4752c4");
        const b3 = createButton("Following", "#57A559", "#458a47");

        if (isOld) [b1, b2, b3].forEach(b => b.style.cssText += smallStyle);

        b1.onclick = () => showFriends(userId);
        b2.onclick = () => showRelationModal(userId, "followers", "Followers");
        b3.onclick = () => showRelationModal(userId, "followings", "Following");

        container.append(b1, b2, b3);
        title.appendChild(container);
    };

    /* ============================================
       OBSERVER
    ============================================ */

    const root = $("#rbx-body-content") || document.body;

    new MutationObserver(() => {
        try { injectButtons(); } catch (e) { console.error(e); }
    }).observe(root, { childList: true, subtree: true });

})();