Roblox Enhanced Experience

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})();