Roblox Multi-Feature User Panel.

Download Roblox thumbnails, game info, badge info, and user info and the images for all those!

当前为 2024-11-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Roblox Multi-Feature User Panel.
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.7
  5. // @description Download Roblox thumbnails, game info, badge info, and user info and the images for all those!
  6. // @author NotRoblox
  7. // @match https://www.roblox.com/userpanel
  8. // @match https://www.roblox.com/getgameinfo
  9. // @match https://www.roblox.com/getbadgeinfo
  10. // @match https://www.roblox.com/getuserinfo
  11. // @match https://www.roblox.com/getgroupinfo
  12. // @grant GM_cookie
  13. // @connect promocodes.roblox.com
  14. // @connect auth.roblox.com
  15. // @connect www.roblox.com
  16. // @match https://www.roblox.com/*
  17. // @match https://promocodes.roblox.com/*
  18. // @match https://auth.roblox.com/*
  19. // @grant GM_xmlhttpRequest
  20. // @grant Gm_download
  21. // @license MIT
  22. // ==/UserScript==
  23.  
  24. (function() {
  25. 'use strict';
  26.  
  27. const style = document.createElement('style');
  28. style.textContent = `
  29. body {
  30. font-family: Arial, sans-serif;
  31. background-color: #f4f7f6;
  32. margin: 0;
  33. padding: 0;
  34. }
  35.  
  36. .main-content-wrapper {
  37. width: 100%;
  38. padding: 20px;
  39. margin-bottom: 120px;
  40. display: flex;
  41. flex-direction: column;
  42. align-items: center;
  43. min-height: calc(100vh - 200px);
  44. }
  45.  
  46. .form-container {
  47. background-color: #ffffff;
  48. padding: 20px;
  49. border-radius: 8px;
  50. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  51. width: 100%;
  52. max-width: 400px;
  53. text-align: center;
  54. margin: 20px auto;
  55. position: relative;
  56. z-index: 1;
  57. }
  58.  
  59. .input-field {
  60. width: 100%;
  61. padding: 10px;
  62. margin: 10px 0;
  63. border: 2px solid #ddd;
  64. border-radius: 4px;
  65. font-size: 16px;
  66. }
  67.  
  68. .submit-button, .panel-button {
  69. background-color: #4CAF50;
  70. color: white;
  71. padding: 12px 20px;
  72. border: none;
  73. border-radius: 4px;
  74. cursor: pointer;
  75. width: 100%;
  76. font-size: 16px;
  77. margin: 10px 0;
  78. }
  79.  
  80. .submit-button:hover, .panel-button:hover {
  81. background-color: #45a049;
  82. }
  83.  
  84. .result-container {
  85. background-color: #ffffff;
  86. padding: 20px;
  87. border-radius: 8px;
  88. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  89. width: 100%;
  90. max-width: 800px;
  91. margin: 20px auto 120px auto;
  92. position: relative;
  93. z-index: 1;
  94. }
  95.  
  96. .image-container {
  97. display: flex;
  98. flex-wrap: wrap;
  99. gap: 20px;
  100. justify-content: center;
  101. margin: 20px 0;
  102. }
  103.  
  104. .image-item {
  105. text-align: center;
  106. }
  107.  
  108. .image-item img {
  109. max-width: 200px;
  110. border-radius: 8px;
  111. margin-bottom: 10px;
  112. }
  113.  
  114. .info-text {
  115. margin: 10px 0;
  116. font-size: 16px;
  117. }
  118.  
  119. .error-message {
  120. color: #ff0000;
  121. margin: 10px 0;
  122. }
  123.  
  124. .success-message {
  125. color: #4CAF50;
  126. margin: 10px 0;
  127. }
  128. `;
  129. document.head.appendChild(style);
  130.  
  131. async function getUserIdFromUsername(username) {
  132. const response = await fetch(`https://users.roblox.com/v1/usernames/users`, {
  133. method: 'POST',
  134. headers: { 'Content-Type': 'application/json' },
  135. body: JSON.stringify({ usernames: [username] })
  136. });
  137. const data = await response.json();
  138. if (!data.data || data.data.length === 0) throw new Error('User not found');
  139. return data.data[0].id;
  140. }
  141.  
  142. function createBasicForm(placeholder, buttonText) {
  143. const container = document.createElement('div');
  144. container.className = 'form-container';
  145.  
  146. const input = document.createElement('input');
  147. input.type = 'text';
  148. input.className = 'input-field';
  149. input.placeholder = placeholder;
  150.  
  151. const button = document.createElement('button');
  152. button.className = 'submit-button';
  153. button.textContent = buttonText;
  154.  
  155. container.appendChild(input);
  156. container.appendChild(button);
  157.  
  158. return { container, input, button };
  159. }
  160.  
  161. function displayMessage(message, isError = false) {
  162. const messageDiv = document.createElement('div');
  163. messageDiv.className = isError ? 'error-message' : 'success-message';
  164. messageDiv.textContent = message;
  165. document.querySelector('.form-container').appendChild(messageDiv);
  166. setTimeout(() => messageDiv.remove(), 5000);
  167. }
  168.  
  169. function createResultContainer() {
  170. const container = document.createElement('div');
  171. container.className = 'result-container';
  172. return container;
  173. }
  174.  
  175. async function initializeGameInfo() {
  176. const mainWrapper = document.createElement('div');
  177. mainWrapper.className = 'main-content-wrapper';
  178. document.body.appendChild(mainWrapper);
  179.  
  180. const { container, input, button } = createBasicForm('Enter Game ID', 'Get Game Info');
  181. mainWrapper.appendChild(container);
  182.  
  183. const refreshContent = async (gameId) => {
  184. const existingResults = mainWrapper.querySelectorAll('.result-container');
  185. existingResults.forEach(result => result.remove());
  186.  
  187. try {
  188. // Get the universe ID first
  189. const placeResponse = await fetch(`https://apis.roblox.com/universes/v1/places/${gameId}/universe`);
  190. const placeData = await placeResponse.json();
  191. const universeId = placeData.universeId;
  192.  
  193. // Fetch all required data
  194. const [gameResponse, iconResponse, thumbnailResponse, votesResponse, favoritesResponse] = await Promise.all([
  195. fetch(`https://games.roblox.com/v1/games?universeIds=${universeId}`),
  196. fetch(`https://thumbnails.roblox.com/v1/games/icons?universeIds=${universeId}&size=512x512&format=Png&isCircular=false`),
  197. fetch(`https://thumbnails.roblox.com/v1/games/${universeId}/thumbnails?size=768x432&format=Png&limit=10`),
  198. fetch(`https://games.roblox.com/v1/games/${universeId}/votes`),
  199. fetch(`https://games.roblox.com/v1/games/${universeId}/favorites/count`),
  200. ]);
  201.  
  202. const [gameData, iconData, thumbnailData, votesData, favoritesData] = await Promise.all([
  203. gameResponse.json(),
  204. iconResponse.json(),
  205. thumbnailResponse.json(),
  206. votesResponse.json(),
  207. favoritesResponse.json()
  208. ]);
  209.  
  210. const resultContainer = createResultContainer();
  211.  
  212. // Create image container for all images
  213. const imageContainer = document.createElement('div');
  214. imageContainer.className = 'image-container';
  215.  
  216. // Display game icon
  217. if (iconData.data && iconData.data[0]) {
  218. const iconDiv = document.createElement('div');
  219. iconDiv.className = 'image-item';
  220.  
  221. const iconImg = document.createElement('img');
  222. iconImg.src = iconData.data[0].imageUrl;
  223. iconImg.alt = 'Game Icon';
  224. iconImg.className = 'game-icon';
  225.  
  226. const downloadIconBtn = document.createElement('button');
  227. downloadIconBtn.className = 'submit-button';
  228. downloadIconBtn.textContent = 'Download Icon';
  229. downloadIconBtn.onclick = () => GM_download({
  230. url: iconData.data[0].imageUrl,
  231. name: `game_${gameId}_icon.png`
  232. });
  233.  
  234. iconDiv.appendChild(iconImg);
  235. iconDiv.appendChild(downloadIconBtn);
  236. imageContainer.appendChild(iconDiv);
  237. }
  238.  
  239. // Display thumbnails
  240. if (thumbnailData.data) {
  241. thumbnailData.data.forEach((thumb, index) => {
  242. const thumbDiv = document.createElement('div');
  243. thumbDiv.className = 'image-item';
  244.  
  245. const thumbImg = document.createElement('img');
  246. thumbImg.src = thumb.imageUrl;
  247. thumbImg.alt = `Game Thumbnail ${index + 1}`;
  248. thumbImg.className = 'thumbnail-image';
  249.  
  250. const downloadThumbBtn = document.createElement('button');
  251. downloadThumbBtn.className = 'submit-button';
  252. downloadThumbBtn.textContent = `Download Thumbnail ${index + 1}`;
  253. downloadThumbBtn.onclick = () => GM_download({
  254. url: thumb.imageUrl,
  255. name: `game_${gameId}_thumbnail_${index + 1}.png`
  256. });
  257.  
  258. thumbDiv.appendChild(thumbImg);
  259. thumbDiv.appendChild(downloadThumbBtn);
  260. imageContainer.appendChild(thumbDiv);
  261. });
  262. }
  263.  
  264. // Display game information
  265. if (gameData.data && gameData.data[0]) {
  266. const game = gameData.data[0];
  267. const likes = votesData.upVotes || 0;
  268. const dislikes = votesData.downVotes || 0;
  269. const totalVotes = likes + dislikes;
  270. const likeRatio = totalVotes > 0 ? ((likes / totalVotes) * 100).toFixed(1) : 0;
  271. resultContainer.appendChild(imageContainer);
  272.  
  273. const gameInfo = document.createElement('div');
  274. gameInfo.className = 'info-text';
  275. gameInfo.innerHTML = `
  276. <h2>${game.name}</h2>
  277. <div class="game-stats">
  278. <div class="stat-item">
  279. <h4>👥 Player Stats</h4>
  280. <p>Current Players: ${game.playing?.toLocaleString() || 0}</p>
  281. <p>Total Visits: ${game.visits?.toLocaleString() || 0}</p>
  282. <p>Max Players: ${game.maxPlayers || 'Unknown'}</p>
  283. </div>
  284. <div class="stat-item">
  285. <h4>👍 Ratings</h4>
  286. <p>Likes: ${likes.toLocaleString()}</p>
  287. <p>Dislikes: ${dislikes.toLocaleString()}</p>
  288. <p>Like Ratio: ${likeRatio}%</p>
  289. <p>Favorites: ${favoritesData.favoritesCount?.toLocaleString() || 0}</p>
  290. </div>
  291. <div class="stat-item">
  292. <h4>ℹ️ Details</h4>
  293. <p>Created: ${new Date(game.created).toLocaleDateString()}</p>
  294. <p>Last Updated: ${new Date(game.updated).toLocaleDateString()}</p>
  295. <p>Genre: ${game.genre || 'Not specified'}</p>
  296. <p>Allowed Gear Types: ${game.allowedGearTypes?.join(', ') || 'None'}</p>
  297. </div>
  298. </div>
  299. <div class="game-description">
  300. <h4>📝 Description</h4>
  301. <p>${game.description || 'No description available'}</p>
  302. </div>
  303. <p class="game-link"><a href="https://www.roblox.com/games/${gameId}" target="_blank">🎮 View Game Page</a></p>
  304. `;
  305. resultContainer.appendChild(gameInfo);
  306. }
  307.  
  308.  
  309. mainWrapper.appendChild(resultContainer);
  310. } catch (error) {
  311. console.error(error);
  312. displayMessage(error.message, true);
  313. }
  314. };
  315.  
  316.  
  317. button.onclick = async () => {
  318. const gameId = input.value.trim();
  319. if (!gameId) {
  320. displayMessage('Please enter a game ID', true);
  321. return;
  322. }
  323. await refreshContent(gameId);
  324. };
  325. }
  326.  
  327. async function initializeBadgeInfo() {
  328. const mainWrapper = document.createElement('div');
  329. mainWrapper.className = 'main-content-wrapper';
  330. document.body.appendChild(mainWrapper);
  331.  
  332. const { container, input, button } = createBasicForm('Enter Badge ID', 'Get Badge Info');
  333. mainWrapper.appendChild(container);
  334.  
  335. const refreshContent = async (badgeId) => {
  336. // Remove any existing result containers
  337. const existingResults = mainWrapper.querySelectorAll('.result-container');
  338. existingResults.forEach(result => result.remove());
  339.  
  340. try {
  341. // Fetch badge info with proper error handling
  342. const infoResponse = await fetch(`https://badges.roblox.com/v1/badges/${badgeId}`, {
  343. method: 'GET',
  344. credentials: 'include'
  345. });
  346.  
  347. if (!infoResponse.ok) {
  348. throw new Error('Failed to fetch badge information');
  349. }
  350.  
  351. const badgeInfo = await infoResponse.json();
  352.  
  353. // Fetch badge statistics
  354. const statsResponse = await fetch(`https://badges.roblox.com/v1/badges/${badgeId}/statistics`, {
  355. method: 'GET',
  356. credentials: 'include'
  357. });
  358.  
  359. const statsData = await statsResponse.json();
  360.  
  361. // Fetch badge icon
  362. const iconResponse = await fetch(`https://thumbnails.roblox.com/v1/badges/icons?badgeIds=${badgeId}&size=150x150&format=Png`, {
  363. method: 'GET',
  364. credentials: 'include'
  365. });
  366.  
  367. const iconData = await iconResponse.json();
  368.  
  369. const resultContainer = createResultContainer();
  370.  
  371. // Create image container
  372. const imageContainer = document.createElement('div');
  373. imageContainer.className = 'image-container';
  374.  
  375. // Display badge icon if available
  376. if (iconData.data && iconData.data[0]) {
  377. const iconDiv = document.createElement('div');
  378. iconDiv.className = 'image-item';
  379.  
  380. const iconImg = document.createElement('img');
  381. iconImg.src = iconData.data[0].imageUrl;
  382. iconImg.alt = 'Badge Icon';
  383. iconImg.style.maxWidth = '150px';
  384.  
  385. const downloadBtn = document.createElement('button');
  386. downloadBtn.className = 'submit-button';
  387. downloadBtn.textContent = 'Download Badge Icon';
  388. downloadBtn.onclick = () => GM_download({
  389. url: iconData.data[0].imageUrl,
  390. name: `badge_${badgeId}.png`
  391. });
  392.  
  393. iconDiv.appendChild(iconImg);
  394. iconDiv.appendChild(downloadBtn);
  395. imageContainer.appendChild(iconDiv);
  396. }
  397.  
  398. // Display badge information
  399. const infoDiv = document.createElement('div');
  400. infoDiv.className = 'info-text';
  401. infoDiv.innerHTML = `
  402. <h3>${badgeInfo.name || 'Unknown Badge'}</h3>
  403. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0;">
  404. <h4>Badge Details</h4>
  405. <p><strong>Description:</strong> ${badgeInfo.description || 'No description available'}</p>
  406. <p><strong>Enabled:</strong> ${badgeInfo.enabled ? '✅ Yes' : '❌ No'}</p>
  407. <p><strong>Created:</strong> ${new Date(badgeInfo.created).toLocaleDateString()}</p>
  408. <p><strong>Updated:</strong> ${new Date(badgeInfo.updated).toLocaleDateString()}</p>
  409. </div>
  410.  
  411. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0;">
  412. <h4>Statistics</h4>
  413. <p><strong>Win Rate:</strong> ${statsData.winRatePercentage?.toFixed(2) || 0}%</p>
  414. <p><strong>Awarded:</strong> ${statsData.awardedCount?.toLocaleString() || 0} times</p>
  415. </div>
  416.  
  417. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 10px 0;">
  418. <h4>Links</h4>
  419. <p><a href="https://www.roblox.com/badges/${badgeId}" target="_blank">View Badge Page</a></p>
  420. ${badgeInfo.awardingUniverse ?
  421. `<p><a href="https://www.roblox.com/games/${badgeInfo.awardingUniverse.id}" target="_blank">View Game Page</a></p>`
  422. : ''}
  423. </div>
  424. `;
  425.  
  426. resultContainer.appendChild(imageContainer);
  427. resultContainer.appendChild(infoDiv);
  428. mainWrapper.appendChild(resultContainer);
  429. displayMessage('Badge information fetched successfully!');
  430. } catch (error) {
  431. const resultContainer = createResultContainer();
  432. resultContainer.innerHTML = `
  433. <div class="error-message" style="padding: 15px; margin-top: 20px; border-radius: 4px;">
  434. <h3>❌ Error</h3>
  435. <p>Failed to fetch badge information: ${error.message}</p>
  436. <p>Please make sure the badge ID is valid and try again.</p>
  437. </div>
  438. `;
  439. mainWrapper.appendChild(resultContainer);
  440. displayMessage(error.message, true);
  441. }
  442. };
  443.  
  444. button.onclick = async () => {
  445. const badgeId = input.value.trim();
  446. if (!badgeId) {
  447. displayMessage('Please enter a badge ID', true);
  448. return;
  449. }
  450. await refreshContent(badgeId);
  451. };
  452. }
  453.  
  454. async function initializeUserInfo() {
  455. const mainWrapper = document.createElement('div');
  456. mainWrapper.className = 'main-content-wrapper';
  457. document.body.appendChild(mainWrapper);
  458.  
  459. const { container, input, button } = createBasicForm('Enter Username', 'Get User Info');
  460. mainWrapper.appendChild(container);
  461.  
  462. const resultContainer = createResultContainer();
  463. resultContainer.style.display = 'none';
  464. mainWrapper.appendChild(resultContainer);
  465.  
  466. button.onclick = async () => {
  467. try {
  468. const username = input.value.trim();
  469. if (!username) throw new Error('Please enter a username');
  470.  
  471. const userId = await getUserIdFromUsername(username);
  472.  
  473. const [
  474. userInfoResponse,
  475. presenceResponse,
  476. friendsResponse,
  477. followersResponse,
  478. thumbnailResponse,
  479. bustResponse,
  480. headshotResponse
  481. ] = await Promise.all([
  482. fetch(`https://users.roblox.com/v1/users/${userId}`),
  483. fetch(`https://presence.roblox.com/v1/presence/users`, {
  484. method: 'POST',
  485. headers: { 'Content-Type': 'application/json' },
  486. body: JSON.stringify({ userIds: [userId] })
  487. }),
  488. fetch(`https://friends.roblox.com/v1/users/${userId}/friends/count`),
  489. fetch(`https://friends.roblox.com/v1/users/${userId}/followers/count`),
  490. fetch(`https://thumbnails.roblox.com/v1/users/avatar?userIds=${userId}&size=420x420&format=Png`),
  491. fetch(`https://thumbnails.roblox.com/v1/users/avatar-bust?userIds=${userId}&size=420x420&format=Png`),
  492. fetch(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&size=420x420&format=Png`)
  493. ]);
  494.  
  495. const [userInfo, presence, friends, followers, thumbnail, bust, headshot] = await Promise.all([
  496. userInfoResponse.json(),
  497. presenceResponse.json(),
  498. friendsResponse.json(),
  499. followersResponse.json(),
  500. thumbnailResponse.json(),
  501. bustResponse.json(),
  502. headshotResponse.json()
  503. ]);
  504.  
  505. resultContainer.innerHTML = '';
  506.  
  507. // Create thumbnails section
  508. const imageContainer = document.createElement('div');
  509. imageContainer.className = 'image-container';
  510.  
  511. const createImageSection = (data, type) => {
  512. if (data.data && data.data[0]) {
  513. const div = document.createElement('div');
  514. div.className = 'image-item';
  515.  
  516. const img = document.createElement('img');
  517. img.src = data.data[0].imageUrl;
  518. img.alt = `${type} thumbnail`;
  519.  
  520. const downloadBtn = document.createElement('button');
  521. downloadBtn.className = 'submit-button';
  522. downloadBtn.textContent = `Download ${type}`;
  523. downloadBtn.onclick = () => GM_download({
  524. url: data.data[0].imageUrl,
  525. name: `${username}_${type}.png`
  526. });
  527.  
  528. div.appendChild(img);
  529. div.appendChild(downloadBtn);
  530. imageContainer.appendChild(div);
  531. }
  532. };
  533.  
  534. createImageSection(thumbnail, 'Full Avatar');
  535. createImageSection(bust, 'Bust');
  536. createImageSection(headshot, 'Headshot');
  537.  
  538. // Create user info section
  539. const userInfoDiv = document.createElement('div');
  540. userInfoDiv.className = 'info-text';
  541.  
  542. const userPresence = presence.userPresences[0];
  543. userInfoDiv.innerHTML = `
  544. <h3>User Information for ${userInfo.displayName} (${userInfo.name})</h3>
  545. <p>User ID: ${userId}</p>
  546. <p>Display Name: ${userInfo.displayName}</p>
  547. <p>Username: ${userInfo.name}</p>
  548. <p>Description: ${userInfo.description ? userInfo.description : 'No description available'}</p>
  549. <p>Account Age: ${userInfo.age} days</p>
  550. <p>Join Date: ${new Date(userInfo.created).toLocaleDateString()}</p>
  551. <p>Online Status: ${userPresence.userPresenceType === 0 ? 'Offline' : userPresence.userPresenceType === 1 ? 'Online' : 'Playing'}</p>
  552. <p>Friends Count: ${friends.count}</p>
  553. <p>Followers Count: ${followers.count}</p>
  554. <p>Profile Link: <a href="https://www.roblox.com/users/${userId}/profile" target="_blank">View Profile</a></p>
  555. ${userPresence.userPresenceType !== 0 ? `<p>Last Location: ${userPresence.lastLocation}</p>` : ''}
  556. <p>
  557. <a href="https://www.roblox.com/abusereport/userprofile?id=${userId}" target="_blank">Report User</a> |
  558. <a href="https://www.roblox.com/illegal-content-reporting" target="_blank">Report to DMCA</a>
  559. </p>
  560. `;
  561.  
  562. // Create container for dynamic content
  563. const dynamicContentDiv = document.createElement('div');
  564. dynamicContentDiv.id = 'dynamic-content';
  565.  
  566. // Create buttons container
  567. const buttonsDiv = document.createElement('div');
  568. buttonsDiv.className = 'buttons-container';
  569.  
  570. // Username History Button
  571. const historyButton = document.createElement('button');
  572. historyButton.className = 'submit-button';
  573. historyButton.textContent = 'Show Username History';
  574. historyButton.onclick = async () => {
  575. try {
  576. const historyResponse = await fetch(`https://users.roblox.com/v1/users/${userId}/username-history`);
  577. const historyData = await historyResponse.json();
  578.  
  579. const historyList = historyData.data.map((entry) => `<li>${entry.name}</li>`).join('');
  580. dynamicContentDiv.innerHTML = `<h4>Username History:</h4><ul>${historyList || '<li>No username changes found</li>'}</ul>`;
  581. } catch (error) {
  582. displayMessage('Failed to fetch username history', true);
  583. }
  584. };
  585.  
  586. // Outfits Button
  587. const outfitsButton = document.createElement('button');
  588. outfitsButton.className = 'submit-button';
  589. outfitsButton.textContent = 'Show User Outfits';
  590. outfitsButton.onclick = async () => {
  591. try {
  592. const outfitsResponse = await fetch(`https://avatar.roblox.com/v1/users/${userId}/outfits?page=1&itemsPerPage=50`);
  593. const outfitsData = await outfitsResponse.json();
  594.  
  595. if (!outfitsData.data || outfitsData.data.length === 0) {
  596. dynamicContentDiv.innerHTML = '<p>No outfits found</p>';
  597. return;
  598. }
  599.  
  600. // Create outfits grid container
  601. const outfitsGrid = document.createElement('div');
  602. outfitsGrid.style.display = 'grid';
  603. outfitsGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(200px, 1fr))';
  604. outfitsGrid.style.gap = '20px';
  605. outfitsGrid.style.padding = '20px';
  606. outfitsGrid.style.marginTop = '20px';
  607.  
  608. // Get outfit thumbnails
  609. const outfitIds = outfitsData.data.map(outfit => outfit.id).filter(id => id); // Filter out any null/undefined IDs
  610. if (outfitIds.length === 0) {
  611. dynamicContentDiv.innerHTML = '<p>No valid outfits found</p>';
  612. return;
  613. }
  614.  
  615. try {
  616. const thumbnailResponse = await fetch(`https://thumbnails.roblox.com/v1/users/outfits?userOutfitIds=${outfitIds.join(',')}&size=420x420&format=Png`);
  617. const thumbnailData = await thumbnailResponse.json();
  618.  
  619. // Create a map of outfit IDs to their thumbnails
  620. const thumbnailMap = new Map(thumbnailData.data.map(item => [item.targetId, item.imageUrl]));
  621.  
  622. // Create outfit cards
  623. outfitsData.data.forEach(outfit => {
  624. if (!outfit || !outfit.id) return; // Skip invalid outfits
  625.  
  626. const outfitCard = document.createElement('div');
  627. outfitCard.style.textAlign = 'center';
  628. outfitCard.style.border = '1px solid #ccc';
  629. outfitCard.style.borderRadius = '8px';
  630. outfitCard.style.padding = '10px';
  631. outfitCard.style.backgroundColor = '#ffffff';
  632. outfitCard.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
  633.  
  634. const thumbnailUrl = thumbnailMap.get(outfit.id) || 'placeholder-url';
  635.  
  636. outfitCard.innerHTML = `
  637. <img src="${thumbnailUrl}" alt="${outfit.name}" style="width: 200px; height: 200px; object-fit: contain; border-radius: 4px;">
  638. <h4 style="margin: 10px 0; color: #333;">${outfit.name}</h4>
  639. `;
  640. outfitsGrid.appendChild(outfitCard);
  641. });
  642.  
  643. // Clear previous content and add new grid
  644. dynamicContentDiv.innerHTML = '<h4>User Outfits:</h4>';
  645. dynamicContentDiv.appendChild(outfitsGrid);
  646. } catch (thumbnailError) {
  647. console.error('Error fetching thumbnails:', thumbnailError);
  648. dynamicContentDiv.innerHTML = '<p>Error loading outfit thumbnails</p>';
  649. }
  650. } catch (error) {
  651. console.error('Error fetching outfits:', error);
  652. dynamicContentDiv.innerHTML = '<p>Failed to fetch outfits</p>';
  653. displayMessage('Failed to fetch outfits', true);
  654. }
  655. };
  656.  
  657. // Current Assets Button
  658. const currentAssetsButton = document.createElement('button');
  659. currentAssetsButton.className = 'submit-button';
  660. currentAssetsButton.textContent = 'Show Current Assets';
  661. currentAssetsButton.onclick = async () => {
  662. try {
  663. const currentWearingResponse = await fetch(`https://avatar.roblox.com/v1/users/${userId}/currently-wearing`);
  664. const currentWearingData = await currentWearingResponse.json();
  665.  
  666. const assetsList = await Promise.all(currentWearingData.assetIds.map(async (assetId) => {
  667. const assetResponse = await fetch(`https://catalog.roblox.com/v1/catalog/items/${assetId}/details`);
  668. const assetData = await assetResponse.json();
  669. return `
  670. <li style="margin-bottom: 10px;">
  671. <div>${assetData.name || `Asset #${assetId}`}</div>
  672. <a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset</a>
  673. </li>
  674. `;
  675. }));
  676.  
  677. dynamicContentDiv.innerHTML = `
  678. <h4>Currently Wearing:</h4>
  679. <ul style="list-style: none; padding: 0;">${assetsList.join('') || '<li>No assets found</li>'}</ul>
  680. `;
  681. } catch (error) {
  682. displayMessage('Failed to fetch current assets', true);
  683. }
  684. };
  685.  
  686. // Add buttons to container
  687. buttonsDiv.appendChild(historyButton);
  688. buttonsDiv.appendChild(outfitsButton);
  689. buttonsDiv.appendChild(currentAssetsButton);
  690.  
  691. // Append everything to result container
  692. resultContainer.appendChild(imageContainer);
  693. resultContainer.appendChild(userInfoDiv);
  694. resultContainer.appendChild(buttonsDiv);
  695. resultContainer.appendChild(dynamicContentDiv);
  696. resultContainer.style.display = 'block';
  697.  
  698. displayMessage('User information fetched successfully!');
  699. } catch (error) {
  700. displayMessage(error.message, true);
  701. }
  702. };
  703. }
  704.  
  705. // Updated getOutfitAssets function
  706. async function getOutfitAssets(outfitId) {
  707. try {
  708. const response = await fetch(`https://catalog.roblox.com/v1/outfits/${outfitId}/get-outfit-details`);
  709. const data = await response.json();
  710.  
  711. if (!data.assetIds || data.assetIds.length === 0) {
  712. throw new Error('No assets found');
  713. }
  714.  
  715. const assetsList = await Promise.all(data.assetIds.map(async (assetId) => {
  716. try {
  717. const assetResponse = await fetch(`https://catalog.roblox.com/v1/catalog/items/${assetId}/details`);
  718. const assetData = await assetResponse.json();
  719. return `
  720. <li style="margin-bottom: 10px;">
  721. <div>${assetData.name || 'Unknown Asset'}</div>
  722. <a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset (ID: ${assetId})</a>
  723. </li>
  724. `;
  725. } catch (err) {
  726. return `
  727. <li style="margin-bottom: 10px;">
  728. <div>Asset ID: ${assetId}</div>
  729. <a href="https://www.roblox.com/catalog/${assetId}" target="_blank">View Asset</a>
  730. </li>
  731. `;
  732. }
  733. }));
  734.  
  735. const dynamicContentDiv = document.getElementById('dynamic-content');
  736. dynamicContentDiv.innerHTML = `
  737. <h4>Outfit Assets:</h4>
  738. <ul style="list-style: none; padding: 0;">${assetsList.join('') || '<li>No assets found</li>'}</ul>
  739. <button class="submit-button" onclick="document.querySelector('[data-action=\'Show User Outfits\']').click()">Back to Outfits</button>
  740. `;
  741. } catch (error) {
  742. console.error('Error fetching outfit assets:', error);
  743. displayMessage('Failed to fetch outfit assets: ' + error.message, true);
  744. }
  745. }
  746. async function initializeGroupInfo() {
  747. const mainWrapper = document.createElement('div');
  748. mainWrapper.className = 'main-content-wrapper';
  749. document.body.appendChild(mainWrapper);
  750.  
  751. const { container, input, button } = createBasicForm('Enter Group ID', 'Get Group Info');
  752. mainWrapper.appendChild(container);
  753.  
  754. const refreshContent = async (groupId) => {
  755. // Remove any existing result containers
  756. const existingResults = mainWrapper.querySelectorAll('.result-container');
  757. existingResults.forEach(result => result.remove());
  758.  
  759. try {
  760. // Fetch all group data in parallel
  761. const [
  762. groupResponse,
  763. membersResponse,
  764. iconResponse,
  765. rolesResponse,
  766. wallResponse,
  767. settingsResponse,
  768. socialLinksResponse,
  769. recentPosts
  770. ] = await Promise.all([
  771. fetch(`https://groups.roblox.com/v1/groups/${groupId}`),
  772. fetch(`https://groups.roblox.com/v1/groups/${groupId}/membership`),
  773. fetch(`https://thumbnails.roblox.com/v1/groups/icons?groupIds=${groupId}&size=420x420&format=Png`),
  774. fetch(`https://groups.roblox.com/v1/groups/${groupId}/roles`),
  775. fetch(`https://groups.roblox.com/v2/groups/${groupId}/wall/posts?limit=10&sortOrder=Desc`),
  776. fetch(`https://groups.roblox.com/v1/groups/${groupId}/settings`),
  777. fetch(`https://groups.roblox.com/v1/groups/${groupId}/social-links`),
  778. fetch(`https://groups.roblox.com/v2/groups/${groupId}/wall/posts?limit=5&sortOrder=Desc`)
  779. ]);
  780.  
  781. const [groupInfo, membersInfo, iconData, rolesInfo, wallData, settings, socialLinks, recentPostsData] = await Promise.all([
  782. groupResponse.json(),
  783. membersResponse.json(),
  784. iconResponse.json(),
  785. rolesResponse.json(),
  786. wallResponse.json(),
  787. settingsResponse.json(),
  788. socialLinksResponse.json(),
  789. recentPosts.json()
  790. ]);
  791.  
  792. const resultContainer = createResultContainer();
  793.  
  794. // Create image container for group icon
  795. const imageContainer = document.createElement('div');
  796. imageContainer.className = 'image-container';
  797.  
  798. // Display group icon
  799. if (iconData.data && iconData.data[0]) {
  800. const iconDiv = document.createElement('div');
  801. iconDiv.className = 'image-item';
  802.  
  803. const iconImg = document.createElement('img');
  804. iconImg.src = iconData.data[0].imageUrl;
  805. iconImg.alt = 'Group Icon';
  806.  
  807. const downloadBtn = document.createElement('button');
  808. downloadBtn.className = 'submit-button';
  809. downloadBtn.textContent = 'Download Group Icon';
  810. downloadBtn.onclick = () => GM_download({
  811. url: iconData.data[0].imageUrl,
  812. name: `group_${groupId}_icon.png`
  813. });
  814.  
  815. iconDiv.appendChild(iconImg);
  816. iconDiv.appendChild(downloadBtn);
  817. imageContainer.appendChild(iconDiv);
  818. }
  819.  
  820. // Calculate group age in days
  821. const groupAge = Math.floor((new Date() - new Date(groupInfo.created)) / (1000 * 60 * 60 * 24));
  822.  
  823. // Format roles with more details
  824. const rolesHtml = rolesInfo.roles.map(role => `
  825. <li style="margin-bottom: 10px; padding: 5px; border: 1px solid #eee; border-radius: 4px;">
  826. <strong>${role.name}</strong><br>
  827. Members: ${role.memberCount}<br>
  828. Rank: ${role.rank}<br>
  829. ${role.description ? `Description: ${role.description}<br>` : ''}
  830. </li>
  831. `).join('');
  832.  
  833. // Format recent posts
  834. const recentPostsHtml = recentPostsData.data ? recentPostsData.data.map(post => `
  835. <li style="margin-bottom: 15px; padding: 10px; border: 1px solid #eee; border-radius: 4px;">
  836. <strong>${post.poster.username}</strong> - ${new Date(post.created).toLocaleString()}<br>
  837. ${post.body}
  838. </li>
  839. `).join('') : '';
  840.  
  841. // Format social links
  842. const socialLinksHtml = socialLinks.data ? socialLinks.data.map(link => `
  843. <li><strong>${link.title}</strong>: <a href="${link.url}" target="_blank">${link.url}</a></li>
  844. `).join('') : '';
  845.  
  846. // Display group information
  847. const infoDiv = document.createElement('div');
  848. infoDiv.className = 'info-text';
  849. infoDiv.innerHTML = `
  850. <h2 style="color: #333; margin-bottom: 20px;">${groupInfo.name}</h2>
  851.  
  852. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
  853. <h3>Basic Information</h3>
  854. <p><strong>Group ID:</strong> ${groupId}</p>
  855. <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>
  856. <p><strong>Created:</strong> ${new Date(groupInfo.created).toLocaleString()} (${groupAge} days ago)</p>
  857. <p><strong>Member Count:</strong> ${membersInfo.memberCount?.toLocaleString() || 0}</p>
  858. <p><strong>Description:</strong> ${groupInfo.description || 'No description'}</p>
  859. </div>
  860.  
  861. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
  862. <h3>Group Settings</h3>
  863. <p><strong>Public Entry:</strong> ${groupInfo.publicEntryAllowed ? 'Yes' : 'No'}</p>
  864. <p><strong>Group Status:</strong> ${groupInfo.isLocked ? 'Locked' : 'Active'}</p>
  865. <p><strong>Membership Type:</strong> ${groupInfo.publicEntryAllowed ? 'Anyone can join' : 'Approval required'}</p>
  866. <p><strong>Verified:</strong> ${groupInfo.hasVerifiedBadge ? 'Yes' : 'No'}</p>
  867. </div>
  868.  
  869. ${socialLinks.data && socialLinks.data.length > 0 ? `
  870. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
  871. <h3>Social Links</h3>
  872. <ul style="list-style-type: none; padding-left: 0;">
  873. ${socialLinksHtml}
  874. </ul>
  875. </div>
  876. ` : ''}
  877.  
  878. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
  879. <h3>Quick Links</h3>
  880. <p><a href="https://www.roblox.com/groups/${groupId}" target="_blank">View Group Page</a></p>
  881. <p><a href="https://www.roblox.com/groups/${groupId}/membership" target="_blank">View Members</a></p>
  882. <p><a href="https://www.roblox.com/abusereport/group?id=${groupId}" target="_blank">Report Group</a></p>
  883. </div>
  884.  
  885. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
  886. <h3>Roles (${rolesInfo.roles.length})</h3>
  887. <ul style="list-style-type: none; padding-left: 0;">
  888. ${rolesHtml}
  889. </ul>
  890. </div>
  891.  
  892. ${recentPostsData.data && recentPostsData.data.length > 0 ? `
  893. <div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
  894. <h3>Recent Wall Posts</h3>
  895. <ul style="list-style-type: none; padding-left: 0;">
  896. ${recentPostsHtml}
  897. </ul>
  898. </div>
  899. ` : ''}
  900. `;
  901.  
  902. resultContainer.appendChild(imageContainer);
  903. resultContainer.appendChild(infoDiv);
  904. mainWrapper.appendChild(resultContainer);
  905. displayMessage('Group information fetched successfully!');
  906. } catch (error) {
  907. displayMessage(error.message, true);
  908. }
  909. };
  910.  
  911. button.onclick = async () => {
  912. const groupId = input.value.trim();
  913. if (!groupId) {
  914. displayMessage('Please enter a group ID', true);
  915. return;
  916. }
  917. await refreshContent(groupId);
  918. };
  919. }
  920.  
  921. async function initializeCodeRedemption() {
  922. const mainWrapper = document.createElement('div');
  923. mainWrapper.className = 'main-content-wrapper';
  924. document.body.appendChild(mainWrapper);
  925.  
  926. const { container, input, button } = createBasicForm('Enter Code', 'Redeem Code');
  927. const autoRedeemButton = document.createElement('button');
  928. autoRedeemButton.className = 'submit-button';
  929. autoRedeemButton.style.backgroundColor = '#ff4444';
  930. autoRedeemButton.textContent = 'Auto-Redeem All Active Codes';
  931. container.appendChild(autoRedeemButton);
  932.  
  933. const showCodesButton = document.createElement('button');
  934. showCodesButton.className = 'submit-button';
  935. showCodesButton.style.backgroundColor = '#4a90e2'; // Blue color to distinguish it
  936. showCodesButton.textContent = 'Show Available Codes';
  937. container.appendChild(showCodesButton);
  938.  
  939. // Known codes list with additional information
  940. const availableCodes = [
  941. {
  942. code: 'SPIDERCOLA',
  943. reward: 'Spider Cola Shoulder Pet',
  944. expires: 'No expiration'
  945. },
  946. {
  947. code: 'TWEETROBLOX',
  948. reward: 'The Bird Says Shoulder Pet',
  949. expires: 'No expiration'
  950. },
  951. {
  952. code: 'ROBLOXEDU2023',
  953. reward: 'School Backpack Accessory',
  954. expires: 'No expiration'
  955. },
  956. {
  957. code: 'AMAZONFRIEND2024',
  958. reward: 'Amazon Prime Gaming Reward',
  959. expires: 'March 31, 2024'
  960. },
  961. {
  962. code: 'BRICKMASTER2024',
  963. reward: 'Special Avatar Item',
  964. expires: 'December 31, 2024'
  965. },
  966. {
  967. code: 'ROADTO100K',
  968. reward: 'Special Avatar Accessory',
  969. expires: 'No expiration'
  970. },
  971. {
  972. code: 'VANITYXBOY',
  973. reward: 'Vanity Backpack',
  974. expires: 'No expiration'
  975. },
  976. {
  977. code: 'SHINYJOKER',
  978. reward: 'Shiny Joker Mask',
  979. expires: 'No expiration'
  980. },
  981. {
  982. code: 'ICYGLOW',
  983. reward: 'Glowing Ice Crown',
  984. expires: 'No expiration'
  985. },
  986. {
  987. code: 'DARKBLOOD',
  988. reward: 'Dark Blood Cape',
  989. expires: 'No expiration'
  990. },
  991. {
  992. code: 'BOOMEXPLOSION',
  993. reward: 'Boom Explosion Mask',
  994. expires: 'No expiration'
  995. },
  996. {
  997. code: 'BLOXYPARTY',
  998. reward: 'Bloxyparty Hat',
  999. expires: 'No expiration'
  1000. },
  1001. {
  1002. code: 'WATERFALL2024',
  1003. reward: 'Waterfall Back Bling',
  1004. expires: 'No expiration'
  1005. },
  1006. {
  1007. code: 'MAYDAY2024',
  1008. reward: 'May Day Hat',
  1009. expires: 'May 1, 2024'
  1010. },
  1011. {
  1012. code: 'PARTYBEAN2024',
  1013. reward: 'Party Bean Hat',
  1014. expires: 'July 1, 2024'
  1015. }
  1016. ];
  1017.  
  1018.  
  1019. // Show Available Codes functionality
  1020. showCodesButton.onclick = () => {
  1021. resultContainer.innerHTML = '<h3>Available Roblox Codes:</h3>';
  1022. const codesTable = document.createElement('div');
  1023. codesTable.style.padding = '15px';
  1024. codesTable.style.backgroundColor = '#ffffff';
  1025. codesTable.style.borderRadius = '8px';
  1026. codesTable.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
  1027. codesTable.style.margin = '10px 0';
  1028.  
  1029. // Create table header
  1030. codesTable.innerHTML = `
  1031. <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;">
  1032. <div>Code</div>
  1033. <div>Reward</div>
  1034. <div>Expires</div>
  1035. </div>
  1036. `;
  1037.  
  1038. // Add each code to the table
  1039. availableCodes.forEach(codeInfo => {
  1040. const codeRow = document.createElement('div');
  1041. codeRow.style.display = 'grid';
  1042. codeRow.style.gridTemplateColumns = '1fr 2fr 1fr';
  1043. codeRow.style.gap = '10px';
  1044. codeRow.style.padding = '10px';
  1045. codeRow.style.borderBottom = '1px solid #eee';
  1046. codeRow.innerHTML = `
  1047. <div style="font-family: monospace; font-weight: bold;">${codeInfo.code}</div>
  1048. <div>${codeInfo.reward}</div>
  1049. <div>${codeInfo.expires}</div>
  1050. `;
  1051. codesTable.appendChild(codeRow);
  1052. });
  1053.  
  1054. resultContainer.appendChild(codesTable);
  1055.  
  1056. // Add note about codes
  1057. const note = document.createElement('p');
  1058. note.style.marginTop = '15px';
  1059. note.style.padding = '10px';
  1060. note.style.backgroundColor = '#fff3cd';
  1061. note.style.borderRadius = '4px';
  1062. note.style.color = '#856404';
  1063. note.innerHTML = '⚠️ Note: These codes are current as of our last update. Some codes might expire without notice.';
  1064. resultContainer.appendChild(note);
  1065. };
  1066.  
  1067. mainWrapper.appendChild(container);
  1068.  
  1069. const resultContainer = createResultContainer();
  1070. mainWrapper.appendChild(resultContainer);
  1071.  
  1072. async function getXCSRFToken() {
  1073. return new Promise((resolve, reject) => {
  1074. GM_xmlhttpRequest({
  1075. method: "POST",
  1076. url: "https://auth.roblox.com/v2/logout",
  1077. headers: {
  1078. "Content-Type": "application/json",
  1079. },
  1080. withCredentials: true,
  1081. onload: function(response) {
  1082. const token = response.responseHeaders.match(/x-csrf-token: (.+)/i)?.[1];
  1083. resolve(token);
  1084. },
  1085. onerror: function(error) {
  1086. reject(new Error('Failed to get CSRF token'));
  1087. }
  1088. });
  1089. });
  1090. }
  1091.  
  1092. async function redeemCode(code, token) {
  1093. return new Promise((resolve, reject) => {
  1094. GM_xmlhttpRequest({
  1095. method: "POST",
  1096. url: "https://promocodes.roblox.com/v1/promocodes/redeem",
  1097. headers: {
  1098. "Content-Type": "application/json",
  1099. "X-CSRF-TOKEN": token
  1100. },
  1101. data: JSON.stringify({ code: code }),
  1102. withCredentials: true,
  1103. onload: function(response) {
  1104. try {
  1105. const result = JSON.parse(response.responseText);
  1106. if (response.status === 200) {
  1107. resolve({
  1108. code: code,
  1109. success: true,
  1110. message: result.message || 'Code redeemed successfully'
  1111. });
  1112. } else {
  1113. resolve({
  1114. code: code,
  1115. success: false,
  1116. message: result.message || result.errors?.[0]?.message || 'Failed to redeem code'
  1117. });
  1118. }
  1119. } catch (e) {
  1120. resolve({
  1121. code: code,
  1122. success: false,
  1123. message: 'Invalid response from server'
  1124. });
  1125. }
  1126. },
  1127. onerror: function(error) {
  1128. resolve({
  1129. code: code,
  1130. success: false,
  1131. message: 'Network request failed'
  1132. });
  1133. }
  1134. });
  1135. });
  1136. }
  1137.  
  1138. // Known codes list
  1139. const knownCodes = [
  1140. 'SPIDERCOLA', 'TWEETROBLOX', 'ROBLOXEDU2023',
  1141. 'AMAZONFRIEND2024', 'BRICKMASTER2024', 'ROADTO100K'
  1142. ];
  1143.  
  1144. // Auto-redeem functionality
  1145. autoRedeemButton.onclick = async () => {
  1146. try {
  1147. resultContainer.innerHTML = '<h3>Auto-Redeem Results:</h3>';
  1148. const resultsList = document.createElement('ul');
  1149. resultsList.style.listStyle = 'none';
  1150. resultsList.style.padding = '10px';
  1151.  
  1152. // Get CSRF token once before starting
  1153. const token = await getXCSRFToken();
  1154. if (!token) {
  1155. throw new Error('Failed to get authentication token. Please ensure you are logged in.');
  1156. }
  1157.  
  1158. for (const code of knownCodes) {
  1159. // Add delay between attempts
  1160. if (knownCodes.indexOf(code) > 0) {
  1161. await new Promise(resolve => setTimeout(resolve, 2000));
  1162. }
  1163.  
  1164. const result = await redeemCode(code, token);
  1165. const listItem = document.createElement('li');
  1166. listItem.style.padding = '10px';
  1167. listItem.style.margin = '5px 0';
  1168. listItem.style.borderRadius = '4px';
  1169. listItem.style.backgroundColor = result.success ? '#e8f5e9' : '#ffebee';
  1170. listItem.innerHTML = `
  1171. <strong>${code}:</strong> ${result.success ? '✅' : '❌'}
  1172. ${result.message}
  1173. `;
  1174. resultsList.appendChild(listItem);
  1175. resultContainer.appendChild(resultsList);
  1176. }
  1177. } catch (error) {
  1178. resultContainer.innerHTML = `
  1179. <div class="error-message" style="padding: 15px; margin-top: 20px; border-radius: 4px;">
  1180. <h3>❌ Error</h3>
  1181. <p>${error.message}</p>
  1182. </div>
  1183. `;
  1184. }
  1185. };
  1186.  
  1187. // Single code redemption
  1188. button.onclick = async () => {
  1189. try {
  1190. const code = input.value.trim();
  1191. if (!code) {
  1192. displayMessage('Please enter a code', true);
  1193. return;
  1194. }
  1195.  
  1196. const token = await getXCSRFToken();
  1197. if (!token) {
  1198. throw new Error('Failed to get authentication token. Please ensure you are logged in.');
  1199. }
  1200.  
  1201. const result = await redeemCode(code, token);
  1202. resultContainer.innerHTML = `
  1203. <div class="${result.success ? 'success-message' : 'error-message'}"
  1204. style="padding: 15px; margin-top: 20px; border-radius: 4px;">
  1205. <h3>${result.success ? '✅ Success!' : '❌ Error'}</h3>
  1206. <p>${result.message}</p>
  1207. </div>
  1208. `;
  1209. } catch (error) {
  1210. resultContainer.innerHTML = `
  1211. <div class="error-message" style="padding: 15px; margin-top: 20px; border-radius: 4px;">
  1212. <h3>❌ Error</h3>
  1213. <p>${error.message}</p>
  1214. </div>
  1215. `;
  1216. }
  1217. };
  1218. }
  1219.  
  1220. // Panel Implementation
  1221. function createPanel() {
  1222. const mainWrapper = document.createElement('div');
  1223. mainWrapper.className = 'main-content-wrapper';
  1224. document.body.appendChild(mainWrapper);
  1225.  
  1226. const container = document.createElement('div');
  1227. container.className = 'form-container';
  1228.  
  1229. const title = document.createElement('h2');
  1230. title.textContent = 'Roblox Multi-Feature Tool';
  1231. container.appendChild(title);
  1232.  
  1233. const buttons = [
  1234. { text: 'Game Information', url: '/getgameinfo' },
  1235. { text: 'Badge Information', url: '/getbadgeinfo' },
  1236. { text: 'User Information', url: '/getuserinfo' },
  1237. { text: 'Group Information', url: '/getgroupinfo' },
  1238. { text: 'Code Redemption', url: '/redeemcode' }
  1239. ];
  1240.  
  1241. buttons.forEach(button => {
  1242. const btn = document.createElement('button');
  1243. btn.className = 'panel-button';
  1244. btn.textContent = button.text;
  1245. btn.onclick = () => window.location.href = 'https://www.roblox.com' + button.url;
  1246. container.appendChild(btn);
  1247. });
  1248.  
  1249. mainWrapper.appendChild(container);
  1250. }
  1251.  
  1252. // Initialize based on current page
  1253. const currentPath = window.location.pathname;
  1254. switch(currentPath) {
  1255. case '/userpanel':
  1256. createPanel();
  1257. break;
  1258. case '/getgameinfo':
  1259. initializeGameInfo();
  1260. break;
  1261. case '/getbadgeinfo':
  1262. initializeBadgeInfo();
  1263. break;
  1264. case '/getuserinfo':
  1265. initializeUserInfo();
  1266. break;
  1267. case '/getgroupinfo':
  1268. initializeGroupInfo();
  1269. break;
  1270. case '/redeemcode':
  1271. initializeCodeRedemption();
  1272. break;
  1273. }
  1274. })();