您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays Roblox friends, followers, and following in simple UI.
// ==UserScript== // @name Roblox Friends/Following/Followers List Bypass // @namespace http://tampermonkey.net/ // @version 3.0 // @description Displays Roblox friends, followers, and following in simple UI. // @author @xss https://www.roblox.com/users/4245248422/profile // @match https://www.roblox.com/users/*/profile* // @icon https://www.google.com/s2/favicons?domain=roblox.com // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function() { 'use strict'; const modalStyle = ` .friends-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 400px; background: #2d2d2d; border-radius: 8px; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.3); color: white; font-family: 'Gotham SSm', Arial, sans-serif; } .modal-header { padding: 16px; border-bottom: 1px solid #404040; display: flex; justify-content: space-between; align-items: center; } .modal-title { font-weight: 600; font-size: 18px; } .modal-close { cursor: pointer; padding: 4px; font-size: 20px; } .friends-list { max-height: 60vh; overflow-y: auto; padding: 8px 0; } .friend-item { padding: 12px 16px; display: flex; align-items: center; cursor: pointer; transition: background 0.2s; } .friend-item:hover { background: #3a3a3a; } .friend-name { margin-left: 12px; font-weight: 500; } .loading { padding: 20px; text-align: center; color: #888; } .load-more { display: block; text-align: center; padding: 12px; background: #3a3a3a; cursor: pointer; margin: 8px 16px; border-radius: 4px; transition: background 0.2s; } .load-more:hover { background: #4a4a4a; } .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9998; } `; function createModal(title) { const style = document.createElement('style'); style.textContent = modalStyle; document.head.appendChild(style); const overlay = document.createElement('div'); overlay.className = 'overlay'; 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> `; document.body.appendChild(overlay); document.body.appendChild(modal); modal.querySelector('.modal-close').addEventListener('click', closeModal); overlay.addEventListener('click', closeModal); return modal; } function closeModal() { document.querySelector('.friends-modal')?.remove(); document.querySelector('.overlay')?.remove(); } // Get user details from list of IDs async function getUserDetails(userIds) { if (userIds.length === 0) return []; // Use Roblox's batch API to get user details const response = await fetch('https://users.roblox.com/v1/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userIds: userIds, excludeBannedUsers: false }) }); const data = await response.json(); return data.data || []; } // Display friends list (already has names) function showFriendsList(friends) { const list = document.querySelector('.friends-list'); list.innerHTML = ''; if (friends.length === 0) { list.innerHTML = '<div class="loading">No friends found</div>'; return; } friends.forEach(friend => { const item = document.createElement('div'); item.className = 'friend-item'; item.innerHTML = ` <span class="friend-name">${friend.displayName || friend.name}</span> `; item.addEventListener('click', (e) => { window.open(`https://www.roblox.com/users/${friend.id}/profile`, '_blank'); }); list.appendChild(item); }); } // Fetch all pages of followers/following async function fetchAllRelations(userId, type) { let allRelations = []; let cursor = null; let hasMore = true; let page = 1; const maxPages = 5; // Limit to 50 results (5 pages × 10 items) const list = document.querySelector('.friends-list'); while (hasMore && page <= maxPages) { try { const url = `https://friends.roblox.com/v1/users/${userId}/${type}${cursor ? `?cursor=${cursor}` : ''}`; const response = await fetch(url); const data = await response.json(); if (data.data && data.data.length > 0) { allRelations = [...allRelations, ...data.data]; // Update loading message list.innerHTML = `<div class="loading">Loading... (Found ${allRelations.length} so far)</div>`; // Check if there's more to load if (data.nextPageCursor) { cursor = data.nextPageCursor; page++; } else { hasMore = false; } } else { hasMore = false; } } catch (error) { console.error(`Error fetching ${type}:`, error); hasMore = false; } } return allRelations; } // Display followers/following with pagination async function showRelationsList(userId, type, title) { const list = document.querySelector('.friends-list'); list.innerHTML = `<div class="loading">Loading ${title.toLowerCase()}...</div>`; try { // Fetch all pages const relations = await fetchAllRelations(userId, type); if (relations.length === 0) { list.innerHTML = `<div class="loading">No ${title.toLowerCase()} found</div>`; return; } list.innerHTML = `<div class="loading">Found ${relations.length} ${title.toLowerCase()}. Loading details...</div>`; // Process in batches of 100 to avoid API limits const batchSize = 100; for (let i = 0; i < relations.length; i += batchSize) { const batch = relations.slice(i, i + batchSize); const userIds = batch.map(item => item.id); const userDetails = await getUserDetails(userIds); // If this is the first batch, clear the list if (i === 0) { list.innerHTML = ''; } // Append this batch of users userDetails.forEach(user => { const item = document.createElement('div'); item.className = 'friend-item'; item.innerHTML = ` <span class="friend-name">${user.displayName || user.name}</span> `; item.addEventListener('click', (e) => { window.open(`https://www.roblox.com/users/${user.id}/profile`, '_blank'); }); list.appendChild(item); }); // Show progress if there are more batches if (i + batchSize < relations.length) { const progressElement = document.createElement('div'); progressElement.className = 'loading'; progressElement.textContent = `Loaded ${i + batch.length} of ${relations.length}...`; list.appendChild(progressElement); // Remove the progress element after next batch is loaded setTimeout(() => { progressElement.remove(); }, 100); } } // Add a note if we hit the maximum if (relations.length === 50) { const note = document.createElement('div'); note.className = 'loading'; note.textContent = 'Showing 50 results'; list.appendChild(note); } } catch (error) { list.innerHTML = `<div class="loading">Failed to load ${title.toLowerCase()} details</div>`; console.error(error); } } function createButton(text, color, hoverColor) { const button = document.createElement('div'); button.textContent = text; button.style.cssText = ` display: inline-flex; align-items: center; padding: 6px 12px; background-color: ${color}; color: white; border-radius: 4px; font-family: 'Gotham SSm', Arial, sans-serif; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.2s; margin-left: 12px; vertical-align: middle; position: relative; top: -2px; `; button.addEventListener('mouseover', () => { button.style.backgroundColor = hoverColor; button.style.transform = 'translateY(-1px)'; }); button.addEventListener('mouseout', () => { button.style.backgroundColor = color; button.style.transform = 'none'; }); return button; } function addButtons() { const userId = window.location.pathname.split('/')[2]; const nameContainer = document.querySelector('.profile-header-title-container'); if (!nameContainer || nameContainer.querySelector('.friends-list-button')) return; // Friends Button const friendsButton = createButton('Friends', '#ff69b4', '#e0529c'); friendsButton.className = 'friends-list-button'; friendsButton.addEventListener('click', async (e) => { e.stopPropagation(); createModal('Friends List'); try { const response = await fetch(`https://friends.roblox.com/v1/users/${userId}/friends`); const data = await response.json(); showFriendsList(data.data); } catch (error) { document.querySelector('.friends-list').innerHTML = '<div class="loading">Failed to load friends list</div>'; } }); // Followers Button const followersButton = createButton('Followers', '#5865F2', '#4752c4'); followersButton.addEventListener('click', async (e) => { e.stopPropagation(); createModal('Followers'); showRelationsList(userId, 'followers', 'Followers'); }); // Following Button const followingButton = createButton('Following', '#57A559', '#458a47'); followingButton.addEventListener('click', async (e) => { e.stopPropagation(); createModal('Following'); showRelationsList(userId, 'followings', 'Following'); }); nameContainer.appendChild(friendsButton); nameContainer.appendChild(followersButton); nameContainer.appendChild(followingButton); } // Run when the page is fully loaded window.addEventListener('load', addButtons); // Also run on navigation (for when users navigate between profiles) let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; setTimeout(addButtons, 500); } }).observe(document, {subtree: true, childList: true}); })();