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