NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Classic Roblox Groups Layout Restorer
// @namespace https://openuserjs.org/users/kittie
// @author kittie
// @homepage https://www.roblox.com/users/4424578847/profile
// @version 0.6.5
// @copyright 2026, kittie (https://openuserjs.org/users/kittie)
// @license MIT
// @match *://www.roblox.com/groups/*
// @match *://www.roblox.com/communities/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function() {
'use strict';
const style = document.createElement('style');
style.innerHTML = `
.group-details {
min-height: 650px !important;
padding-bottom: 60px !important;
}
.group-header {
background: transparent !important;
border: none !important;
padding: 0 0 15px 0 !important;
position: relative !important;
}
/* 2024-Style Theme-Adaptive Dropdown Menu */
.classic-menu-container {
position: absolute;
top: 0;
right: 0;
}
.classic-dots-btn {
background: transparent;
border: none;
cursor: pointer;
padding: 6px 12px;
font-size: 20px;
color: var(--text-color, inherit);
opacity: 0.7;
border-radius: 8px;
transition: opacity 0.2s, background 0.2s;
}
.classic-dots-btn:hover {
opacity: 1;
background: rgba(128, 128, 128, 0.15);
}
.classic-dropdown-menu {
display: none;
position: absolute;
right: 0;
top: 40px;
background: var(--background-secondary, var(--card-background, #ffffff));
border: 1px solid var(--input-border, rgba(0, 0, 0, 0.15));
border-radius: 12px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
list-style: none;
padding: 8px;
margin: 0;
z-index: 1000;
min-width: 190px;
}
.classic-dropdown-menu.show {
display: block;
}
.classic-dropdown-item {
padding: 10px 14px;
font-size: 14px;
font-weight: 400;
color: var(--text-color, #111);
cursor: pointer;
text-decoration: none;
display: block;
white-space: nowrap;
border-radius: 8px;
transition: background 0.15s ease;
}
.classic-dropdown-item:hover {
background: var(--state-hover, rgba(128, 128, 128, 0.12));
}
/* Tabs Frame */
.rbx-tabs-horizontal {
display: block !important;
border-bottom: 1px solid var(--input-border, rgba(128, 128, 128, 0.3)) !important;
margin: 18px 0 35px 0 !important;
padding-top: 8px !important;
}
.rbx-tabs-horizontal ul#horizontal-tabs {
display: flex !important;
flex-direction: row !important;
list-style: none !important;
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
}
.rbx-tabs-horizontal ul#horizontal-tabs li {
flex: 1 !important;
text-align: center !important;
margin-right: 0 !important;
padding-top: 8px !important;
padding-bottom: 22px !important;
cursor: pointer !important;
font-size: 15px;
color: var(--text-secondary, #555) !important;
opacity: 0.8;
transition: opacity 0.2s ease, color 0.2s ease;
}
.rbx-tabs-horizontal ul#horizontal-tabs li:hover {
opacity: 1;
color: var(--text-color, #000) !important;
}
.rbx-tabs-horizontal ul#horizontal-tabs li.active {
border-bottom: 2px solid var(--text-color, #000) !important;
color: var(--text-color, #000) !important;
opacity: 1 !important;
font-weight: 500 !important;
}
.classic-description-section {
background: transparent !important;
border: none !important;
padding: 0 !important;
margin-bottom: 25px !important;
}
.classic-description-section .container-header {
border: none !important;
padding-left: 0 !important;
margin-bottom: 8px !important;
}
.classic-description-section h2 {
font-size: 18px !important;
font-weight: bold !important;
color: var(--text-color, inherit) !important;
margin: 0 !important;
}
.classic-description-text {
background: transparent !important;
border: none !important;
padding: 0 !important;
color: var(--text-color, inherit) !important;
font-size: 14px;
}
.classic-group-btn {
background: transparent !important;
color: var(--text-color, inherit) !important;
border: 1px solid var(--input-border, rgba(0, 0, 0, 0.3)) !important;
padding: 6px 18px !important;
font-weight: 400 !important;
border-radius: 8px !important;
cursor: pointer !important;
font-size: 13px !important;
display: inline-block !important;
transition: background 0.2s, border-color 0.2s;
}
.classic-group-btn:hover {
background: rgba(128, 128, 128, 0.15) !important;
}
.classic-members-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 15px;
padding: 10px 0;
min-height: 150px;
}
.classic-member-card {
text-align: center;
text-decoration: none;
}
.classic-member-card img {
width: 80px;
height: 80px;
border-radius: 50%;
border: 1px solid var(--input-border, #d0d0d0);
background: var(--background-secondary, #f2f2f2);
}
.classic-member-name {
display: block;
font-size: 12px;
margin-top: 5px;
color: var(--text-color, inherit);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.group-tab-content {
min-height: 620px;
color: var(--text-color, inherit);
}
`;
document.head.appendChild(style);
function getGroupIdFromUrl() {
const match = window.location.pathname.match(/(?:groups|communities)\/(\d+)/);
return match ? match[1] : null;
}
function getCurrentUserId() {
try {
if (window.Roblox && window.Roblox.CurrentUser && window.Roblox.CurrentUser.userId) {
return String(window.Roblox.CurrentUser.userId);
}
} catch(e) {}
const meta = document.querySelector('meta[name="user-data"]');
if (meta && meta.dataset.userid) {
return String(meta.dataset.userid);
}
const profileLink = document.querySelector('.rbx-menu-item[href*="/users/"]');
if (profileLink) {
const match = profileLink.getAttribute('href').match(/\/users\/(\d+)/);
if (match) return match[1];
}
return null;
}
async function robloxFetch(url, options = {}) {
options.credentials = 'include';
let res = await fetch(url, options);
if (res.status === 403) {
const token = res.headers.get('x-csrf-token');
if (token) {
options.headers = options.headers || {};
options.headers['x-csrf-token'] = token;
res = await fetch(url, options);
}
}
return res;
}
async function applyClassicLayout() {
const container = document.querySelector('.group-details');
const groupId = getGroupIdFromUrl();
if (container && groupId && !container.dataset.classicRestored) {
container.dataset.classicRestored = "true";
container.innerHTML = `
<div class="section-content">
<div class="group-header">
<div class="group-image" style="background: rgba(0,0,0,0.05); min-height: 150px;"></div>
<div class="group-caption">
<div class="group-title">
<h1 class="group-name" style="color: var(--text-color, inherit);">Loading group data...</h1>
</div>
</div>
</div>
</div>
`;
try {
const [groupRes, thumbRes] = await Promise.all([
fetch(`https://groups.roblox.com/v1/groups/${groupId}`),
fetch(`https://thumbnails.roblox.com/v1/groups/icons?groupIds=${groupId}&size=150x150&format=Png&isCircular=false`)
]);
const groupData = await groupRes.json();
const thumbData = await thumbRes.json();
const groupName = groupData.name || "Unknown Group";
const descriptionText = groupData.description || "No description provided.";
const memberCount = groupData.memberCount ? groupData.memberCount.toLocaleString() : "0";
const ownerName = groupData.owner ? groupData.owner.username : "Roblox";
const ownerId = groupData.owner ? groupData.owner.userId : null;
const ownerLink = ownerId ? `https://www.roblox.com/users/${ownerId}/profile` : "#";
const groupImg = thumbData.data && thumbData.data.length > 0 ? thumbData.data[0].imageUrl : "";
let membersHTML = `<div style="text-align: center; opacity: 0.7; padding: 25px;">Members list unavailable.</div>`;
const userId = getCurrentUserId();
let isMember = false;
let canConfigure = false;
let isOwner = false;
if (ownerId && userId && String(ownerId) === String(userId)) {
isOwner = true;
canConfigure = true;
}
if (userId) {
try {
const userRoleRes = await robloxFetch(`https://groups.roblox.com/v1/users/${userId}/groups/roles`);
if (userRoleRes.ok) {
const userRoleData = await userRoleRes.json();
const groupMembership = userRoleData.data?.find(item => String(item.group.id) === String(groupId));
if (groupMembership) {
isMember = true;
if (groupMembership.role && groupMembership.role.rank === 255) {
canConfigure = true;
}
}
}
if (!canConfigure && !isOwner) {
const permissionsRes = await robloxFetch(`https://groups.roblox.com/v1/groups/${groupId}/users/${userId}/permissions`);
if (permissionsRes.ok) {
const permData = await permissionsRes.json();
if (permData && (permData.groupChanges || permData.manageLayout || permData.inviteMembers)) {
canConfigure = true;
}
}
}
} catch (e) {
console.error("Permission check error:", e);
}
}
let dropdownItemsHTML = '';
if (canConfigure) {
dropdownItemsHTML += `<a class="classic-dropdown-item" href="https://www.roblox.com/groups/configure?id=${groupId}" target="_blank">Configure Community</a>`;
}
if (isMember && !isOwner) {
dropdownItemsHTML += `<li class="classic-dropdown-item" id="menu-leave-group">Leave Community</li>`;
}
dropdownItemsHTML += `<li class="classic-dropdown-item" id="menu-copy-link">Copy link</li>`;
dropdownItemsHTML += `<li class="classic-dropdown-item" id="menu-report">Report abuse</li>`;
const membershipActionHTML = isMember
? ''
: `<div style="margin-top: 10px;"><button id="group-action-btn" class="classic-group-btn">Join Group</button></div>`;
try {
const membersRes = await fetch(`https://groups.roblox.com/v1/groups/${groupId}/users?limit=12&sortOrder=Asc`, { credentials: 'include' });
if (membersRes.ok) {
const membersData = await membersRes.json();
if (membersData.data && membersData.data.length > 0) {
const userIds = membersData.data.map(m => m.user.userId);
const headshotRes = await fetch(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userIds.join(',')}&size=150x150&format=Png&isCircular=true`);
const headshotData = await headshotRes.json();
const headshots = {};
headshotData.data?.forEach(h => { headshots[h.targetId] = h.imageUrl; });
membersHTML = `<div class="classic-members-grid">`;
membersData.data.forEach(m => {
const mId = m.user.userId;
const mName = m.user.username;
const thumbUrl = headshots[mId] || "";
membersHTML += `
<a href="https://www.roblox.com/users/${mId}/profile" class="classic-member-card">
<img src="${thumbUrl}" alt="${mName}" />
<span class="classic-member-name">${mName}</span>
</a>
`;
});
membersHTML += `</div>`;
}
}
} catch (err) {
console.error("Members fetch error:", err);
}
container.innerHTML = `
<div class="section-content">
<div class="group-header">
<div class="classic-menu-container">
<button class="classic-dots-btn" id="classic-menu-toggle" title="More">•••</button>
<ul class="classic-dropdown-menu" id="classic-dropdown">
${dropdownItemsHTML}
</ul>
</div>
<div class="group-image">
<span class="thumbnail-2d-container">
<img src="${groupImg}" alt="${groupName}" />
</span>
</div>
<div class="group-caption">
<div class="group-title">
<h1 class="group-name" style="color: var(--text-color, inherit);">${groupName}</h1>
<div class="group-owner text-date-hint">
<span>By</span> <a class="text-link" href="${ownerLink}">${ownerName}</a>
</div>
</div>
<div class="group-info">
<ul class="group-stats">
<li class="group-members">
<span class="font-header-2" style="color: var(--text-color, inherit);">${memberCount}</span>
<div class="text-label font-caption-header">Members</div>
</li>
</ul>
${membershipActionHTML}
</div>
</div>
</div>
</div>
<div class="rbx-tab-location">
<div class="rbx-tabs-horizontal">
<ul id="horizontal-tabs" class="nav nav-tabs" role="tablist">
<li id="tab-about" class="rbx-tab group-tab active" data-target="section-about">About</li>
<li id="tab-events" class="rbx-tab group-tab" data-target="section-events">Events</li>
<li id="tab-store" class="rbx-tab group-tab" data-target="section-store">Store</li>
<li id="tab-affiliates" class="rbx-tab group-tab" data-target="section-affiliates">Affiliates</li>
</ul>
</div>
</div>
<div id="section-about" class="group-tab-content">
<div class="classic-description-section">
<div class="container-header">
<h2>Description</h2>
</div>
<div class="classic-description-text">
<p class="text-body" style="white-space: pre-wrap; margin: 0;">${descriptionText}</p>
</div>
</div>
<div class="section members-section" style="background: transparent; border: none; padding: 0;">
<div class="container-header" style="border: none; padding-left: 0;">
<h2>Members</h2>
</div>
<div class="group-section-content" style="padding: 0;">
${membersHTML}
</div>
</div>
</div>
<div id="section-events" class="group-tab-content" style="display:none;">
<div style="padding: 20px 0; text-align: center; opacity: 0.7;">No events available.</div>
</div>
<div id="section-store" class="group-tab-content" style="display:none;">
<div style="padding: 20px 0; text-align: center; opacity: 0.7;">Group store items.</div>
</div>
<div id="section-affiliates" class="group-tab-content" style="display:none;">
<div style="padding: 20px 0; text-align: center; opacity: 0.7;">No affiliates found.</div>
</div>
`;
const menuToggle = container.querySelector('#classic-menu-toggle');
const dropdownMenu = container.querySelector('#classic-dropdown');
if (menuToggle && dropdownMenu) {
menuToggle.addEventListener('click', (e) => {
e.stopPropagation();
dropdownMenu.classList.toggle('show');
});
document.addEventListener('click', () => {
dropdownMenu.classList.remove('show');
});
}
const copyLinkBtn = container.querySelector('#menu-copy-link');
if (copyLinkBtn) {
copyLinkBtn.addEventListener('click', () => {
navigator.clipboard.writeText(window.location.href);
copyLinkBtn.innerText = "Copied!";
setTimeout(() => { copyLinkBtn.innerText = "Copy link"; }, 2000);
});
}
const reportBtn = container.querySelector('#menu-report');
if (reportBtn) {
reportBtn.addEventListener('click', () => {
alert("Reporting functionality redirects or opens modal per standard Roblox behavior.");
});
}
const leaveMenuBtn = container.querySelector('#menu-leave-group');
if (leaveMenuBtn && userId) {
leaveMenuBtn.addEventListener('click', async () => {
if (!confirm("Are you sure you want to leave this community?")) return;
try {
const res = await robloxFetch(`https://groups.roblox.com/v1/groups/${groupId}/users/${userId}`, {
method: 'DELETE'
});
if (res.ok) {
window.location.reload();
} else {
alert("Failed to leave group.");
}
} catch (err) {
console.error("Leave error:", err);
}
});
}
const actionBtn = container.querySelector('#group-action-btn');
if (actionBtn && userId) {
actionBtn.addEventListener('click', async () => {
actionBtn.innerText = "Joining...";
actionBtn.style.opacity = "0.5";
try {
const res = await robloxFetch(`https://groups.roblox.com/v1/groups/${groupId}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
if (res.ok) {
window.location.reload();
} else {
const errJson = await res.json().catch(() => ({}));
alert(`Failed to join group: ${errJson.message || 'Action restricted.'}`);
actionBtn.innerText = "Join Group";
actionBtn.style.opacity = "1";
}
} catch (err) {
console.error("Join error:", err);
actionBtn.innerText = "Join Group";
actionBtn.style.opacity = "1";
}
});
}
const tabs = container.querySelectorAll('#horizontal-tabs li');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const targetId = tab.getAttribute('data-target');
container.querySelectorAll('.group-tab-content').forEach(content => {
content.style.display = content.id === targetId ? 'block' : 'none';
});
});
});
} catch (err) {
console.error("Failed to load group details:", err);
container.innerHTML = `<div class="section-content"><p>Failed to load group details.</p></div>`;
}
}
}
const observer = new MutationObserver(() => {
applyClassicLayout();
});
observer.observe(document.body, { childList: true, subtree: true });
applyClassicLayout();
})();