Roblox Multi-Feature User Panel.

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

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