Greasy Fork 还支持 简体中文。

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