您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download Roblox thumbnails, game info, badge info, and user info and the images for all those!
当前为
// ==UserScript== // @name Roblox Multi-Feature User Panel. // @namespace http://tampermonkey.net/ // @version 0.8 // @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 // @license MIT // ==/UserScript== (function() { 'use strict'; const style = document.createElement('style'); style.textContent = ` body { font-family: Arial, sans-serif; background-color: #f4f7f6; margin: 0; padding: 0; } .main-content-wrapper { width: 100%; padding: 20px; 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; } `; 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'; // Get outfit thumbnails const outfitIds = outfitsData.data.map(outfit => outfit.id).filter(id => id); // Filter out any null/undefined IDs if (outfitIds.length === 0) { dynamicContentDiv.innerHTML = '<p>No valid outfits found</p>'; return; } try { const thumbnailResponse = await fetch(`https://thumbnails.roblox.com/v1/users/outfits?userOutfitIds=${outfitIds.join(',')}&size=420x420&format=Png`); const thumbnailData = await thumbnailResponse.json(); // Create a map of outfit IDs to their thumbnails const thumbnailMap = new Map(thumbnailData.data.map(item => [item.targetId, item.imageUrl])); // Create outfit cards outfitsData.data.forEach(outfit => { if (!outfit || !outfit.id) return; // Skip invalid outfits 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) || 'placeholder-url'; outfitCard.innerHTML = ` <img src="${thumbnailUrl}" alt="${outfit.name}" style="width: 200px; height: 200px; object-fit: contain; border-radius: 4px;"> <h4 style="margin: 10px 0; color: #333;">${outfit.name}</h4> `; outfitsGrid.appendChild(outfitCard); }); // Clear previous content and add new grid dynamicContentDiv.innerHTML = '<h4>User Outfits:</h4>'; dynamicContentDiv.appendChild(outfitsGrid); } catch (thumbnailError) { console.error('Error fetching thumbnails:', thumbnailError); dynamicContentDiv.innerHTML = '<p>Error loading outfit thumbnails</p>'; } } 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) => { 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 || `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', 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); } // Add this function near the top of your script, after 'use strict'; function handle404Redirect() { // Check if current page is a 404 error if (document.querySelector('.request-error-page-content')) { // Get the current URL path const path = window.location.pathname; // Define valid paths and their corresponding functions const validPaths = { '/userpanel': createPanel, '/getgameinfo': initializeGameInfo, '/getbadgeinfo': initializeBadgeInfo, '/getuserinfo': initializeUserInfo, '/getgroupinfo': initializeGroupInfo, '/redeemcode': initializeCodeRedemption }; // Check if the path is one we handle if (validPaths[path]) { // Clear the 404 content document.body.innerHTML = ''; // Initialize the correct feature validPaths[path](); } } } handle404Redirect(); // Initialize based on current page const currentPath = window.location.pathname; 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; } })();