// ==UserScript==
// @name Roblox Multi-Feature User Panel.
// @namespace http://tampermonkey.net/
// @version 1.7
// @description Download Roblox thumbnails, game info, badge info, and user info and the images for all those!
// @author NotRoblox
// @match https://www.roblox.com/userpanel
// @match https://www.roblox.com/getgameinfo
// @match https://www.roblox.com/getbadgeinfo
// @match https://www.roblox.com/getuserinfo
// @match https://www.roblox.com/getgroupinfo
// @grant GM_cookie
// @connect promocodes.roblox.com
// @connect auth.roblox.com
// @connect www.roblox.com
// @match https://www.roblox.com/*
// @match https://promocodes.roblox.com/*
// @match https://auth.roblox.com/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_download
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const style = document.createElement('style');
style.textContent = `
/* Only apply styles to our custom pages */
body.custom-roblox-tool {
background-color: #f4f7f6 !important;
overflow-x: hidden;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
/* Only hide error elements on our custom pages */
body.custom-roblox-tool .content,
body.custom-roblox-tool .request-error-page-content,
body.custom-roblox-tool .default-error-page,
body.custom-roblox-tool .message-container,
body.custom-roblox-tool .error-title,
body.custom-roblox-tool .error-message,
body.custom-roblox-tool .error-image,
body.custom-roblox-tool .action-buttons,
body.custom-roblox-tool #container-main,
body.custom-roblox-tool .error-message-container {
display: none !important;
}
/* Rest of your component styles remain the same, but prefix with body.custom-roblox-tool */
body.custom-roblox-tool .main-content-wrapper {
width: 100%;
padding: 20px;
margin-top: 60px;
margin-bottom: 120px;
display: flex;
flex-direction: column;
align-items: center;
min-height: calc(100vh - 200px);
}
body {
background-color: #f4f7f6 !important;
overflow-x: hidden;
}
body {
font-family: Arial, sans-serif;
background-color: #f4f7f6;
margin: 0;
padding: 0;
}
.main-content-wrapper {
width: 100%;
padding: 20px;
margin-top: 60px; /* Add this line to create space at the top */
margin-bottom: 120px;
display: flex;
flex-direction: column;
align-items: center;
min-height: calc(100vh - 200px);
}
.form-container {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
text-align: center;
margin: 20px auto;
position: relative;
z-index: 1;
}
.input-field {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.submit-button, .panel-button {
background-color: #4CAF50;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 16px;
margin: 10px 0;
}
.submit-button:hover, .panel-button:hover {
background-color: #45a049;
}
.result-container {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 800px;
margin: 20px auto 120px auto;
position: relative;
z-index: 1;
}
.image-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
margin: 20px 0;
}
.image-item {
text-align: center;
}
.image-item img {
max-width: 200px;
border-radius: 8px;
margin-bottom: 10px;
}
.info-text {
margin: 10px 0;
font-size: 16px;
}
.error-message {
color: #ff0000;
margin: 10px 0;
}
.success-message {
color: #4CAF50;
margin: 10px 0;
}
`;
// Add this right after creating the style element
const customPages = ['/userpanel', '/getgameinfo', '/getbadgeinfo', '/getuserinfo', '/getgroupinfo', '/redeemcode'];
if (customPages.includes(window.location.pathname)) {
document.body.setAttribute('data-custom-page', 'true');
}
document.head.appendChild(style);
async function getUserIdFromUsername(username) {
const response = await fetch(`https://users.roblox.com/v1/usernames/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ usernames: [username] })
});
const data = await response.json();
if (!data.data || data.data.length === 0) throw new Error('User not found');
return data.data[0].id;
}
function createBasicForm(placeholder, buttonText) {
const container = document.createElement('div');
container.className = 'form-container';
const input = document.createElement('input');
input.type = 'text';
input.className = 'input-field';
input.placeholder = placeholder;
const button = document.createElement('button');
button.className = 'submit-button';
button.textContent = buttonText;
container.appendChild(input);
container.appendChild(button);
return { container, input, button };
}
function displayMessage(message, isError = false) {
const messageDiv = document.createElement('div');
messageDiv.className = isError ? 'error-message' : 'success-message';
messageDiv.textContent = message;
document.querySelector('.form-container').appendChild(messageDiv);
setTimeout(() => messageDiv.remove(), 5000);
}
function createResultContainer() {
const container = document.createElement('div');
container.className = 'result-container';
return container;
}
async function initializeGameInfo() {
const mainWrapper = document.createElement('div');
mainWrapper.className = 'main-content-wrapper';
document.body.appendChild(mainWrapper);
const { container, input, button } = createBasicForm('Enter Game ID', 'Get Game Info');
mainWrapper.appendChild(container);
const refreshContent = async (gameId) => {
const existingResults = mainWrapper.querySelectorAll('.result-container');
existingResults.forEach(result => result.remove());
try {
// Get the universe ID first
const placeResponse = await fetch(`https://apis.roblox.com/universes/v1/places/${gameId}/universe`);
const placeData = await placeResponse.json();
const universeId = placeData.universeId;
// Fetch all required data
const [gameResponse, iconResponse, thumbnailResponse, votesResponse, favoritesResponse] = await Promise.all([
fetch(`https://games.roblox.com/v1/games?universeIds=${universeId}`),
fetch(`https://thumbnails.roblox.com/v1/games/icons?universeIds=${universeId}&size=512x512&format=Png&isCircular=false`),
fetch(`https://thumbnails.roblox.com/v1/games/${universeId}/thumbnails?size=768x432&format=Png&limit=10`),
fetch(`https://games.roblox.com/v1/games/${universeId}/votes`),
fetch(`https://games.roblox.com/v1/games/${universeId}/favorites/count`),
]);
const [gameData, iconData, thumbnailData, votesData, favoritesData] = await Promise.all([
gameResponse.json(),
iconResponse.json(),
thumbnailResponse.json(),
votesResponse.json(),
favoritesResponse.json()
]);
const resultContainer = createResultContainer();
// Create image container for all images
const imageContainer = document.createElement('div');
imageContainer.className = 'image-container';
// Display game icon
if (iconData.data && iconData.data[0]) {
const iconDiv = document.createElement('div');
iconDiv.className = 'image-item';
const iconImg = document.createElement('img');
iconImg.src = iconData.data[0].imageUrl;
iconImg.alt = 'Game Icon';
iconImg.className = 'game-icon';
const downloadIconBtn = document.createElement('button');
downloadIconBtn.className = 'submit-button';
downloadIconBtn.textContent = 'Download Icon';
downloadIconBtn.onclick = () => GM_download({
url: iconData.data[0].imageUrl,
name: `game_${gameId}_icon.png`
});
iconDiv.appendChild(iconImg);
iconDiv.appendChild(downloadIconBtn);
imageContainer.appendChild(iconDiv);
}
// Display thumbnails
if (thumbnailData.data) {
thumbnailData.data.forEach((thumb, index) => {
const thumbDiv = document.createElement('div');
thumbDiv.className = 'image-item';
const thumbImg = document.createElement('img');
thumbImg.src = thumb.imageUrl;
thumbImg.alt = `Game Thumbnail ${index + 1}`;
thumbImg.className = 'thumbnail-image';
const downloadThumbBtn = document.createElement('button');
downloadThumbBtn.className = 'submit-button';
downloadThumbBtn.textContent = `Download Thumbnail ${index + 1}`;
downloadThumbBtn.onclick = () => GM_download({
url: thumb.imageUrl,
name: `game_${gameId}_thumbnail_${index + 1}.png`
});
thumbDiv.appendChild(thumbImg);
thumbDiv.appendChild(downloadThumbBtn);
imageContainer.appendChild(thumbDiv);
});
}
// Display game information
if (gameData.data && gameData.data[0]) {
const game = gameData.data[0];
const likes = votesData.upVotes || 0;
const dislikes = votesData.downVotes || 0;
const totalVotes = likes + dislikes;
const likeRatio = totalVotes > 0 ? ((likes / totalVotes) * 100).toFixed(1) : 0;
resultContainer.appendChild(imageContainer);
const gameInfo = document.createElement('div');
gameInfo.className = 'info-text';
gameInfo.innerHTML = `
<h2>${game.name}</h2>
<div class="game-stats">
<div class="stat-item">
<h4>👥 Player Stats</h4>
<p>Current Players: ${game.playing?.toLocaleString() || 0}</p>
<p>Total Visits: ${game.visits?.toLocaleString() || 0}</p>
<p>Max Players: ${game.maxPlayers || 'Unknown'}</p>
</div>
<div class="stat-item">
<h4>👍 Ratings</h4>
<p>Likes: ${likes.toLocaleString()}</p>
<p>Dislikes: ${dislikes.toLocaleString()}</p>
<p>Like Ratio: ${likeRatio}%</p>
<p>Favorites: ${favoritesData.favoritesCount?.toLocaleString() || 0}</p>
</div>
<div class="stat-item">
<h4>ℹ️ Details</h4>
<p>Created: ${new Date(game.created).toLocaleDateString()}</p>
<p>Last Updated: ${new Date(game.updated).toLocaleDateString()}</p>
<p>Genre: ${game.genre || 'Not specified'}</p>
<p>Allowed Gear Types: ${game.allowedGearTypes?.join(', ') || 'None'}</p>
</div>
</div>
<div class="game-description">
<h4>📝 Description</h4>
<p>${game.description || 'No description available'}</p>
</div>
<p class="game-link"><a href="https://www.roblox.com/games/${gameId}" target="_blank">🎮 View Game Page</a></p>
`;
resultContainer.appendChild(gameInfo);
}
mainWrapper.appendChild(resultContainer);
} catch (error) {
console.error(error);
displayMessage(error.message, true);
}
};
button.onclick = async () => {
const gameId = input.value.trim();
if (!gameId) {
displayMessage('Please enter a game ID', true);
return;
}
await refreshContent(gameId);
};
}
async function initializeBadgeInfo() {
const mainWrapper = document.createElement('div');
mainWrapper.className = 'main-content-wrapper';
document.body.appendChild(mainWrapper);
const { container, input, button } = createBasicForm('Enter Badge ID', 'Get Badge Info');
mainWrapper.appendChild(container);
const refreshContent = async (badgeId) => {
// Remove any existing result containers
const existingResults = mainWrapper.querySelectorAll('.result-container');
existingResults.forEach(result => result.remove());
try {
// Fetch badge info with proper error handling
const infoResponse = await fetch(`https://badges.roblox.com/v1/badges/${badgeId}`, {
method: 'GET',
credentials: 'include'
});
if (!infoResponse.ok) {
throw new Error('Failed to fetch badge information');
}
const badgeInfo = await infoResponse.json();
// Fetch badge statistics
const statsResponse = await fetch(`https://badges.roblox.com/v1/badges/${badgeId}/statistics`, {
method: 'GET',
credentials: 'include'
});
const statsData = await statsResponse.json();
// Fetch badge icon
const iconResponse = await fetch(`https://thumbnails.roblox.com/v1/badges/icons?badgeIds=${badgeId}&size=150x150&format=Png`, {
method: 'GET',
credentials: 'include'
});
const iconData = await iconResponse.json();
const resultContainer = createResultContainer();
// Create image container
const imageContainer = document.createElement('div');
imageContainer.className = 'image-container';
// Display badge icon if available
if (iconData.data && iconData.data[0]) {
const iconDiv = document.createElement('div');
iconDiv.className = 'image-item';
const iconImg = document.createElement('img');
iconImg.src = iconData.data[0].imageUrl;
iconImg.alt = 'Badge Icon';
iconImg.style.maxWidth = '150px';
const downloadBtn = document.createElement('button');
downloadBtn.className = 'submit-button';
downloadBtn.textContent = 'Download Badge Icon';
downloadBtn.onclick = () => GM_download({
url: iconData.data[0].imageUrl,
name: `badge_${badgeId}.png`
});
iconDiv.appendChild(iconImg);
iconDiv.appendChild(downloadBtn);
imageContainer.appendChild(iconDiv);
}
// Display badge information
const infoDiv = document.createElement('div');
infoDiv.className = 'info-text';
infoDiv.innerHTML = `
<h3>${badgeInfo.name || 'Unknown Badge'}</h3>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0;">
<h4>Badge Details</h4>
<p><strong>Description:</strong> ${badgeInfo.description || 'No description available'}</p>
<p><strong>Enabled:</strong> ${badgeInfo.enabled ? '✅ Yes' : '❌ No'}</p>
<p><strong>Created:</strong> ${new Date(badgeInfo.created).toLocaleDateString()}</p>
<p><strong>Updated:</strong> ${new Date(badgeInfo.updated).toLocaleDateString()}</p>
</div>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0;">
<h4>Statistics</h4>
<p><strong>Win Rate:</strong> ${statsData.winRatePercentage?.toFixed(2) || 0}%</p>
<p><strong>Awarded:</strong> ${statsData.awardedCount?.toLocaleString() || 0} times</p>
</div>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0;">
<h4>Links</h4>
<p><a href="https://www.roblox.com/badges/${badgeId}" target="_blank">View Badge Page</a></p>
${badgeInfo.awardingUniverse ?
`<p><a href="https://www.roblox.com/games/${badgeInfo.awardingUniverse.id}" target="_blank">View Game Page</a></p>`
: ''}
</div>
`;
resultContainer.appendChild(imageContainer);
resultContainer.appendChild(infoDiv);
mainWrapper.appendChild(resultContainer);
displayMessage('Badge information fetched successfully!');
} catch (error) {
const resultContainer = createResultContainer();
resultContainer.innerHTML = `
<div class="error-message" style="padding: 15px; margin-top: 20px; border-radius: 4px;">
<h3>❌ Error</h3>
<p>Failed to fetch badge information: ${error.message}</p>
<p>Please make sure the badge ID is valid and try again.</p>
</div>
`;
mainWrapper.appendChild(resultContainer);
displayMessage(error.message, true);
}
};
button.onclick = async () => {
const badgeId = input.value.trim();
if (!badgeId) {
displayMessage('Please enter a badge ID', true);
return;
}
await refreshContent(badgeId);
};
}
async function initializeUserInfo() {
const mainWrapper = document.createElement('div');
mainWrapper.className = 'main-content-wrapper';
document.body.appendChild(mainWrapper);
const { container, input, button } = createBasicForm('Enter Username', 'Get User Info');
mainWrapper.appendChild(container);
const resultContainer = createResultContainer();
resultContainer.style.display = 'none';
mainWrapper.appendChild(resultContainer);
button.onclick = async () => {
try {
const username = input.value.trim();
if (!username) throw new Error('Please enter a username');
const userId = await getUserIdFromUsername(username);
const [
userInfoResponse,
presenceResponse,
friendsResponse,
followersResponse,
thumbnailResponse,
bustResponse,
headshotResponse
] = await Promise.all([
fetch(`https://users.roblox.com/v1/users/${userId}`),
fetch(`https://presence.roblox.com/v1/presence/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds: [userId] })
}),
fetch(`https://friends.roblox.com/v1/users/${userId}/friends/count`),
fetch(`https://friends.roblox.com/v1/users/${userId}/followers/count`),
fetch(`https://thumbnails.roblox.com/v1/users/avatar?userIds=${userId}&size=420x420&format=Png`),
fetch(`https://thumbnails.roblox.com/v1/users/avatar-bust?userIds=${userId}&size=420x420&format=Png`),
fetch(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&size=420x420&format=Png`)
]);
const [userInfo, presence, friends, followers, thumbnail, bust, headshot] = await Promise.all([
userInfoResponse.json(),
presenceResponse.json(),
friendsResponse.json(),
followersResponse.json(),
thumbnailResponse.json(),
bustResponse.json(),
headshotResponse.json()
]);
resultContainer.innerHTML = '';
// Create thumbnails section
const imageContainer = document.createElement('div');
imageContainer.className = 'image-container';
const createImageSection = (data, type) => {
if (data.data && data.data[0]) {
const div = document.createElement('div');
div.className = 'image-item';
const img = document.createElement('img');
img.src = data.data[0].imageUrl;
img.alt = `${type} thumbnail`;
const downloadBtn = document.createElement('button');
downloadBtn.className = 'submit-button';
downloadBtn.textContent = `Download ${type}`;
downloadBtn.onclick = () => GM_download({
url: data.data[0].imageUrl,
name: `${username}_${type}.png`
});
div.appendChild(img);
div.appendChild(downloadBtn);
imageContainer.appendChild(div);
}
};
createImageSection(thumbnail, 'Full Avatar');
createImageSection(bust, 'Bust');
createImageSection(headshot, 'Headshot');
// Create user info section
const userInfoDiv = document.createElement('div');
userInfoDiv.className = 'info-text';
const userPresence = presence.userPresences[0];
userInfoDiv.innerHTML = `
<h3>User Information for ${userInfo.displayName} (${userInfo.name})</h3>
<p>User ID: ${userId}</p>
<p>Display Name: ${userInfo.displayName}</p>
<p>Username: ${userInfo.name}</p>
<p>Description: ${userInfo.description ? userInfo.description : 'No description available'}</p>
<p>Account Age: ${userInfo.age} days</p>
<p>Join Date: ${new Date(userInfo.created).toLocaleDateString()}</p>
<p>Online Status: ${userPresence.userPresenceType === 0 ? 'Offline' : userPresence.userPresenceType === 1 ? 'Online' : 'Playing'}</p>
<p>Friends Count: ${friends.count}</p>
<p>Followers Count: ${followers.count}</p>
<p>Profile Link: <a href="https://www.roblox.com/users/${userId}/profile" target="_blank">View Profile</a></p>
${userPresence.userPresenceType !== 0 ? `<p>Last Location: ${userPresence.lastLocation}</p>` : ''}
<p>
<a href="https://www.roblox.com/abusereport/userprofile?id=${userId}" target="_blank">Report User</a> |
<a href="https://www.roblox.com/illegal-content-reporting" target="_blank">Report to DMCA</a>
</p>
`;
// Create container for dynamic content
const dynamicContentDiv = document.createElement('div');
dynamicContentDiv.id = 'dynamic-content';
// Create buttons container
const buttonsDiv = document.createElement('div');
buttonsDiv.className = 'buttons-container';
// Username History Button
const historyButton = document.createElement('button');
historyButton.className = 'submit-button';
historyButton.textContent = 'Show Username History';
historyButton.onclick = async () => {
try {
const historyResponse = await fetch(`https://users.roblox.com/v1/users/${userId}/username-history`);
const historyData = await historyResponse.json();
const historyList = historyData.data.map((entry) => `<li>${entry.name}</li>`).join('');
dynamicContentDiv.innerHTML = `<h4>Username History:</h4><ul>${historyList || '<li>No username changes found</li>'}</ul>`;
} catch (error) {
displayMessage('Failed to fetch username history', true);
}
};
// Outfits Button
const outfitsButton = document.createElement('button');
outfitsButton.className = 'submit-button';
outfitsButton.textContent = 'Show User Outfits';
outfitsButton.onclick = async () => {
try {
const outfitsResponse = await fetch(`https://avatar.roblox.com/v1/users/${userId}/outfits?page=1&itemsPerPage=50`);
const outfitsData = await outfitsResponse.json();
if (!outfitsData.data || outfitsData.data.length === 0) {
dynamicContentDiv.innerHTML = '<p>No outfits found</p>';
return;
}
// Create outfits grid container
const outfitsGrid = document.createElement('div');
outfitsGrid.style.display = 'grid';
outfitsGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(200px, 1fr))';
outfitsGrid.style.gap = '20px';
outfitsGrid.style.padding = '20px';
outfitsGrid.style.marginTop = '20px';
// Clear previous content and add header
dynamicContentDiv.innerHTML = '<h4>User Outfits:</h4>';
// Create download buttons container
const downloadButtonsContainer = document.createElement('div');
downloadButtonsContainer.style.gridColumn = '1 / -1';
downloadButtonsContainer.style.marginBottom = '20px';
downloadButtonsContainer.style.display = 'flex';
downloadButtonsContainer.style.gap = '10px';
downloadButtonsContainer.style.justifyContent = 'center';
// Create ZIP download button
const downloadZipButton = document.createElement('button');
downloadZipButton.className = 'submit-button';
downloadZipButton.textContent = 'Download ZIP';
downloadZipButton.addEventListener('click', async () => {
try {
displayMessage('Preparing ZIP file...');
const zip = new JSZip();
const thumbnailResponse = await fetch(`https://thumbnails.roblox.com/v1/users/outfits?userOutfitIds=${outfitsData.data.map(outfit => outfit.id).join(',')}&size=420x420&format=Png`);
const thumbnailData = await thumbnailResponse.json();
const imagePromises = thumbnailData.data.map(async (item, index) => {
if (item.imageUrl) {
const outfitName = outfitsData.data[index]?.name || `Outfit_${item.targetId}`;
const sanitizedName = outfitName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
const filename = `${sanitizedName}_${item.targetId}.png`;
try {
const imageResponse = await fetch(item.imageUrl);
const imageBlob = await imageResponse.blob();
zip.file(filename, imageBlob);
return true;
} catch (error) {
console.error(`Failed to fetch image for ${filename}:`, error);
return false;
}
}
return false;
});
await Promise.all(imagePromises);
const zipBlob = await zip.generateAsync({type: "blob"});
const downloadUrl = URL.createObjectURL(zipBlob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `${username}_outfits.zip`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(downloadUrl);
displayMessage('All outfit images have been downloaded as ZIP!');
} catch (error) {
displayMessage('Failed to create ZIP file: ' + error.message, true);
}
});
// Create individual downloads button
const downloadAllButton = document.createElement('button');
downloadAllButton.className = 'submit-button';
downloadAllButton.textContent = 'Download All';
downloadAllButton.addEventListener('click', async () => {
try {
const thumbnailResponse = await fetch(`https://thumbnails.roblox.com/v1/users/outfits?userOutfitIds=${outfitsData.data.map(outfit => outfit.id).join(',')}&size=420x420&format=Png`);
const thumbnailData = await thumbnailResponse.json();
thumbnailData.data.forEach((item, index) => {
if (item.imageUrl) {
const outfitName = outfitsData.data[index]?.name || `Outfit_${item.targetId}`;
const sanitizedName = outfitName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
GM_download({
url: item.imageUrl,
name: `${sanitizedName}_${item.targetId}.png`
});
}
});
displayMessage('Started downloading all outfit images!');
} catch (error) {
displayMessage('Failed to download images: ' + error.message, true);
}
});
// Add buttons to container
downloadButtonsContainer.appendChild(downloadZipButton);
downloadButtonsContainer.appendChild(downloadAllButton);
// Add container to page
dynamicContentDiv.appendChild(downloadButtonsContainer);
dynamicContentDiv.appendChild(outfitsGrid);
// Get outfit thumbnails and create cards
const outfitIds = outfitsData.data.map(outfit => outfit.id).filter(id => id);
if (outfitIds.length === 0) {
dynamicContentDiv.innerHTML = '<p>No valid outfits found</p>';
return;
}
const thumbnailResponse = await fetch(`https://thumbnails.roblox.com/v1/users/outfits?userOutfitIds=${outfitIds.join(',')}&size=420x420&format=Png`);
const thumbnailData = await thumbnailResponse.json();
const thumbnailMap = new Map(thumbnailData.data.map(item => [item.targetId, item.imageUrl]));
// Create outfit cards
outfitsData.data.forEach(outfit => {
if (!outfit || !outfit.id) return;
const outfitCard = document.createElement('div');
outfitCard.style.textAlign = 'center';
outfitCard.style.border = '1px solid #ccc';
outfitCard.style.borderRadius = '8px';
outfitCard.style.padding = '10px';
outfitCard.style.backgroundColor = '#ffffff';
outfitCard.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
const thumbnailUrl = thumbnailMap.get(outfit.id);
if (thumbnailUrl) {
const img = document.createElement('img');
img.src = thumbnailUrl;
img.alt = outfit.name;
img.style.width = '200px';
img.style.height = '200px';
img.style.objectFit = 'contain';
img.style.borderRadius = '4px';
const title = document.createElement('h4');
title.style.margin = '10px 0';
title.style.color = '#333';
title.textContent = outfit.name;
const downloadButton = document.createElement('button');
downloadButton.className = 'submit-button';
downloadButton.style.margin = '5px 0';
downloadButton.textContent = 'Download Image';
downloadButton.addEventListener('click', () => {
GM_download({
url: thumbnailUrl,
name: `${outfit.name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_${outfit.id}.png`
});
});
outfitCard.appendChild(img);
outfitCard.appendChild(title);
outfitCard.appendChild(downloadButton);
}
outfitsGrid.appendChild(outfitCard);
});
} catch (error) {
console.error('Error fetching outfits:', error);
dynamicContentDiv.innerHTML = '<p>Failed to fetch outfits</p>';
displayMessage('Failed to fetch outfits', true);
}
};
// Current Assets Button
const currentAssetsButton = document.createElement('button');
currentAssetsButton.className = 'submit-button';
currentAssetsButton.textContent = 'Show Current Assets';
currentAssetsButton.onclick = async () => {
try {
const currentWearingResponse = await fetch(`https://avatar.roblox.com/v1/users/${userId}/currently-wearing`);
const currentWearingData = await currentWearingResponse.json();
const assetsList = await Promise.all(currentWearingData.assetIds.map(async (assetId) => {
try {
// Use the correct catalog endpoint
const assetResponse = await fetch(`https://catalog.roblox.com/v1/assets/${assetId}/details`);
if (!assetResponse.ok) {
return `
<li style="margin-bottom: 10px;">
<div>Asset #${assetId}</div>
<a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset</a>
</li>
`;
}
const assetData = await assetResponse.json();
return `
<li style="margin-bottom: 10px;">
<div>${assetData.name || `Asset #${assetId}`}</div>
<a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset</a>
</li>
`;
} catch (err) {
// Fallback for any errors
return `
<li style="margin-bottom: 10px;">
<div>Asset #${assetId}</div>
<a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset</a>
</li>
`;
}
}));
dynamicContentDiv.innerHTML = `
<h4>Currently Wearing:</h4>
<ul style="list-style: none; padding: 0;">${assetsList.join('') || '<li>No assets found</li>'}</ul>
`;
} catch (error) {
displayMessage('Failed to fetch current assets: ' + error.message, true);
}
};
// Add buttons to container
buttonsDiv.appendChild(historyButton);
buttonsDiv.appendChild(outfitsButton);
buttonsDiv.appendChild(currentAssetsButton);
// Append everything to result container
resultContainer.appendChild(imageContainer);
resultContainer.appendChild(userInfoDiv);
resultContainer.appendChild(buttonsDiv);
resultContainer.appendChild(dynamicContentDiv);
resultContainer.style.display = 'block';
displayMessage('User information fetched successfully!');
} catch (error) {
displayMessage(error.message, true);
}
};
}
// Updated getOutfitAssets function
async function getOutfitAssets(outfitId) {
try {
const response = await fetch(`https://catalog.roblox.com/v1/outfits/${outfitId}/get-outfit-details`);
const data = await response.json();
if (!data.assetIds || data.assetIds.length === 0) {
throw new Error('No assets found');
}
const assetsList = await Promise.all(data.assetIds.map(async (assetId) => {
try {
const assetResponse = await fetch(`https://catalog.roblox.com/v1/catalog/items/${assetId}/details`);
const assetData = await assetResponse.json();
return `
<li style="margin-bottom: 10px;">
<div>${assetData.name || 'Unknown Asset'}</div>
<a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset (ID: ${assetId})</a>
</li>
`;
} catch (err) {
return `
<li style="margin-bottom: 10px;">
<div>Asset ID: ${assetId}</div>
<a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset</a>
</li>
`;
}
}));
const dynamicContentDiv = document.getElementById('dynamic-content');
dynamicContentDiv.innerHTML = `
<h4>Outfit Assets:</h4>
<ul style="list-style: none; padding: 0;">${assetsList.join('') || '<li>No assets found</li>'}</ul>
<button class="submit-button" onclick="document.querySelector('[data-action=\'Show User Outfits\']').click()">Back to Outfits</button>
`;
} catch (error) {
console.error('Error fetching outfit assets:', error);
displayMessage('Failed to fetch outfit assets: ' + error.message, true);
}
}
async function initializeGroupInfo() {
const mainWrapper = document.createElement('div');
mainWrapper.className = 'main-content-wrapper';
document.body.appendChild(mainWrapper);
const { container, input, button } = createBasicForm('Enter Group ID', 'Get Group Info');
mainWrapper.appendChild(container);
const refreshContent = async (groupId) => {
// Remove any existing result containers
const existingResults = mainWrapper.querySelectorAll('.result-container');
existingResults.forEach(result => result.remove());
try {
// Fetch all group data in parallel
const [
groupResponse,
membersResponse,
iconResponse,
rolesResponse,
wallResponse,
settingsResponse,
socialLinksResponse,
recentPosts
] = await Promise.all([
fetch(`https://groups.roblox.com/v1/groups/${groupId}`),
fetch(`https://groups.roblox.com/v1/groups/${groupId}/membership`),
fetch(`https://thumbnails.roblox.com/v1/groups/icons?groupIds=${groupId}&size=420x420&format=Png`),
fetch(`https://groups.roblox.com/v1/groups/${groupId}/roles`),
fetch(`https://groups.roblox.com/v2/groups/${groupId}/wall/posts?limit=10&sortOrder=Desc`),
fetch(`https://groups.roblox.com/v1/groups/${groupId}/settings`),
fetch(`https://groups.roblox.com/v1/groups/${groupId}/social-links`),
fetch(`https://groups.roblox.com/v2/groups/${groupId}/wall/posts?limit=5&sortOrder=Desc`)
]);
const [groupInfo, membersInfo, iconData, rolesInfo, wallData, settings, socialLinks, recentPostsData] = await Promise.all([
groupResponse.json(),
membersResponse.json(),
iconResponse.json(),
rolesResponse.json(),
wallResponse.json(),
settingsResponse.json(),
socialLinksResponse.json(),
recentPosts.json()
]);
const resultContainer = createResultContainer();
// Create image container for group icon
const imageContainer = document.createElement('div');
imageContainer.className = 'image-container';
// Display group icon
if (iconData.data && iconData.data[0]) {
const iconDiv = document.createElement('div');
iconDiv.className = 'image-item';
const iconImg = document.createElement('img');
iconImg.src = iconData.data[0].imageUrl;
iconImg.alt = 'Group Icon';
const downloadBtn = document.createElement('button');
downloadBtn.className = 'submit-button';
downloadBtn.textContent = 'Download Group Icon';
downloadBtn.onclick = () => GM_download({
url: iconData.data[0].imageUrl,
name: `group_${groupId}_icon.png`
});
iconDiv.appendChild(iconImg);
iconDiv.appendChild(downloadBtn);
imageContainer.appendChild(iconDiv);
}
// Calculate group age in days
const groupAge = Math.floor((new Date() - new Date(groupInfo.created)) / (1000 * 60 * 60 * 24));
// Format roles with more details
const rolesHtml = rolesInfo.roles.map(role => `
<li style="margin-bottom: 10px; padding: 5px; border: 1px solid #eee; border-radius: 4px;">
<strong>${role.name}</strong><br>
Members: ${role.memberCount}<br>
Rank: ${role.rank}<br>
${role.description ? `Description: ${role.description}<br>` : ''}
</li>
`).join('');
// Format recent posts
const recentPostsHtml = recentPostsData.data ? recentPostsData.data.map(post => `
<li style="margin-bottom: 15px; padding: 10px; border: 1px solid #eee; border-radius: 4px;">
<strong>${post.poster.username}</strong> - ${new Date(post.created).toLocaleString()}<br>
${post.body}
</li>
`).join('') : '';
// Format social links
const socialLinksHtml = socialLinks.data ? socialLinks.data.map(link => `
<li><strong>${link.title}</strong>: <a href="${link.url}" target="_blank">${link.url}</a></li>
`).join('') : '';
// Display group information
const infoDiv = document.createElement('div');
infoDiv.className = 'info-text';
infoDiv.innerHTML = `
<h2 style="color: #333; margin-bottom: 20px;">${groupInfo.name}</h2>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3>Basic Information</h3>
<p><strong>Group ID:</strong> ${groupId}</p>
<p><strong>Owner:</strong> ${groupInfo.owner ? `<a href="https://www.roblox.com/users/${groupInfo.owner.userId}/profile" target="_blank">${groupInfo.owner.username}</a>` : 'No owner'}</p>
<p><strong>Created:</strong> ${new Date(groupInfo.created).toLocaleString()} (${groupAge} days ago)</p>
<p><strong>Member Count:</strong> ${membersInfo.memberCount?.toLocaleString() || 0}</p>
<p><strong>Description:</strong> ${groupInfo.description || 'No description'}</p>
</div>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3>Group Settings</h3>
<p><strong>Public Entry:</strong> ${groupInfo.publicEntryAllowed ? 'Yes' : 'No'}</p>
<p><strong>Group Status:</strong> ${groupInfo.isLocked ? 'Locked' : 'Active'}</p>
<p><strong>Membership Type:</strong> ${groupInfo.publicEntryAllowed ? 'Anyone can join' : 'Approval required'}</p>
<p><strong>Verified:</strong> ${groupInfo.hasVerifiedBadge ? 'Yes' : 'No'}</p>
</div>
${socialLinks.data && socialLinks.data.length > 0 ? `
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3>Social Links</h3>
<ul style="list-style-type: none; padding-left: 0;">
${socialLinksHtml}
</ul>
</div>
` : ''}
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3>Quick Links</h3>
<p><a href="https://www.roblox.com/groups/${groupId}" target="_blank">View Group Page</a></p>
<p><a href="https://www.roblox.com/groups/${groupId}/membership" target="_blank">View Members</a></p>
<p><a href="https://www.roblox.com/abusereport/group?id=${groupId}" target="_blank">Report Group</a></p>
</div>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3>Roles (${rolesInfo.roles.length})</h3>
<ul style="list-style-type: none; padding-left: 0;">
${rolesHtml}
</ul>
</div>
${recentPostsData.data && recentPostsData.data.length > 0 ? `
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3>Recent Wall Posts</h3>
<ul style="list-style-type: none; padding-left: 0;">
${recentPostsHtml}
</ul>
</div>
` : ''}
`;
resultContainer.appendChild(imageContainer);
resultContainer.appendChild(infoDiv);
mainWrapper.appendChild(resultContainer);
displayMessage('Group information fetched successfully!');
} catch (error) {
displayMessage(error.message, true);
}
};
button.onclick = async () => {
const groupId = input.value.trim();
if (!groupId) {
displayMessage('Please enter a group ID', true);
return;
}
await refreshContent(groupId);
};
}
async function initializeCodeRedemption() {
const mainWrapper = document.createElement('div');
mainWrapper.className = 'main-content-wrapper';
document.body.appendChild(mainWrapper);
const { container, input, button } = createBasicForm('Enter Code', 'Redeem Code');
const autoRedeemButton = document.createElement('button');
autoRedeemButton.className = 'submit-button';
autoRedeemButton.style.backgroundColor = '#ff4444';
autoRedeemButton.textContent = 'Auto-Redeem All Active Codes';
container.appendChild(autoRedeemButton);
const showCodesButton = document.createElement('button');
showCodesButton.className = 'submit-button';
showCodesButton.style.backgroundColor = '#4a90e2'; // Blue color to distinguish it
showCodesButton.textContent = 'Show Available Codes';
container.appendChild(showCodesButton);
// Known codes list with additional information
const availableCodes = [
{
code: 'SPIDERCOLA',
reward: 'Spider Cola Shoulder Pet',
expires: 'No expiration'
},
{
code: 'TWEETROBLOX',
reward: 'The Bird Says Shoulder Pet',
expires: 'No expiration'
},
{
code: 'ROBLOXEDU2023',
reward: 'School Backpack Accessory',
expires: 'No expiration'
},
{
code: 'AMAZONFRIEND2024',
reward: 'Amazon Prime Gaming Reward',
expires: 'March 31, 2024'
},
{
code: 'BRICKMASTER2024',
reward: 'Special Avatar Item',
expires: 'December 31, 2024'
},
{
code: 'ROADTO100K',
reward: 'Special Avatar Accessory',
expires: 'No expiration'
},
{
code: 'VANITYXBOY',
reward: 'Vanity Backpack',
expires: 'No expiration'
},
{
code: 'SHINYJOKER',
reward: 'Shiny Joker Mask',
expires: 'No expiration'
},
{
code: 'ICYGLOW',
reward: 'Glowing Ice Crown',
expires: 'No expiration'
},
{
code: 'DARKBLOOD',
reward: 'Dark Blood Cape',
expires: 'No expiration'
},
{
code: 'BOOMEXPLOSION',
reward: 'Boom Explosion Mask',
expires: 'No expiration'
},
{
code: 'BLOXYPARTY',
reward: 'Bloxyparty Hat',
expires: 'No expiration'
},
{
code: 'WATERFALL2024',
reward: 'Waterfall Back Bling',
expires: 'No expiration'
},
{
code: 'MAYDAY2024',
reward: 'May Day Hat',
expires: 'May 1, 2024'
},
{
code: 'PARTYBEAN2024',
reward: 'Party Bean Hat',
expires: 'July 1, 2024'
}
];
// Show Available Codes functionality
showCodesButton.onclick = () => {
resultContainer.innerHTML = '<h3>Available Roblox Codes:</h3>';
const codesTable = document.createElement('div');
codesTable.style.padding = '15px';
codesTable.style.backgroundColor = '#ffffff';
codesTable.style.borderRadius = '8px';
codesTable.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
codesTable.style.margin = '10px 0';
// Create table header
codesTable.innerHTML = `
<div style="display: grid; grid-template-columns: 1fr 2fr 1fr; gap: 10px; margin-bottom: 10px; font-weight: bold; padding: 10px; background-color: #f5f5f5; border-radius: 4px;">
<div>Code</div>
<div>Reward</div>
<div>Expires</div>
</div>
`;
// Add each code to the table
availableCodes.forEach(codeInfo => {
const codeRow = document.createElement('div');
codeRow.style.display = 'grid';
codeRow.style.gridTemplateColumns = '1fr 2fr 1fr';
codeRow.style.gap = '10px';
codeRow.style.padding = '10px';
codeRow.style.borderBottom = '1px solid #eee';
codeRow.innerHTML = `
<div style="font-family: monospace; font-weight: bold;">${codeInfo.code}</div>
<div>${codeInfo.reward}</div>
<div>${codeInfo.expires}</div>
`;
codesTable.appendChild(codeRow);
});
resultContainer.appendChild(codesTable);
// Add note about codes
const note = document.createElement('p');
note.style.marginTop = '15px';
note.style.padding = '10px';
note.style.backgroundColor = '#fff3cd';
note.style.borderRadius = '4px';
note.style.color = '#856404';
note.innerHTML = '⚠️ Note: These codes are current as of our last update. Some codes might expire without notice.';
resultContainer.appendChild(note);
};
mainWrapper.appendChild(container);
const resultContainer = createResultContainer();
mainWrapper.appendChild(resultContainer);
async function getXCSRFToken() {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: "https://auth.roblox.com/v2/logout",
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
onload: function(response) {
const token = response.responseHeaders.match(/x-csrf-token: (.+)/i)?.[1];
resolve(token);
},
onerror: function(error) {
reject(new Error('Failed to get CSRF token'));
}
});
});
}
async function redeemCode(code, token) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: "https://promocodes.roblox.com/v1/promocodes/redeem",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": token
},
data: JSON.stringify({ code: code }),
withCredentials: true,
onload: function(response) {
try {
const result = JSON.parse(response.responseText);
if (response.status === 200) {
resolve({
code: code,
success: true,
message: result.message || 'Code redeemed successfully'
});
} else {
resolve({
code: code,
success: false,
message: result.message || result.errors?.[0]?.message || 'Failed to redeem code'
});
}
} catch (e) {
resolve({
code: code,
success: false,
message: 'Invalid response from server'
});
}
},
onerror: function(error) {
resolve({
code: code,
success: false,
message: 'Network request failed'
});
}
});
});
}
// Known codes list
const knownCodes = [
'SPIDERCOLA', 'TWEETROBLOX', 'ROBLOXEDU2023',
'AMAZONFRIEND2024', 'BRICKMASTER2024', 'ROADTO100K'
];
// Auto-redeem functionality
autoRedeemButton.onclick = async () => {
try {
resultContainer.innerHTML = '<h3>Auto-Redeem Results:</h3>';
const resultsList = document.createElement('ul');
resultsList.style.listStyle = 'none';
resultsList.style.padding = '10px';
// Get CSRF token once before starting
const token = await getXCSRFToken();
if (!token) {
throw new Error('Failed to get authentication token. Please ensure you are logged in.');
}
for (const code of knownCodes) {
// Add delay between attempts
if (knownCodes.indexOf(code) > 0) {
await new Promise(resolve => setTimeout(resolve, 2000));
}
const result = await redeemCode(code, token);
const listItem = document.createElement('li');
listItem.style.padding = '10px';
listItem.style.margin = '5px 0';
listItem.style.borderRadius = '4px';
listItem.style.backgroundColor = result.success ? '#e8f5e9' : '#ffebee';
listItem.innerHTML = `
<strong>${code}:</strong> ${result.success ? '✅' : '❌'}
${result.message}
`;
resultsList.appendChild(listItem);
resultContainer.appendChild(resultsList);
}
} catch (error) {
resultContainer.innerHTML = `
<div class="error-message" style="padding: 15px; margin-top: 20px; border-radius: 4px;">
<h3>❌ Error</h3>
<p>${error.message}</p>
</div>
`;
}
};
// Single code redemption
button.onclick = async () => {
try {
const code = input.value.trim();
if (!code) {
displayMessage('Please enter a code', true);
return;
}
const token = await getXCSRFToken();
if (!token) {
throw new Error('Failed to get authentication token. Please ensure you are logged in.');
}
const result = await redeemCode(code, token);
resultContainer.innerHTML = `
<div class="${result.success ? 'success-message' : 'error-message'}"
style="padding: 15px; margin-top: 20px; border-radius: 4px;">
<h3>${result.success ? '✅ Success!' : '❌ Error'}</h3>
<p>${result.message}</p>
</div>
`;
} catch (error) {
resultContainer.innerHTML = `
<div class="error-message" style="padding: 15px; margin-top: 20px; border-radius: 4px;">
<h3>❌ Error</h3>
<p>${error.message}</p>
</div>
`;
}
};
}
// Panel Implementation
function createPanel() {
const mainWrapper = document.createElement('div');
mainWrapper.className = 'main-content-wrapper';
document.body.appendChild(mainWrapper);
const container = document.createElement('div');
container.className = 'form-container';
const title = document.createElement('h2');
title.textContent = 'Roblox Multi-Feature Tool';
container.appendChild(title);
const buttons = [
{ text: 'Game Information', url: '/getgameinfo' },
{ text: 'Badge Information', url: '/getbadgeinfo' },
{ text: 'User Information', url: '/getuserinfo' },
{ text: 'Group Information', url: '/getgroupinfo' },
{ text: 'Code Redemption', url: '/redeemcode' }
];
buttons.forEach(button => {
const btn = document.createElement('button');
btn.className = 'panel-button';
btn.textContent = button.text;
btn.onclick = () => window.location.href = 'https://www.roblox.com' + button.url;
container.appendChild(btn);
});
mainWrapper.appendChild(container);
}
// Modify the initialization function
function initializePage() {
const currentPath = window.location.pathname;
const customPages = [
'/userpanel',
'/getgameinfo',
'/getbadgeinfo',
'/getuserinfo',
'/getgroupinfo',
'/redeemcode'
];
// Only apply our custom handling if we're on one of our specific pages
if (customPages.includes(currentPath)) {
// Add the custom class to body only on our pages
document.body.classList.add('custom-roblox-tool');
// Initialize based on current page
switch(currentPath) {
case '/userpanel':
createPanel();
break;
case '/getgameinfo':
initializeGameInfo();
break;
case '/getbadgeinfo':
initializeBadgeInfo();
break;
case '/getuserinfo':
initializeUserInfo();
break;
case '/getgroupinfo':
initializeGroupInfo();
break;
case '/redeemcode':
initializeCodeRedemption();
break;
}
}
}
initializePage();
})();