Live counters, RAP values, best servers, rejoin last server, and stylish UI effects
Size
22.1 KB
Version
1.0.1
Created
Mar 17, 2026
Updated
5 days ago
1// ==UserScript==
2// @name Roblox Enhanced Experience
3// @description Live counters, RAP values, best servers, rejoin last server, and stylish UI effects
4// @version 1.0.1
5// @match https://*.roblox.com/*
6// @icon https://www.roblox.com/favicon.ico
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.xmlhttpRequest
10// @connect games.roblox.com
11// @connect economy.roblox.com
12// @connect users.roblox.com
13// @connect thumbnails.roblox.com
14// ==/UserScript==
15(function() {
16 'use strict';
17
18 // ============================================
19 // UTILITY FUNCTIONS
20 // ============================================
21
22 function debounce(func, wait) {
23 let timeout;
24 return function executedFunction(...args) {
25 const later = () => {
26 clearTimeout(timeout);
27 func(...args);
28 };
29 clearTimeout(timeout);
30 timeout = setTimeout(later, wait);
31 };
32 }
33
34 async function makeRobloxRequest(url) {
35 return new Promise((resolve, reject) => {
36 GM.xmlhttpRequest({
37 method: 'GET',
38 url: url,
39 headers: {
40 'Accept': 'application/json'
41 },
42 onload: (response) => {
43 try {
44 const data = JSON.parse(response.responseText);
45 resolve(data);
46 } catch (e) {
47 console.error('Failed to parse response:', e);
48 reject(e);
49 }
50 },
51 onerror: (error) => {
52 console.error('Request failed:', error);
53 reject(error);
54 }
55 });
56 });
57 }
58
59 // ============================================
60 // STYLISH CSS WITH GLOWING EFFECTS
61 // ============================================
62
63 const styles = `
64 @keyframes glow-pulse {
65 0%, 100% { box-shadow: 0 0 5px rgba(0, 162, 255, 0.5), 0 0 10px rgba(0, 162, 255, 0.3); }
66 50% { box-shadow: 0 0 20px rgba(0, 162, 255, 0.8), 0 0 30px rgba(0, 162, 255, 0.5); }
67 }
68
69 @keyframes shimmer {
70 0% { background-position: -1000px 0; }
71 100% { background-position: 1000px 0; }
72 }
73
74 @keyframes float {
75 0%, 100% { transform: translateY(0px); }
76 50% { transform: translateY(-10px); }
77 }
78
79 @keyframes fadeInUp {
80 from {
81 opacity: 0;
82 transform: translateY(20px);
83 }
84 to {
85 opacity: 1;
86 transform: translateY(0);
87 }
88 }
89
90 .rbx-enhanced-counter {
91 display: inline-flex;
92 align-items: center;
93 gap: 8px;
94 padding: 8px 16px;
95 background: linear-gradient(135deg, rgba(0, 162, 255, 0.15) 0%, rgba(138, 43, 226, 0.15) 100%);
96 border: 2px solid rgba(0, 162, 255, 0.4);
97 border-radius: 12px;
98 font-weight: 600;
99 color: #00a2ff;
100 animation: glow-pulse 2s infinite, fadeInUp 0.5s ease-out;
101 backdrop-filter: blur(10px);
102 transition: all 0.3s ease;
103 margin: 8px 4px;
104 }
105
106 .rbx-enhanced-counter:hover {
107 transform: translateY(-2px);
108 box-shadow: 0 0 25px rgba(0, 162, 255, 0.9), 0 0 40px rgba(0, 162, 255, 0.6);
109 border-color: rgba(0, 162, 255, 0.8);
110 }
111
112 .rbx-enhanced-counter .icon {
113 font-size: 18px;
114 filter: drop-shadow(0 0 5px rgba(0, 162, 255, 0.8));
115 }
116
117 .rbx-enhanced-counter .value {
118 font-size: 16px;
119 text-shadow: 0 0 10px rgba(0, 162, 255, 0.5);
120 }
121
122 .rbx-rap-display {
123 display: inline-flex;
124 align-items: center;
125 gap: 10px;
126 padding: 12px 20px;
127 background: linear-gradient(135deg, rgba(255, 215, 0, 0.2) 0%, rgba(255, 140, 0, 0.2) 100%);
128 border: 2px solid rgba(255, 215, 0, 0.5);
129 border-radius: 15px;
130 font-weight: 700;
131 font-size: 18px;
132 color: #ffd700;
133 animation: glow-pulse 2s infinite, fadeInUp 0.6s ease-out;
134 backdrop-filter: blur(10px);
135 margin: 12px 0;
136 box-shadow: 0 0 15px rgba(255, 215, 0, 0.4);
137 }
138
139 .rbx-rap-display:hover {
140 transform: scale(1.05);
141 box-shadow: 0 0 30px rgba(255, 215, 0, 0.8);
142 }
143
144 .rbx-rap-display .icon {
145 font-size: 24px;
146 animation: float 3s ease-in-out infinite;
147 }
148
149 .rbx-server-btn {
150 padding: 10px 20px;
151 background: linear-gradient(135deg, rgba(138, 43, 226, 0.2) 0%, rgba(75, 0, 130, 0.2) 100%);
152 border: 2px solid rgba(138, 43, 226, 0.5);
153 border-radius: 10px;
154 color: #ba55d3;
155 font-weight: 600;
156 cursor: pointer;
157 transition: all 0.3s ease;
158 animation: fadeInUp 0.7s ease-out;
159 backdrop-filter: blur(10px);
160 margin: 5px;
161 }
162
163 .rbx-server-btn:hover {
164 background: linear-gradient(135deg, rgba(138, 43, 226, 0.4) 0%, rgba(75, 0, 130, 0.4) 100%);
165 transform: translateY(-3px);
166 box-shadow: 0 0 20px rgba(138, 43, 226, 0.8);
167 border-color: rgba(138, 43, 226, 0.9);
168 }
169
170 .rbx-server-btn:active {
171 transform: translateY(-1px);
172 }
173
174 .rbx-rejoin-btn {
175 position: fixed;
176 bottom: 30px;
177 right: 30px;
178 padding: 15px 25px;
179 background: linear-gradient(135deg, rgba(30, 215, 96, 0.25) 0%, rgba(0, 150, 136, 0.25) 100%);
180 border: 3px solid rgba(30, 215, 96, 0.6);
181 border-radius: 50px;
182 color: #1ed760;
183 font-weight: 700;
184 font-size: 16px;
185 cursor: pointer;
186 z-index: 10000;
187 animation: glow-pulse 2s infinite, float 3s ease-in-out infinite;
188 backdrop-filter: blur(15px);
189 box-shadow: 0 5px 25px rgba(30, 215, 96, 0.4);
190 transition: all 0.3s ease;
191 }
192
193 .rbx-rejoin-btn:hover {
194 transform: scale(1.1) translateY(-5px);
195 box-shadow: 0 10px 40px rgba(30, 215, 96, 0.8);
196 border-color: rgba(30, 215, 96, 1);
197 }
198
199 .rbx-rejoin-btn:active {
200 transform: scale(1.05);
201 }
202
203 .rbx-most-played {
204 background: linear-gradient(135deg, rgba(255, 20, 147, 0.15) 0%, rgba(255, 105, 180, 0.15) 100%);
205 border: 2px solid rgba(255, 20, 147, 0.4);
206 border-radius: 15px;
207 padding: 20px;
208 margin: 20px 0;
209 animation: fadeInUp 0.8s ease-out;
210 backdrop-filter: blur(10px);
211 }
212
213 .rbx-most-played h3 {
214 color: #ff1493;
215 text-shadow: 0 0 10px rgba(255, 20, 147, 0.5);
216 margin-bottom: 15px;
217 font-size: 22px;
218 }
219
220 .rbx-game-card {
221 background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%);
222 border: 1px solid rgba(255, 20, 147, 0.3);
223 border-radius: 10px;
224 padding: 15px;
225 margin: 10px 0;
226 transition: all 0.3s ease;
227 cursor: pointer;
228 }
229
230 .rbx-game-card:hover {
231 transform: translateX(10px);
232 border-color: rgba(255, 20, 147, 0.8);
233 box-shadow: 0 0 20px rgba(255, 20, 147, 0.5);
234 }
235
236 .rbx-server-list {
237 max-height: 400px;
238 overflow-y: auto;
239 margin: 15px 0;
240 }
241
242 .rbx-server-item {
243 background: linear-gradient(135deg, rgba(100, 149, 237, 0.1) 0%, rgba(65, 105, 225, 0.1) 100%);
244 border: 1px solid rgba(100, 149, 237, 0.3);
245 border-radius: 8px;
246 padding: 12px;
247 margin: 8px 0;
248 display: flex;
249 justify-content: space-between;
250 align-items: center;
251 transition: all 0.3s ease;
252 }
253
254 .rbx-server-item:hover {
255 border-color: rgba(100, 149, 237, 0.8);
256 box-shadow: 0 0 15px rgba(100, 149, 237, 0.5);
257 transform: translateX(5px);
258 }
259
260 .rbx-server-item.best-server {
261 border: 2px solid rgba(255, 215, 0, 0.8);
262 background: linear-gradient(135deg, rgba(255, 215, 0, 0.15) 0%, rgba(255, 140, 0, 0.15) 100%);
263 animation: glow-pulse 2s infinite;
264 }
265
266 .rbx-loading {
267 display: inline-block;
268 width: 20px;
269 height: 20px;
270 border: 3px solid rgba(0, 162, 255, 0.3);
271 border-radius: 50%;
272 border-top-color: #00a2ff;
273 animation: spin 1s linear infinite;
274 }
275
276 @keyframes spin {
277 to { transform: rotate(360deg); }
278 }
279
280 .rbx-badge {
281 display: inline-block;
282 padding: 4px 10px;
283 background: linear-gradient(135deg, rgba(255, 215, 0, 0.3) 0%, rgba(255, 140, 0, 0.3) 100%);
284 border: 1px solid rgba(255, 215, 0, 0.6);
285 border-radius: 12px;
286 font-size: 12px;
287 font-weight: 600;
288 color: #ffd700;
289 text-shadow: 0 0 5px rgba(255, 215, 0, 0.5);
290 animation: shimmer 2s infinite linear;
291 background-size: 200% 100%;
292 background-image: linear-gradient(90deg, rgba(255, 215, 0, 0.3) 0%, rgba(255, 255, 255, 0.5) 50%, rgba(255, 215, 0, 0.3) 100%);
293 }
294 `;
295
296 TM_addStyle(styles);
297
298 // ============================================
299 // LIVE COUNTERS FOR EXPERIENCE PAGE
300 // ============================================
301
302 async function addLiveCounters() {
303 const urlMatch = window.location.href.match(/games\/(\d+)\//);
304 if (!urlMatch) return;
305
306 const placeId = urlMatch[1];
307 console.log('Adding live counters for place:', placeId);
308
309 // Find the game details section
310 const gameDetailsSection = document.querySelector('div[class*="game-details"]') ||
311 document.querySelector('div[class*="game-info"]') ||
312 document.querySelector('.game-calls-to-action');
313
314 if (!gameDetailsSection) {
315 console.log('Game details section not found, retrying...');
316 return;
317 }
318
319 // Create container for counters
320 const counterContainer = document.createElement('div');
321 counterContainer.id = 'rbx-live-counters';
322 counterContainer.style.cssText = 'display: flex; flex-wrap: wrap; margin: 15px 0;';
323
324 // Playing counter
325 const playingCounter = document.createElement('div');
326 playingCounter.className = 'rbx-enhanced-counter';
327 playingCounter.innerHTML = `
328 <span class="icon">👥</span>
329 <span class="label">Playing:</span>
330 <span class="value" id="rbx-playing-count"><div class="rbx-loading"></div></span>
331 `;
332
333 // Visits counter
334 const visitsCounter = document.createElement('div');
335 visitsCounter.className = 'rbx-enhanced-counter';
336 visitsCounter.innerHTML = `
337 <span class="icon">🎮</span>
338 <span class="label">Visits:</span>
339 <span class="value" id="rbx-visits-count"><div class="rbx-loading"></div></span>
340 `;
341
342 counterContainer.appendChild(playingCounter);
343 counterContainer.appendChild(visitsCounter);
344 gameDetailsSection.insertBefore(counterContainer, gameDetailsSection.firstChild);
345
346 // Update counters
347 async function updateCounters() {
348 try {
349 const universeData = await makeRobloxRequest(`https://apis.roblox.com/universes/v1/places/${placeId}/universe`);
350 const universeId = universeData.universeId;
351
352 const gameData = await makeRobloxRequest(`https://games.roblox.com/v1/games?universeIds=${universeId}`);
353
354 if (gameData.data && gameData.data[0]) {
355 const game = gameData.data[0];
356 document.getElementById('rbx-playing-count').textContent = game.playing.toLocaleString();
357 document.getElementById('rbx-visits-count').textContent = game.visits.toLocaleString();
358 }
359 } catch (error) {
360 console.error('Failed to update counters:', error);
361 }
362 }
363
364 updateCounters();
365 setInterval(updateCounters, 10000); // Update every 10 seconds
366 }
367
368 // ============================================
369 // RAP VALUE ON PROFILE
370 // ============================================
371
372 async function addRAPValue() {
373 const urlMatch = window.location.href.match(/users\/(\d+)\/profile/);
374 if (!urlMatch) return;
375
376 const userId = urlMatch[1];
377 console.log('Adding RAP value for user:', userId);
378
379 const profileHeader = document.querySelector('div[class*="profile-header"]') ||
380 document.querySelector('.profile-header-top') ||
381 document.querySelector('.header-caption');
382
383 if (!profileHeader) {
384 console.log('Profile header not found, retrying...');
385 return;
386 }
387
388 const rapDisplay = document.createElement('div');
389 rapDisplay.className = 'rbx-rap-display';
390 rapDisplay.innerHTML = `
391 <span class="icon">💎</span>
392 <span class="label">Account RAP:</span>
393 <span class="value" id="rbx-rap-value"><div class="rbx-loading"></div></span>
394 `;
395
396 profileHeader.appendChild(rapDisplay);
397
398 // Fetch RAP value
399 try {
400 const inventoryData = await makeRobloxRequest(`https://inventory.roblox.com/v1/users/${userId}/assets/collectibles?sortOrder=Asc&limit=100`);
401
402 let totalRAP = 0;
403 if (inventoryData.data) {
404 for (const item of inventoryData.data) {
405 if (item.recentAveragePrice) {
406 totalRAP += item.recentAveragePrice;
407 }
408 }
409 }
410
411 document.getElementById('rbx-rap-value').textContent = `R$ ${totalRAP.toLocaleString()}`;
412 } catch (error) {
413 console.error('Failed to fetch RAP:', error);
414 document.getElementById('rbx-rap-value').textContent = 'N/A';
415 }
416 }
417
418 // ============================================
419 // BEST SERVER FILTER
420 // ============================================
421
422 async function addBestServerFilter() {
423 const urlMatch = window.location.href.match(/games\/(\d+)\//);
424 if (!urlMatch) return;
425
426 const placeId = urlMatch[1];
427 console.log('Adding best server filter for place:', placeId);
428
429 // Wait for servers section
430 await new Promise(resolve => setTimeout(resolve, 2000));
431
432 const serversSection = document.querySelector('div[class*="server"]') ||
433 document.querySelector('.rbx-game-server-item-container');
434
435 if (!serversSection) {
436 console.log('Servers section not found');
437 return;
438 }
439
440 const filterButton = document.createElement('button');
441 filterButton.className = 'rbx-server-btn';
442 filterButton.innerHTML = '⭐ Find Best Server (Lowest Ping)';
443 filterButton.onclick = async () => {
444 filterButton.innerHTML = '<div class="rbx-loading"></div> Searching...';
445
446 try {
447 const universeData = await makeRobloxRequest(`https://apis.roblox.com/universes/v1/places/${placeId}/universe`);
448 const universeId = universeData.universeId;
449
450 const serversData = await makeRobloxRequest(`https://games.roblox.com/v1/games/${universeId}/servers/Public?sortOrder=Asc&limit=100`);
451
452 if (serversData.data && serversData.data.length > 0) {
453 // Find server with most players but not full
454 const bestServer = serversData.data
455 .filter(s => s.playing < s.maxPlayers)
456 .sort((a, b) => b.playing - a.playing)[0];
457
458 if (bestServer) {
459 alert(`Best server found!\nPlayers: ${bestServer.playing}/${bestServer.maxPlayers}\nPing: ${bestServer.ping || 'N/A'}ms`);
460 // Join the server
461 window.location.href = `roblox://placeId=${placeId}&gameInstanceId=${bestServer.id}`;
462 }
463 }
464 } catch (error) {
465 console.error('Failed to find best server:', error);
466 alert('Failed to find best server. Please try again.');
467 }
468
469 filterButton.innerHTML = '⭐ Find Best Server (Lowest Ping)';
470 };
471
472 serversSection.insertBefore(filterButton, serversSection.firstChild);
473 }
474
475 // ============================================
476 // REJOIN LAST SERVER
477 // ============================================
478
479 async function addRejoinButton() {
480 const urlMatch = window.location.href.match(/games\/(\d+)\//);
481 if (!urlMatch) return;
482
483 const placeId = urlMatch[1];
484
485 // Save current server when joining
486 const currentServerId = new URLSearchParams(window.location.search).get('gameInstanceId');
487 if (currentServerId) {
488 await GM.setValue('lastServer_' + placeId, currentServerId);
489 console.log('Saved last server:', currentServerId);
490 }
491
492 // Check if we have a last server
493 const lastServerId = await GM.getValue('lastServer_' + placeId);
494 if (!lastServerId) return;
495
496 const rejoinBtn = document.createElement('button');
497 rejoinBtn.className = 'rbx-rejoin-btn';
498 rejoinBtn.innerHTML = '🔄 Rejoin Last Server';
499 rejoinBtn.onclick = () => {
500 window.location.href = `roblox://placeId=${placeId}&gameInstanceId=${lastServerId}`;
501 };
502
503 document.body.appendChild(rejoinBtn);
504 }
505
506 // ============================================
507 // MOST PLAYED EXPERIENCES ON HOMEPAGE
508 // ============================================
509
510 async function addMostPlayedExperiences() {
511 if (!window.location.href.includes('roblox.com/home')) return;
512
513 console.log('Adding most played experiences to homepage');
514
515 const homeContainer = document.querySelector('div[class*="container-main"]') ||
516 document.querySelector('.content') ||
517 document.querySelector('main');
518
519 if (!homeContainer) {
520 console.log('Home container not found');
521 return;
522 }
523
524 const mostPlayedSection = document.createElement('div');
525 mostPlayedSection.className = 'rbx-most-played';
526 mostPlayedSection.innerHTML = `
527 <h3>🔥 Most Played Experiences Right Now</h3>
528 <div id="rbx-most-played-list"><div class="rbx-loading"></div></div>
529 `;
530
531 homeContainer.insertBefore(mostPlayedSection, homeContainer.firstChild);
532
533 try {
534 const gamesData = await makeRobloxRequest('https://games.roblox.com/v1/games/list?model.sortToken=&model.gameFilter=1&model.timeFilter=0&model.genreFilter=1&model.exclusiveStartId=0&model.sortOrder=2&model.gameSetTargetId=0&model.keyword=&model.startRows=0&model.maxRows=10&model.isKeywordSuggestionEnabled=true&model.contextCountryRegionId=0&model.contextUniverseId=0&model.pageContext.pageId=6f90d5a6-231e-4c3f-a48c-0cd0a0e6d3e8&model.pageContext.isSeeAllPage=false');
535
536 const listContainer = document.getElementById('rbx-most-played-list');
537 listContainer.innerHTML = '';
538
539 if (gamesData.games && gamesData.games.length > 0) {
540 gamesData.games.slice(0, 5).forEach((game, index) => {
541 const gameCard = document.createElement('div');
542 gameCard.className = 'rbx-game-card';
543 gameCard.innerHTML = `
544 <div style="display: flex; justify-content: space-between; align-items: center;">
545 <div>
546 <strong style="color: #ff1493; font-size: 16px;">${index + 1}. ${game.name}</strong>
547 <div style="color: #00a2ff; margin-top: 5px;">
548 👥 ${game.playerCount?.toLocaleString() || 'N/A'} playing
549 </div>
550 </div>
551 <span class="rbx-badge">TOP ${index + 1}</span>
552 </div>
553 `;
554 gameCard.onclick = () => {
555 window.location.href = `https://www.roblox.com/games/${game.placeId}`;
556 };
557 listContainer.appendChild(gameCard);
558 });
559 }
560 } catch (error) {
561 console.error('Failed to fetch most played games:', error);
562 document.getElementById('rbx-most-played-list').innerHTML = '<p style="color: #ff1493;">Failed to load games</p>';
563 }
564 }
565
566 // ============================================
567 // MAIN INITIALIZATION
568 // ============================================
569
570 function init() {
571 console.log('Roblox Enhanced Experience initialized');
572
573 // Wait for page to load
574 if (document.readyState === 'loading') {
575 document.addEventListener('DOMContentLoaded', runFeatures);
576 } else {
577 runFeatures();
578 }
579
580 // Re-run on navigation (for SPA behavior)
581 let lastUrl = location.href;
582 new MutationObserver(() => {
583 const url = location.href;
584 if (url !== lastUrl) {
585 lastUrl = url;
586 setTimeout(runFeatures, 1000);
587 }
588 }).observe(document.body, { subtree: true, childList: true });
589 }
590
591 function runFeatures() {
592 setTimeout(() => {
593 addLiveCounters();
594 addRAPValue();
595 addBestServerFilter();
596 addRejoinButton();
597 addMostPlayedExperiences();
598 }, 2000);
599 }
600
601 init();
602})();