Roblox Multi-Feature User Panel.

Download Roblox thumbnails, game icons, badge icons, and user info

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

  1. // ==UserScript==
  2. // @name Roblox Multi-Feature User Panel.
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4
  5. // @description Download Roblox thumbnails, game icons, badge icons, and user info
  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_xmlhttpRequest
  13. // @grant Gm_download
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. const style = document.createElement('style');
  21. style.textContent = `
  22. body {
  23. font-family: Arial, sans-serif;
  24. background-color: #f4f7f6;
  25. margin: 0;
  26. padding: 0;
  27. }
  28.  
  29. .main-content-wrapper {
  30. width: 100%;
  31. padding: 20px;
  32. margin-bottom: 120px;
  33. display: flex;
  34. flex-direction: column;
  35. align-items: center;
  36. min-height: calc(100vh - 200px);
  37. }
  38.  
  39. .form-container {
  40. background-color: #ffffff;
  41. padding: 20px;
  42. border-radius: 8px;
  43. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  44. width: 100%;
  45. max-width: 400px;
  46. text-align: center;
  47. margin: 20px auto;
  48. position: relative;
  49. z-index: 1;
  50. }
  51.  
  52. .input-field {
  53. width: 100%;
  54. padding: 10px;
  55. margin: 10px 0;
  56. border: 2px solid #ddd;
  57. border-radius: 4px;
  58. font-size: 16px;
  59. }
  60.  
  61. .submit-button, .panel-button {
  62. background-color: #4CAF50;
  63. color: white;
  64. padding: 12px 20px;
  65. border: none;
  66. border-radius: 4px;
  67. cursor: pointer;
  68. width: 100%;
  69. font-size: 16px;
  70. margin: 10px 0;
  71. }
  72.  
  73. .submit-button:hover, .panel-button:hover {
  74. background-color: #45a049;
  75. }
  76.  
  77. .result-container {
  78. background-color: #ffffff;
  79. padding: 20px;
  80. border-radius: 8px;
  81. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  82. width: 100%;
  83. max-width: 800px;
  84. margin: 20px auto 120px auto;
  85. position: relative;
  86. z-index: 1;
  87. }
  88.  
  89. .image-container {
  90. display: flex;
  91. flex-wrap: wrap;
  92. gap: 20px;
  93. justify-content: center;
  94. margin: 20px 0;
  95. }
  96.  
  97. .image-item {
  98. text-align: center;
  99. }
  100.  
  101. .image-item img {
  102. max-width: 200px;
  103. border-radius: 8px;
  104. margin-bottom: 10px;
  105. }
  106.  
  107. .info-text {
  108. margin: 10px 0;
  109. font-size: 16px;
  110. }
  111.  
  112. .error-message {
  113. color: #ff0000;
  114. margin: 10px 0;
  115. }
  116.  
  117. .success-message {
  118. color: #4CAF50;
  119. margin: 10px 0;
  120. }
  121. `;
  122. document.head.appendChild(style);
  123.  
  124. async function getUserIdFromUsername(username) {
  125. const response = await fetch(`https://users.roblox.com/v1/usernames/users`, {
  126. method: 'POST',
  127. headers: { 'Content-Type': 'application/json' },
  128. body: JSON.stringify({ usernames: [username] })
  129. });
  130. const data = await response.json();
  131. if (!data.data || data.data.length === 0) throw new Error('User not found');
  132. return data.data[0].id;
  133. }
  134.  
  135. function createBasicForm(placeholder, buttonText) {
  136. const container = document.createElement('div');
  137. container.className = 'form-container';
  138.  
  139. const input = document.createElement('input');
  140. input.type = 'text';
  141. input.className = 'input-field';
  142. input.placeholder = placeholder;
  143.  
  144. const button = document.createElement('button');
  145. button.className = 'submit-button';
  146. button.textContent = buttonText;
  147.  
  148. container.appendChild(input);
  149. container.appendChild(button);
  150.  
  151. return { container, input, button };
  152. }
  153.  
  154. function displayMessage(message, isError = false) {
  155. const messageDiv = document.createElement('div');
  156. messageDiv.className = isError ? 'error-message' : 'success-message';
  157. messageDiv.textContent = message;
  158. document.querySelector('.form-container').appendChild(messageDiv);
  159. setTimeout(() => messageDiv.remove(), 5000);
  160. }
  161.  
  162. function createResultContainer() {
  163. const container = document.createElement('div');
  164. container.className = 'result-container';
  165. return container;
  166. }
  167.  
  168. async function initializeGameInfo() {
  169. const mainWrapper = document.createElement('div');
  170. mainWrapper.className = 'main-content-wrapper';
  171. document.body.appendChild(mainWrapper);
  172.  
  173. const { container, input, button } = createBasicForm('Enter Game ID', 'Get Game Info');
  174. mainWrapper.appendChild(container);
  175.  
  176. const refreshContent = async (gameId) => {
  177. const existingResults = mainWrapper.querySelectorAll('.result-container');
  178. existingResults.forEach(result => result.remove());
  179.  
  180. try {
  181. // Get the universe ID first
  182. const placeResponse = await fetch(`https://apis.roblox.com/universes/v1/places/${gameId}/universe`);
  183. const placeData = await placeResponse.json();
  184. const universeId = placeData.universeId;
  185.  
  186. // Now fetch all data with the universe ID
  187. const [gameResponse, thumbnailResponse, iconResponse] = await Promise.all([
  188. fetch(`https://games.roblox.com/v1/games?universeIds=${universeId}`),
  189. fetch(`https://thumbnails.roblox.com/v1/games/${universeId}/thumbnails?size=768x432&format=Png&limit=10`),
  190. fetch(`https://thumbnails.roblox.com/v1/games/icons?universeIds=${universeId}&size=512x512&format=Png&isCircular=false`)
  191. ]);
  192.  
  193. const [gameData, thumbnailData, iconData] = await Promise.all([
  194. gameResponse.json(),
  195. thumbnailResponse.json(),
  196. iconResponse.json()
  197. ]);
  198.  
  199. const resultContainer = createResultContainer();
  200.  
  201. // Create image container for all images
  202. const imageContainer = document.createElement('div');
  203. imageContainer.className = 'image-container';
  204.  
  205. // Display game icon first
  206. if (iconData.data && iconData.data[0]) {
  207. const iconDiv = document.createElement('div');
  208. iconDiv.className = 'image-item';
  209.  
  210. const iconImg = document.createElement('img');
  211. iconImg.src = iconData.data[0].imageUrl;
  212. iconImg.alt = 'Game Icon';
  213.  
  214. const downloadIconBtn = document.createElement('button');
  215. downloadIconBtn.className = 'submit-button';
  216. downloadIconBtn.textContent = 'Download Icon';
  217. downloadIconBtn.onclick = () => GM_download({
  218. url: iconData.data[0].imageUrl,
  219. name: `game_${gameId}_icon.png`
  220. });
  221.  
  222. iconDiv.appendChild(iconImg);
  223. iconDiv.appendChild(downloadIconBtn);
  224. imageContainer.appendChild(iconDiv);
  225. }
  226.  
  227. // Display game information
  228. if (gameData.data && gameData.data[0]) {
  229. const gameInfo = document.createElement('div');
  230. gameInfo.className = 'info-text';
  231. gameInfo.innerHTML = `
  232. <h3>${gameData.data[0].name}</h3>
  233. <p>Description: ${gameData.data[0].description || 'No description'}</p>
  234. <p>Created: ${new Date(gameData.data[0].created).toLocaleDateString()}</p>
  235. <p>Updated: ${new Date(gameData.data[0].updated).toLocaleDateString()}</p>
  236. <p>Playing: ${gameData.data[0].playing || 0}</p>
  237. <p>Visits: ${gameData.data[0].visits || 0}</p>
  238. <p><a href="https://www.roblox.com/games/${gameId}" target="_blank">View Game Page</a></p>
  239. `;
  240. resultContainer.appendChild(gameInfo);
  241. }
  242.  
  243. // Display all thumbnails
  244. if (thumbnailData.data) {
  245. thumbnailData.data.forEach((thumb, index) => {
  246. const thumbDiv = document.createElement('div');
  247. thumbDiv.className = 'image-item';
  248.  
  249. const thumbImg = document.createElement('img');
  250. thumbImg.src = thumb.imageUrl;
  251. thumbImg.alt = `Game Thumbnail ${index + 1}`;
  252.  
  253. const downloadThumbBtn = document.createElement('button');
  254. downloadThumbBtn.className = 'submit-button';
  255. downloadThumbBtn.textContent = `Download Thumbnail ${index + 1}`;
  256. downloadThumbBtn.onclick = () => GM_download({
  257. url: thumb.imageUrl,
  258. name: `game_${gameId}_thumbnail_${index + 1}.png`
  259. });
  260.  
  261. thumbDiv.appendChild(thumbImg);
  262. thumbDiv.appendChild(downloadThumbBtn);
  263. imageContainer.appendChild(thumbDiv);
  264. });
  265. }
  266.  
  267. resultContainer.appendChild(imageContainer);
  268. mainWrapper.appendChild(resultContainer);
  269. displayMessage('Game information fetched successfully!');
  270. } catch (error) {
  271. displayMessage(error.message, true);
  272. }
  273. };
  274.  
  275. button.onclick = async () => {
  276. const gameId = input.value.trim();
  277. if (!gameId) {
  278. displayMessage('Please enter a game ID', true);
  279. return;
  280. }
  281. await refreshContent(gameId);
  282. };
  283. }
  284.  
  285. async function initializeBadgeInfo() {
  286. const mainWrapper = document.createElement('div');
  287. mainWrapper.className = 'main-content-wrapper';
  288. document.body.appendChild(mainWrapper);
  289.  
  290. const { container, input, button } = createBasicForm('Enter Badge ID', 'Get Badge Info');
  291. mainWrapper.appendChild(container);
  292.  
  293. const refreshContent = async (badgeId) => {
  294. // Remove any existing result containers
  295. const existingResults = mainWrapper.querySelectorAll('.result-container');
  296. existingResults.forEach(result => result.remove());
  297.  
  298. try {
  299. // Fetch badge info
  300. const infoResponse = await fetch(`https://badges.roblox.com/v1/badges/${badgeId}`);
  301. const badgeInfo = await infoResponse.json();
  302.  
  303. // Fetch badge icon
  304. const iconResponse = await fetch(`https://thumbnails.roblox.com/v1/badges/icons?badgeIds=${badgeId}&size=150x150&format=Png`);
  305. const iconData = await iconResponse.json();
  306.  
  307. const resultContainer = createResultContainer();
  308.  
  309. // Create image container first
  310. const imageContainer = document.createElement('div');
  311. imageContainer.className = 'image-container';
  312.  
  313. // Display badge icon
  314. if (iconData.data && iconData.data[0]) {
  315. const iconDiv = document.createElement('div');
  316. iconDiv.className = 'image-item';
  317.  
  318. const iconImg = document.createElement('img');
  319. iconImg.src = iconData.data[0].imageUrl;
  320. iconImg.alt = 'Badge Icon';
  321.  
  322. const downloadBtn = document.createElement('button');
  323. downloadBtn.className = 'submit-button';
  324. downloadBtn.textContent = 'Download Badge Icon';
  325. downloadBtn.onclick = () => GM_download({
  326. url: iconData.data[0].imageUrl,
  327. name: `badge_${badgeId}.png`
  328. });
  329.  
  330. iconDiv.appendChild(iconImg);
  331. iconDiv.appendChild(downloadBtn);
  332. imageContainer.appendChild(iconDiv);
  333. }
  334.  
  335. // Display badge information
  336. const infoDiv = document.createElement('div');
  337. infoDiv.className = 'info-text';
  338. infoDiv.innerHTML = `
  339. <h3>${badgeInfo.name}</h3>
  340. <p>${badgeInfo.description}</p>
  341. <p>Enabled: ${badgeInfo.enabled}</p>
  342. <p>Statistics:</p>
  343. <p>- Created: ${new Date(badgeInfo.created).toLocaleDateString()}</p>
  344. <p>- Updated: ${new Date(badgeInfo.updated).toLocaleDateString()}</p>
  345. `;
  346.  
  347. resultContainer.appendChild(imageContainer);
  348. resultContainer.appendChild(infoDiv);
  349. mainWrapper.appendChild(resultContainer);
  350. displayMessage('Badge information fetched successfully!');
  351. } catch (error) {
  352. displayMessage(error.message, true);
  353. }
  354. };
  355.  
  356. button.onclick = async () => {
  357. const badgeId = input.value.trim();
  358. if (!badgeId) {
  359. displayMessage('Please enter a badge ID', true);
  360. return;
  361. }
  362. await refreshContent(badgeId);
  363. };
  364. }
  365.  
  366. async function initializeUserInfo() {
  367. const mainWrapper = document.createElement('div');
  368. mainWrapper.className = 'main-content-wrapper';
  369. document.body.appendChild(mainWrapper);
  370.  
  371. const { container, input, button } = createBasicForm('Enter Username', 'Get User Info');
  372. mainWrapper.appendChild(container);
  373.  
  374. // Create a result container to hold the user info, initially hidden
  375. const resultContainer = createResultContainer();
  376. resultContainer.style.display = 'none'; // Hide initially
  377. mainWrapper.appendChild(resultContainer);
  378.  
  379. button.onclick = async () => {
  380. try {
  381. const username = input.value.trim();
  382. if (!username) throw new Error('Please enter a username');
  383.  
  384. const userId = await getUserIdFromUsername(username);
  385.  
  386. // Fetch all data in parallel
  387. const [
  388. presenceResponse,
  389. friendsResponse,
  390. followersResponse,
  391. thumbnailResponse,
  392. bustResponse,
  393. headshotResponse
  394. ] = await Promise.all([
  395. fetch(`https://presence.roblox.com/v1/presence/users`, {
  396. method: 'POST',
  397. headers: { 'Content-Type': 'application/json' },
  398. body: JSON.stringify({ userIds: [userId] })
  399. }),
  400. fetch(`https://friends.roblox.com/v1/users/${userId}/friends/count`),
  401. fetch(`https://friends.roblox.com/v1/users/${userId}/followers/count`),
  402. fetch(`https://thumbnails.roblox.com/v1/users/avatar?userIds=${userId}&size=420x420&format=Png`),
  403. fetch(`https://thumbnails.roblox.com/v1/users/avatar-bust?userIds=${userId}&size=420x420&format=Png`),
  404. fetch(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&size=420x420&format=Png`)
  405. ]);
  406.  
  407. const [presence, friends, followers, thumbnail, bust, headshot] = await Promise.all([
  408. presenceResponse.json(),
  409. friendsResponse.json(),
  410. followersResponse.json(),
  411. thumbnailResponse.json(),
  412. bustResponse.json(),
  413. headshotResponse.json()
  414. ]);
  415.  
  416. // Clear previous content in the result container
  417. resultContainer.innerHTML = '';
  418.  
  419. // Create thumbnails section
  420. const imageContainer = document.createElement('div');
  421. imageContainer.className = 'image-container';
  422.  
  423. // Helper function to create image sections
  424. const createImageSection = (data, type) => {
  425. if (data.data && data.data[0]) {
  426. const div = document.createElement('div');
  427. div.className = 'image-item';
  428.  
  429. const img = document.createElement('img');
  430. img.src = data.data[0].imageUrl;
  431. img.alt = `${type} thumbnail`;
  432.  
  433. const downloadBtn = document.createElement('button');
  434. downloadBtn.className = 'submit-button';
  435. downloadBtn.textContent = `Download ${type}`;
  436. downloadBtn.onclick = () => GM_download({
  437. url: data.data[0].imageUrl,
  438. name: `${username}_${type}.png`
  439. });
  440.  
  441. div.appendChild(img);
  442. div.appendChild(downloadBtn);
  443. imageContainer.appendChild(div);
  444. }
  445. };
  446.  
  447. // Add all thumbnails
  448. createImageSection(thumbnail, 'Full Avatar');
  449. createImageSection(bust, 'Bust');
  450. createImageSection(headshot, 'Headshot');
  451.  
  452. // Create user info section
  453. const userInfo = document.createElement('div');
  454. userInfo.className = 'info-text';
  455.  
  456. const userPresence = presence.userPresences[0];
  457. userInfo.innerHTML = `
  458. <h3>User Information for ${username}</h3>
  459. <p>User ID: ${userId}</p>
  460. <p>Online Status: ${userPresence.userPresenceType === 0 ? 'Offline' : 'Online'}</p>
  461. <p>Friends Count: ${friends.count}</p>
  462. <p>Followers Count: ${followers.count}</p>
  463. <p>Profile Link: <a href="https://www.roblox.com/users/${userId}/profile" target="_blank">View Profile</a></p>
  464. ${userPresence.userPresenceType !== 0 ? `<p>Last Location: ${userPresence.lastLocation}</p>` : ''}
  465. `;
  466.  
  467. // Append the new content to the result container
  468. resultContainer.appendChild(imageContainer);
  469. resultContainer.appendChild(userInfo);
  470. resultContainer.style.display = 'block'; // Show the result container
  471. displayMessage('User information fetched successfully!');
  472. } catch (error) {
  473. displayMessage(error.message, true);
  474. }
  475. };
  476. }
  477. // Add this new function:
  478. async function initializeGroupInfo() {
  479. const mainWrapper = document.createElement('div');
  480. mainWrapper.className = 'main-content-wrapper';
  481. document.body.appendChild(mainWrapper);
  482.  
  483. const { container, input, button } = createBasicForm('Enter Group ID', 'Get Group Info');
  484. mainWrapper.appendChild(container);
  485.  
  486. const refreshContent = async (groupId) => {
  487. // Remove any existing result containers
  488. const existingResults = mainWrapper.querySelectorAll('.result-container');
  489. existingResults.forEach(result => result.remove());
  490.  
  491. try {
  492. // Fetch all group data in parallel
  493. const [
  494. groupResponse,
  495. membersResponse,
  496. iconResponse,
  497. rolesResponse
  498. ] = await Promise.all([
  499. fetch(`https://groups.roblox.com/v1/groups/${groupId}`),
  500. fetch(`https://groups.roblox.com/v1/groups/${groupId}/membership`),
  501. fetch(`https://thumbnails.roblox.com/v1/groups/icons?groupIds=${groupId}&size=420x420&format=Png`),
  502. fetch(`https://groups.roblox.com/v1/groups/${groupId}/roles`)
  503. ]);
  504.  
  505. const [groupInfo, membersInfo, iconData, rolesInfo] = await Promise.all([
  506. groupResponse.json(),
  507. membersResponse.json(),
  508. iconResponse.json(),
  509. rolesResponse.json()
  510. ]);
  511.  
  512. const resultContainer = createResultContainer();
  513.  
  514. // Create image container for group icon
  515. const imageContainer = document.createElement('div');
  516. imageContainer.className = 'image-container';
  517.  
  518. // Display group icon
  519. if (iconData.data && iconData.data[0]) {
  520. const iconDiv = document.createElement('div');
  521. iconDiv.className = 'image-item';
  522.  
  523. const iconImg = document.createElement('img');
  524. iconImg.src = iconData.data[0].imageUrl;
  525. iconImg.alt = 'Group Icon';
  526.  
  527. const downloadBtn = document.createElement('button');
  528. downloadBtn.className = 'submit-button';
  529. downloadBtn.textContent = 'Download Group Icon';
  530. downloadBtn.onclick = () => GM_download({
  531. url: iconData.data[0].imageUrl,
  532. name: `group_${groupId}_icon.png`
  533. });
  534.  
  535. iconDiv.appendChild(iconImg);
  536. iconDiv.appendChild(downloadBtn);
  537. imageContainer.appendChild(iconDiv);
  538. }
  539.  
  540. // Display group information
  541. const infoDiv = document.createElement('div');
  542. infoDiv.className = 'info-text';
  543. infoDiv.innerHTML = `
  544. <h3>${groupInfo.name}</h3>
  545. <p>Description: ${groupInfo.description || 'No description'}</p>
  546. <p>Owner: ${groupInfo.owner ? groupInfo.owner.username : 'No owner'}</p>
  547. <p>Member Count: ${membersInfo.memberCount || 0}</p>
  548. <p>Created: ${new Date(groupInfo.created).toLocaleDateString()}</p>
  549. <p>Public Entry: ${groupInfo.publicEntryAllowed ? 'Yes' : 'No'}</p>
  550. <p><a href="https://www.roblox.com/groups/${groupId}" target="_blank">View Group Page</a></p>
  551. <h4>Roles:</h4>
  552. <ul>
  553. ${rolesInfo.roles.map(role => `
  554. <li>${role.name} (${role.memberCount} members)</li>
  555. `).join('')}
  556. </ul>
  557. `;
  558.  
  559. resultContainer.appendChild(imageContainer);
  560. resultContainer.appendChild(infoDiv);
  561. mainWrapper.appendChild(resultContainer);
  562. displayMessage('Group information fetched successfully!');
  563. } catch (error) {
  564. displayMessage(error.message, true);
  565. }
  566. };
  567.  
  568. button.onclick = async () => {
  569. const groupId = input.value.trim();
  570. if (!groupId) {
  571. displayMessage('Please enter a group ID', true);
  572. return;
  573. }
  574. await refreshContent(groupId);
  575. };
  576. }
  577. // Panel Implementation
  578. function createPanel() {
  579. const mainWrapper = document.createElement('div');
  580. mainWrapper.className = 'main-content-wrapper';
  581. document.body.appendChild(mainWrapper);
  582.  
  583. const container = document.createElement('div');
  584. container.className = 'form-container';
  585.  
  586. const title = document.createElement('h2');
  587. title.textContent = 'Roblox Multi-Feature Tool';
  588. container.appendChild(title);
  589.  
  590. const buttons = [
  591. { text: 'Game Information', url: '/getgameinfo' },
  592. { text: 'Badge Information', url: '/getbadgeinfo' },
  593. { text: 'User Information', url: '/getuserinfo' },
  594. { text: 'Group Information', url: '/getgroupinfo' }
  595. ];
  596.  
  597. buttons.forEach(button => {
  598. const btn = document.createElement('button');
  599. btn.className = 'panel-button';
  600. btn.textContent = button.text;
  601. btn.onclick = () => window.location.href = 'https://www.roblox.com' + button.url;
  602. container.appendChild(btn);
  603. });
  604.  
  605. mainWrapper.appendChild(container);
  606. }
  607.  
  608. // Initialize based on current page
  609. const currentPath = window.location.pathname;
  610. switch(currentPath) {
  611. case '/userpanel':
  612. createPanel();
  613. break;
  614. case '/getgameinfo':
  615. initializeGameInfo();
  616. break;
  617. case '/getbadgeinfo':
  618. initializeBadgeInfo();
  619. break;
  620. case '/getuserinfo':
  621. initializeUserInfo();
  622. break;
  623. case '/getgroupinfo':
  624. initializeGroupInfo();
  625. break;
  626. }
  627. })();