reaper-arcade-hub 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +34 -5
- package/js/app.js +207 -31
- package/js/firebase-client.js +96 -0
- package/main/index.html +34 -5
- package/main/js/app.js +207 -31
- package/main/js/firebase-client.js +96 -0
- package/package.json +3 -13
- package/verify/js/firebase-client.js +96 -0
package/index.html
CHANGED
|
@@ -280,6 +280,16 @@
|
|
|
280
280
|
<input type="checkbox" id="mod-slowmode-toggle" style="width: 22px; height: 22px; cursor: pointer; accent-color: #00ffaa;" onchange="app.toggleSlowMode(this.checked)">
|
|
281
281
|
</div>
|
|
282
282
|
|
|
283
|
+
<!-- Live Online Users for Mods -->
|
|
284
|
+
<div class="mod-section">
|
|
285
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
|
286
|
+
<h3 style="font-size: 0.95rem; color: #00e676;">🟢 ACTIVE PLAYERS ONLINE (REALTIME)</h3>
|
|
287
|
+
<button onclick="app.renderModUsersDirectory()" class="btn-stealth" style="font-size: 0.75rem;">REFRESH</button>
|
|
288
|
+
</div>
|
|
289
|
+
<p style="color: #88aacc; font-size: 0.8rem; margin-bottom: 0.65rem;">See connected users, their rank, and their active site version.</p>
|
|
290
|
+
<div id="mod-users-directory" style="max-height: 200px; overflow-y: auto; display: flex; flex-direction: column; gap: 0.4rem;"></div>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
283
293
|
<!-- Timeout User Box -->
|
|
284
294
|
<div class="mod-section">
|
|
285
295
|
<h3 style="font-size: 0.95rem; color: #fff; margin-bottom: 0.35rem;">TIMEOUT / MUTE USER</h3>
|
|
@@ -320,11 +330,15 @@
|
|
|
320
330
|
<!-- Superpower 1: Live Real-Time Analytics Dashboard -->
|
|
321
331
|
<div class="admin-section" style="border-color: #00d2ff; background: rgba(0, 210, 255, 0.05);">
|
|
322
332
|
<h3 style="font-size: 0.95rem; color: #00d2ff; margin-bottom: 0.6rem;">📊 LIVE HUB SYSTEM ANALYTICS</h3>
|
|
323
|
-
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
|
333
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 0.65rem;">
|
|
324
334
|
<div style="background: #040912; border: 1px solid var(--blue-border); padding: 0.65rem; border-radius: 6px; text-align: center;">
|
|
325
335
|
<div style="font-size: 0.7rem; color: #88aacc;">TOTAL GAMES</div>
|
|
326
336
|
<div style="font-size: 1.3rem; font-weight: 800; color: #00ffaa;" id="admin-stat-games">788</div>
|
|
327
337
|
</div>
|
|
338
|
+
<div style="background: #040912; border: 1px solid var(--blue-border); padding: 0.65rem; border-radius: 6px; text-align: center;">
|
|
339
|
+
<div style="font-size: 0.7rem; color: #88aacc;">ONLINE USERS</div>
|
|
340
|
+
<div style="font-size: 1.3rem; font-weight: 800; color: #00e676;" id="admin-stat-online">1</div>
|
|
341
|
+
</div>
|
|
328
342
|
<div style="background: #040912; border: 1px solid var(--blue-border); padding: 0.65rem; border-radius: 6px; text-align: center;">
|
|
329
343
|
<div style="font-size: 0.7rem; color: #88aacc;">TURBO CACHED</div>
|
|
330
344
|
<div style="font-size: 1.3rem; font-weight: 800; color: #00d2ff;" id="admin-stat-cached">0</div>
|
|
@@ -340,6 +354,21 @@
|
|
|
340
354
|
</div>
|
|
341
355
|
</div>
|
|
342
356
|
|
|
357
|
+
<!-- Superpower 6: Cross-Site Version Control & Auto-Forwarder -->
|
|
358
|
+
<div class="admin-section" style="border-color: #00ffaa; background: #02140a;">
|
|
359
|
+
<h3 style="font-size: 0.95rem; color: #00ffaa; margin-bottom: 0.4rem;">🌐 GLOBAL VERSION CONTROLLER & AUTO-REDIRECT</h3>
|
|
360
|
+
<p style="color: #88ffcc; font-size: 0.8rem; margin-bottom: 0.75rem;">
|
|
361
|
+
When you publish a new version, update this once. Any user visiting ANY old version (1.0.9, 2.0.x, etc.) will be notified and instantly brought to the new site!
|
|
362
|
+
</p>
|
|
363
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.65rem;">
|
|
364
|
+
<input type="text" id="admin-latest-version-input" class="red-chat-input" placeholder="Latest Version (e.g. 2.3.0)" value="2.3.0" style="max-width: 140px;">
|
|
365
|
+
<input type="text" id="admin-latest-url-input" class="red-chat-input" placeholder="New UNPKG URL" value="https://unpkg.com/reaper-arcade-hub@2.3.0/main/index.html" style="flex: 1; min-width: 250px;">
|
|
366
|
+
<button class="btn-primary" style="background: #00e676; color: #02050a; font-weight: 900; border-color: #00ff88;" onclick="app.publishLatestVersionToFirebase()">
|
|
367
|
+
BROADCAST VERSION & FORWARD ALL SITES
|
|
368
|
+
</button>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
343
372
|
<!-- Superpower 2: Global Enforce Turbo Mode (Anti-Lag Broadcast) -->
|
|
344
373
|
<div class="admin-section" style="border-color: #00ffaa; background: #03140c;">
|
|
345
374
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
@@ -412,14 +441,14 @@
|
|
|
412
441
|
</div>
|
|
413
442
|
</div>
|
|
414
443
|
|
|
415
|
-
<!-- Connected Players Directory (With
|
|
444
|
+
<!-- Connected Real Players Directory (With Version & Controls) -->
|
|
416
445
|
<div class="admin-section">
|
|
417
446
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
|
418
|
-
<h3 style="font-size: 0.95rem; color: #
|
|
447
|
+
<h3 style="font-size: 0.95rem; color: #00e676;">🟢 REALTIME ONLINE PLAYERS (ZERO BOTS)</h3>
|
|
419
448
|
<button onclick="app.renderAdminUsersDirectory()" class="btn-stealth" style="font-size: 0.75rem;">REFRESH LIST</button>
|
|
420
449
|
</div>
|
|
421
|
-
<p style="color: #88aacc; font-size: 0.8rem; margin-bottom: 0.65rem;">
|
|
422
|
-
<div id="admin-users-directory" style="max-height:
|
|
450
|
+
<p style="color: #88aacc; font-size: 0.8rem; margin-bottom: 0.65rem;">Active real players currently connected to the hub. View their current site version, rank, and moderation actions.</p>
|
|
451
|
+
<div id="admin-users-directory" style="max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 0.4rem;"></div>
|
|
423
452
|
</div>
|
|
424
453
|
|
|
425
454
|
<!-- Custom Access Key Generator -->
|
package/js/app.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
// REAPER HUB - Core Engine (3
|
|
1
|
+
// REAPER HUB - Core Engine (v2.3.0 Turbo Edition)
|
|
2
|
+
window.REAPER_VERSION = "2.3.0";
|
|
3
|
+
window.REAPER_LATEST_URL = "https://unpkg.com/reaper-arcade-hub@2.3.0/main/index.html";
|
|
2
4
|
|
|
3
5
|
class ReaperHubApp {
|
|
4
6
|
constructor() {
|
|
@@ -28,6 +30,7 @@ class ReaperHubApp {
|
|
|
28
30
|
this.lastActivityTime = Date.now();
|
|
29
31
|
this.isPausedForInactivity = false;
|
|
30
32
|
this.leaderboardData = [];
|
|
33
|
+
this.onlineUsers = [];
|
|
31
34
|
|
|
32
35
|
// Turbo Anti-Lag Engine & Superpower State
|
|
33
36
|
this.turboMode = localStorage.getItem('reaper_turbo_mode') !== 'false'; // Default TRUE for high FPS
|
|
@@ -218,6 +221,25 @@ class ReaperHubApp {
|
|
|
218
221
|
const select = document.getElementById('admin-slowmode-interval');
|
|
219
222
|
if (select) select.value = this.slowModeInterval.toString();
|
|
220
223
|
});
|
|
224
|
+
window.reaperFirebase.purgeGhostUsers();
|
|
225
|
+
window.reaperFirebase.registerOnlinePresence({
|
|
226
|
+
username: this.currentUser.username,
|
|
227
|
+
rank: this.currentUser.rank,
|
|
228
|
+
version: window.REAPER_VERSION,
|
|
229
|
+
playtime: this.playtimeSeconds
|
|
230
|
+
});
|
|
231
|
+
window.reaperFirebase.listenOnlineUsers((users) => {
|
|
232
|
+
this.onlineUsers = users || [];
|
|
233
|
+
this.renderAdminUsersDirectory();
|
|
234
|
+
this.renderModUsersDirectory();
|
|
235
|
+
const onlineCountEl = document.getElementById('admin-stat-online');
|
|
236
|
+
if (onlineCountEl) onlineCountEl.textContent = this.onlineUsers.length;
|
|
237
|
+
});
|
|
238
|
+
window.reaperFirebase.listenLatestVersion((info) => {
|
|
239
|
+
if (info && info.version && info.version !== window.REAPER_VERSION && info.forceUpgrade !== false) {
|
|
240
|
+
this.handleOldVersionDetected(info);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
// 9. Render UI, Games, Leaderboard, Turbo Engine
|
|
@@ -795,40 +817,192 @@ class ReaperHubApp {
|
|
|
795
817
|
const container = document.getElementById('admin-users-directory');
|
|
796
818
|
if (!container) return;
|
|
797
819
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
820
|
+
// Collect REAL active users only (Zero fake bots/users)
|
|
821
|
+
const userMap = new Map();
|
|
822
|
+
if (this.currentUser && this.currentUser.username && this.currentUser.username !== 'User') {
|
|
823
|
+
userMap.set(this.currentUser.username, {
|
|
824
|
+
username: this.currentUser.username,
|
|
825
|
+
rank: this.currentUser.rank || 'member',
|
|
826
|
+
version: window.REAPER_VERSION || '2.3.0',
|
|
827
|
+
playtime: this.playtimeSeconds || 0
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (this.onlineUsers && this.onlineUsers.length > 0) {
|
|
832
|
+
this.onlineUsers.forEach(u => {
|
|
833
|
+
if (u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon') {
|
|
834
|
+
userMap.set(u.username, u);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const realUsers = Array.from(userMap.values());
|
|
840
|
+
|
|
841
|
+
if (realUsers.length <= 1) {
|
|
842
|
+
container.innerHTML = `
|
|
843
|
+
<div style="color: #88aacc; padding: 1.15rem; text-align: center; font-size: 0.82rem; font-family: var(--font-mono); background: #040912; border: 1px dashed var(--blue-border); border-radius: 6px;">
|
|
844
|
+
🟢 Only you are currently online. (0 fake users / 0 ghost bots)
|
|
845
|
+
</div>
|
|
846
|
+
`;
|
|
847
|
+
return;
|
|
802
848
|
}
|
|
803
849
|
|
|
804
|
-
container.innerHTML =
|
|
850
|
+
container.innerHTML = realUsers.map(u => {
|
|
805
851
|
const isSelf = u.username === this.currentUser.username;
|
|
806
852
|
const currentRank = (u.rank || 'member').toLowerCase();
|
|
807
|
-
const timeStr = this.formatPlaytime(u.seconds || 0);
|
|
853
|
+
const timeStr = this.formatPlaytime(u.playtime || u.seconds || 0);
|
|
854
|
+
const userVer = u.version || '2.3.0';
|
|
855
|
+
const isLatest = userVer === window.REAPER_VERSION;
|
|
856
|
+
|
|
857
|
+
const verBadge = isLatest
|
|
858
|
+
? `<span style="color: #00ffaa; font-size: 0.68rem; font-weight: 800; border: 1px solid #00ffaa; padding: 1px 5px; border-radius: 4px;">v${this.escapeHtml(userVer)} (LATEST)</span>`
|
|
859
|
+
: `<span style="color: #ff3377; font-size: 0.68rem; font-weight: 800; border: 1px solid #ff3377; background: rgba(255, 51, 119, 0.15); padding: 1px 5px; border-radius: 4px;">⚠️ OLD VER (v${this.escapeHtml(userVer)})</span>`;
|
|
808
860
|
|
|
809
861
|
return `
|
|
810
|
-
<div style="display: flex; justify-content: space-between; align-items: center; background: #041022; padding: 0.
|
|
811
|
-
<div>
|
|
812
|
-
<
|
|
862
|
+
<div style="display: flex; justify-content: space-between; align-items: center; background: #041022; padding: 0.5rem 0.75rem; border-radius: 6px; border: 1px solid var(--blue-border); font-size: 0.8rem; margin-bottom: 0.35rem;">
|
|
863
|
+
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
|
|
864
|
+
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #00ffaa; box-shadow: 0 0 8px #00ffaa;"></span>
|
|
865
|
+
<strong style="color: #fff;" class="${currentRank === 'owner' ? 'owner-animated-name' : ''}">${this.escapeHtml(u.username)}</strong>
|
|
813
866
|
<span class="rank-pill rank-${currentRank}">${currentRank.toUpperCase()}</span>
|
|
814
|
-
|
|
867
|
+
${verBadge}
|
|
868
|
+
<span style="color: #88ccff; font-size: 0.75rem;">Playtime: ${timeStr}</span>
|
|
815
869
|
</div>
|
|
816
870
|
<div style="display: flex; gap: 0.35rem;">
|
|
817
871
|
${!isSelf ? `
|
|
818
872
|
${currentRank === 'member' ? `
|
|
819
|
-
<button onclick="app.setUserRank('${u.username}', 'chat_mod')" class="btn-stealth" style="color: #00ffaa; padding: 0.2rem 0.5rem; font-size: 0.72rem;">+ PROMOTE MOD</button>
|
|
873
|
+
<button onclick="app.setUserRank('${this.escapeHtml(u.username)}', 'chat_mod')" class="btn-stealth" style="color: #00ffaa; padding: 0.2rem 0.5rem; font-size: 0.72rem;">+ PROMOTE MOD</button>
|
|
820
874
|
` : currentRank === 'chat_mod' || currentRank === 'mod' ? `
|
|
821
|
-
<button onclick="app.setUserRank('${u.username}', 'member')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">- DE-RANK
|
|
875
|
+
<button onclick="app.setUserRank('${this.escapeHtml(u.username)}', 'member')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">- DE-RANK</button>
|
|
822
876
|
` : ''}
|
|
823
|
-
<button onclick="app.kickUser('${u.username}')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">KICK</button>
|
|
824
|
-
<button onclick="app.banUser('${u.username}')" class="btn-stealth" style="color: #ff5555; padding: 0.2rem 0.5rem; font-size: 0.72rem;">BAN</button>
|
|
825
|
-
` : '<span style="color: #ffd700; font-weight: 800;">YOU</span>'}
|
|
877
|
+
<button onclick="app.kickUser('${this.escapeHtml(u.username)}')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">KICK</button>
|
|
878
|
+
<button onclick="app.banUser('${this.escapeHtml(u.username)}')" class="btn-stealth" style="color: #ff5555; padding: 0.2rem 0.5rem; font-size: 0.72rem;">BAN</button>
|
|
879
|
+
` : '<span style="color: #ffd700; font-weight: 800; font-size: 0.75rem;">YOU</span>'}
|
|
880
|
+
</div>
|
|
881
|
+
</div>
|
|
882
|
+
`;
|
|
883
|
+
}).join('');
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
renderModUsersDirectory() {
|
|
887
|
+
const container = document.getElementById('mod-users-directory');
|
|
888
|
+
if (!container) return;
|
|
889
|
+
|
|
890
|
+
const userMap = new Map();
|
|
891
|
+
if (this.currentUser && this.currentUser.username && this.currentUser.username !== 'User') {
|
|
892
|
+
userMap.set(this.currentUser.username, {
|
|
893
|
+
username: this.currentUser.username,
|
|
894
|
+
rank: this.currentUser.rank || 'member',
|
|
895
|
+
version: window.REAPER_VERSION || '2.3.0'
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (this.onlineUsers && this.onlineUsers.length > 0) {
|
|
900
|
+
this.onlineUsers.forEach(u => {
|
|
901
|
+
if (u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon') {
|
|
902
|
+
userMap.set(u.username, u);
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const realUsers = Array.from(userMap.values());
|
|
908
|
+
|
|
909
|
+
if (realUsers.length <= 1) {
|
|
910
|
+
container.innerHTML = `
|
|
911
|
+
<div style="color: #88aacc; padding: 0.85rem; text-align: center; font-size: 0.8rem; font-family: var(--font-mono); background: #040912; border: 1px dashed var(--blue-border); border-radius: 6px;">
|
|
912
|
+
🟢 Only you are currently online. (0 fake users)
|
|
913
|
+
</div>
|
|
914
|
+
`;
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
container.innerHTML = realUsers.map(u => {
|
|
919
|
+
const isSelf = u.username === this.currentUser.username;
|
|
920
|
+
const currentRank = (u.rank || 'member').toLowerCase();
|
|
921
|
+
const userVer = u.version || '2.3.0';
|
|
922
|
+
const isLatest = userVer === window.REAPER_VERSION;
|
|
923
|
+
|
|
924
|
+
const verBadge = isLatest
|
|
925
|
+
? `<span style="color: #00ffaa; font-size: 0.65rem; font-weight: 800; border: 1px solid #00ffaa; padding: 1px 4px; border-radius: 4px;">v${this.escapeHtml(userVer)} (LATEST)</span>`
|
|
926
|
+
: `<span style="color: #ff3377; font-size: 0.65rem; font-weight: 800; border: 1px solid #ff3377; background: rgba(255, 51, 119, 0.15); padding: 1px 4px; border-radius: 4px;">⚠️ OLD VER (v${this.escapeHtml(userVer)})</span>`;
|
|
927
|
+
|
|
928
|
+
return `
|
|
929
|
+
<div style="display: flex; justify-content: space-between; align-items: center; background: #041022; padding: 0.4rem 0.65rem; border-radius: 6px; border: 1px solid var(--blue-border); font-size: 0.78rem; margin-bottom: 0.3rem;">
|
|
930
|
+
<div style="display: flex; align-items: center; gap: 0.45rem;">
|
|
931
|
+
<span style="display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #00ffaa;"></span>
|
|
932
|
+
<strong style="color: #fff;">${this.escapeHtml(u.username)}</strong>
|
|
933
|
+
<span class="rank-pill rank-${currentRank}">${currentRank.toUpperCase()}</span>
|
|
934
|
+
${verBadge}
|
|
935
|
+
</div>
|
|
936
|
+
<div>
|
|
937
|
+
${!isSelf ? `
|
|
938
|
+
<button onclick="app.modApplyTimeoutToUser('${this.escapeHtml(u.username)}')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.4rem; font-size: 0.7rem;">MUTE</button>
|
|
939
|
+
` : '<span style="color: #00ffaa; font-size: 0.7rem;">YOU</span>'}
|
|
826
940
|
</div>
|
|
827
941
|
</div>
|
|
828
942
|
`;
|
|
829
943
|
}).join('');
|
|
830
944
|
}
|
|
831
945
|
|
|
946
|
+
// Cross-Site Old Version Interceptor & Forwarder
|
|
947
|
+
handleOldVersionDetected(info) {
|
|
948
|
+
if (!info || !info.version || info.version === window.REAPER_VERSION) return;
|
|
949
|
+
if (info.forceUpgrade === false) return;
|
|
950
|
+
|
|
951
|
+
const currentVer = window.REAPER_VERSION;
|
|
952
|
+
const latestVer = info.version;
|
|
953
|
+
const latestUrl = info.url || window.REAPER_LATEST_URL;
|
|
954
|
+
|
|
955
|
+
let modal = document.getElementById('version-upgrade-modal');
|
|
956
|
+
if (!modal) {
|
|
957
|
+
modal = document.createElement('div');
|
|
958
|
+
modal.id = 'version-upgrade-modal';
|
|
959
|
+
modal.style.cssText = 'position: fixed; inset: 0; background: rgba(2, 5, 10, 0.97); z-index: 9999999999; display: flex; align-items: center; justify-content: center; padding: 1.5rem; text-align: center; font-family: var(--font-heading); backdrop-filter: blur(12px);';
|
|
960
|
+
document.body.appendChild(modal);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
modal.innerHTML = `
|
|
964
|
+
<div style="max-width: 500px; background: #040e1c; border: 2px solid #00d2ff; border-radius: 12px; padding: 2.5rem 2rem; box-shadow: 0 0 50px rgba(0, 210, 255, 0.4);">
|
|
965
|
+
<div style="font-size: 3rem; margin-bottom: 0.5rem;">⚡</div>
|
|
966
|
+
<div style="font-size: 1.4rem; font-weight: 900; color: #00d2ff; letter-spacing: 0.08em; margin-bottom: 0.5rem;">NEW VERSION ACTIVE</div>
|
|
967
|
+
<p style="color: #adc4e2; font-size: 0.9rem; line-height: 1.6; margin-bottom: 1.25rem;">
|
|
968
|
+
You are currently visiting an older version of Reaper Hub (<strong style="color: #ff5555;">v${currentVer}</strong>).<br>
|
|
969
|
+
The new upgraded site is live at <strong style="color: #00ffaa;">v${latestVer}</strong> with zero lag and fixes.
|
|
970
|
+
</p>
|
|
971
|
+
<div style="color: #00ffaa; font-size: 0.85rem; font-weight: 800; margin-bottom: 1.5rem; font-family: var(--font-mono);">
|
|
972
|
+
🚀 BRINGING YOU TO THE NEW SITE IN 2 SECONDS...
|
|
973
|
+
</div>
|
|
974
|
+
<button class="btn-primary" style="width: 100%; justify-content: center; padding: 0.85rem; font-size: 0.9rem; background: #00d2ff; color: #000; font-weight: 900;" onclick="window.location.replace('${latestUrl}')">
|
|
975
|
+
OPEN NEW SITE NOW ➔
|
|
976
|
+
</button>
|
|
977
|
+
</div>
|
|
978
|
+
`;
|
|
979
|
+
|
|
980
|
+
setTimeout(() => {
|
|
981
|
+
window.location.replace(latestUrl);
|
|
982
|
+
}, 2000);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Root Owner Version Control Broadcast
|
|
986
|
+
async publishLatestVersionToFirebase() {
|
|
987
|
+
const verInput = document.getElementById('admin-latest-version-input');
|
|
988
|
+
const urlInput = document.getElementById('admin-latest-url-input');
|
|
989
|
+
const version = verInput && verInput.value.trim() ? verInput.value.trim() : window.REAPER_VERSION;
|
|
990
|
+
const url = urlInput && urlInput.value.trim() ? urlInput.value.trim() : window.REAPER_LATEST_URL;
|
|
991
|
+
|
|
992
|
+
if (window.reaperFirebase) {
|
|
993
|
+
await window.reaperFirebase.setLatestVersionInfo({
|
|
994
|
+
version: version,
|
|
995
|
+
url: url,
|
|
996
|
+
forceUpgrade: true,
|
|
997
|
+
updatedAt: Date.now()
|
|
998
|
+
});
|
|
999
|
+
// Also broadcast an emergency alert to all legacy versions (like 1.0.9) that listen to reaperGlobalAlert
|
|
1000
|
+
await window.reaperFirebase.setGlobalAlert(`NOTICE: Please update to the latest Reaper site: ${url}`);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
this.showToast(`Version ${version} broadcasted! All older sites will forward here.`);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
832
1006
|
async sendGlobalAlert() {
|
|
833
1007
|
if (this.currentUser.rank !== 'owner') return;
|
|
834
1008
|
const input = document.getElementById('admin-broadcast-msg');
|
|
@@ -1167,24 +1341,18 @@ class ReaperHubApp {
|
|
|
1167
1341
|
window.reaperFirebase.db.ref("reaperPlaytime").on("value", (snap) => {
|
|
1168
1342
|
const val = snap.val();
|
|
1169
1343
|
if (val) {
|
|
1170
|
-
this.leaderboardData = Object.values(val)
|
|
1344
|
+
this.leaderboardData = Object.values(val).filter(u => {
|
|
1345
|
+
return u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon' && (u.seconds || 0) > 0;
|
|
1346
|
+
});
|
|
1171
1347
|
this.leaderboardData.sort((a, b) => (b.seconds || 0) - (a.seconds || 0));
|
|
1172
1348
|
} else {
|
|
1173
|
-
this.leaderboardData = [
|
|
1174
|
-
{ username: this.currentUser.username, seconds: this.playtimeSeconds, rank: this.currentUser.rank }
|
|
1175
|
-
];
|
|
1349
|
+
this.leaderboardData = [];
|
|
1176
1350
|
}
|
|
1177
1351
|
this.renderLeaderboardUI();
|
|
1178
|
-
this.renderAdminUsersDirectory();
|
|
1179
1352
|
});
|
|
1180
1353
|
} catch (e) {
|
|
1181
1354
|
this.renderLeaderboardUI();
|
|
1182
1355
|
}
|
|
1183
|
-
} else {
|
|
1184
|
-
this.leaderboardData = [
|
|
1185
|
-
{ username: this.currentUser.username, seconds: this.playtimeSeconds, rank: this.currentUser.rank }
|
|
1186
|
-
];
|
|
1187
|
-
this.renderLeaderboardUI();
|
|
1188
1356
|
}
|
|
1189
1357
|
}
|
|
1190
1358
|
|
|
@@ -1200,13 +1368,21 @@ class ReaperHubApp {
|
|
|
1200
1368
|
const container = document.getElementById('leaderboard-rows-container');
|
|
1201
1369
|
if (!container) return;
|
|
1202
1370
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1371
|
+
// Filter strictly to real players with recorded playtime
|
|
1372
|
+
const realPlayers = (this.leaderboardData || []).filter(p => {
|
|
1373
|
+
return p && p.username && p.username !== 'User' && p.username !== 'Member' && p.username !== 'Anon' && (p.seconds || 0) > 0;
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
if (realPlayers.length === 0) {
|
|
1377
|
+
container.innerHTML = `
|
|
1378
|
+
<div style="text-align: center; padding: 2rem; color: #88aacc; font-family: var(--font-mono); font-size: 0.85rem;">
|
|
1379
|
+
Play any game to register your playtime on the global leaderboard!
|
|
1380
|
+
</div>
|
|
1381
|
+
`;
|
|
1382
|
+
return;
|
|
1207
1383
|
}
|
|
1208
1384
|
|
|
1209
|
-
container.innerHTML =
|
|
1385
|
+
container.innerHTML = realPlayers.slice(0, 20).map((player, idx) => {
|
|
1210
1386
|
const rankNum = idx + 1;
|
|
1211
1387
|
const timeStr = this.formatPlaytime(player.seconds || 0);
|
|
1212
1388
|
const isSelf = player.username === this.currentUser.username;
|
package/js/firebase-client.js
CHANGED
|
@@ -742,6 +742,102 @@ class FirebaseService {
|
|
|
742
742
|
} catch (e) {}
|
|
743
743
|
}
|
|
744
744
|
}
|
|
745
|
+
|
|
746
|
+
// Real-time Online Users Presence System (Zero Fake Users)
|
|
747
|
+
registerOnlinePresence(userObj) {
|
|
748
|
+
if (!this.db || !userObj || !userObj.username || userObj.username === 'User' || userObj.username === 'guest') return;
|
|
749
|
+
|
|
750
|
+
try {
|
|
751
|
+
const cleanUsername = userObj.username.replace(/[.#$[\]]/g, '_');
|
|
752
|
+
const userPresenceRef = this.db.ref(`reaperOnlineUsers/${cleanUsername}`);
|
|
753
|
+
const connectedRef = this.db.ref('.info/connected');
|
|
754
|
+
|
|
755
|
+
connectedRef.on('value', (snap) => {
|
|
756
|
+
if (snap.val() === true) {
|
|
757
|
+
userPresenceRef.onDisconnect().remove();
|
|
758
|
+
userPresenceRef.set({
|
|
759
|
+
username: userObj.username,
|
|
760
|
+
rank: userObj.rank || 'member',
|
|
761
|
+
version: userObj.version || window.REAPER_VERSION || '2.3.0',
|
|
762
|
+
playtime: userObj.playtime || 0,
|
|
763
|
+
connectedAt: Date.now(),
|
|
764
|
+
lastSeen: Date.now()
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// Keepalive heartbeat every 20 seconds
|
|
770
|
+
if (this.presenceHeartbeat) clearInterval(this.presenceHeartbeat);
|
|
771
|
+
this.presenceHeartbeat = setInterval(() => {
|
|
772
|
+
userPresenceRef.update({
|
|
773
|
+
lastSeen: Date.now(),
|
|
774
|
+
playtime: userObj.playtime || 0
|
|
775
|
+
}).catch(() => {});
|
|
776
|
+
}, 20000);
|
|
777
|
+
} catch (e) {
|
|
778
|
+
console.warn("Online presence error:", e);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
listenOnlineUsers(callback) {
|
|
783
|
+
if (this.db) {
|
|
784
|
+
try {
|
|
785
|
+
this.db.ref("reaperOnlineUsers").on("value", (snap) => {
|
|
786
|
+
const val = snap.val();
|
|
787
|
+
let list = [];
|
|
788
|
+
if (val) {
|
|
789
|
+
const now = Date.now();
|
|
790
|
+
list = Object.values(val).filter(u => {
|
|
791
|
+
return u && u.username && u.username !== 'User' && u.username !== 'Member' && (now - (u.lastSeen || 0) < 60000);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
callback(list);
|
|
795
|
+
});
|
|
796
|
+
} catch (e) {
|
|
797
|
+
callback([]);
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
callback([]);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Cross-Version Global Upgrade & Redirect Detection
|
|
805
|
+
listenLatestVersion(callback) {
|
|
806
|
+
if (this.db) {
|
|
807
|
+
try {
|
|
808
|
+
this.db.ref("reaperLatestVersionInfo").on("value", (snap) => {
|
|
809
|
+
const val = snap.val();
|
|
810
|
+
if (val) {
|
|
811
|
+
callback(val);
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
} catch (e) {}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async setLatestVersionInfo(versionInfo) {
|
|
819
|
+
if (this.db) {
|
|
820
|
+
try {
|
|
821
|
+
await this.db.ref("reaperLatestVersionInfo").set(versionInfo);
|
|
822
|
+
} catch (e) {}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Auto-clean ghost dummy accounts from Firebase DB
|
|
827
|
+
async purgeGhostUsers() {
|
|
828
|
+
if (!this.db) return;
|
|
829
|
+
try {
|
|
830
|
+
const snap = await this.db.ref("reaperPlaytime").once("value");
|
|
831
|
+
const val = snap.val();
|
|
832
|
+
if (val) {
|
|
833
|
+
for (const k of Object.keys(val)) {
|
|
834
|
+
if (!k || k === 'User' || k === 'Member' || k === 'null' || k === 'undefined' || !val[k].username || val[k].username === 'User') {
|
|
835
|
+
await this.db.ref(`reaperPlaytime/${k}`).remove();
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
} catch (e) {}
|
|
840
|
+
}
|
|
745
841
|
}
|
|
746
842
|
|
|
747
843
|
window.reaperFirebase = new FirebaseService();
|
package/main/index.html
CHANGED
|
@@ -280,6 +280,16 @@
|
|
|
280
280
|
<input type="checkbox" id="mod-slowmode-toggle" style="width: 22px; height: 22px; cursor: pointer; accent-color: #00ffaa;" onchange="app.toggleSlowMode(this.checked)">
|
|
281
281
|
</div>
|
|
282
282
|
|
|
283
|
+
<!-- Live Online Users for Mods -->
|
|
284
|
+
<div class="mod-section">
|
|
285
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
|
286
|
+
<h3 style="font-size: 0.95rem; color: #00e676;">🟢 ACTIVE PLAYERS ONLINE (REALTIME)</h3>
|
|
287
|
+
<button onclick="app.renderModUsersDirectory()" class="btn-stealth" style="font-size: 0.75rem;">REFRESH</button>
|
|
288
|
+
</div>
|
|
289
|
+
<p style="color: #88aacc; font-size: 0.8rem; margin-bottom: 0.65rem;">See connected users, their rank, and their active site version.</p>
|
|
290
|
+
<div id="mod-users-directory" style="max-height: 200px; overflow-y: auto; display: flex; flex-direction: column; gap: 0.4rem;"></div>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
283
293
|
<!-- Timeout User Box -->
|
|
284
294
|
<div class="mod-section">
|
|
285
295
|
<h3 style="font-size: 0.95rem; color: #fff; margin-bottom: 0.35rem;">TIMEOUT / MUTE USER</h3>
|
|
@@ -320,11 +330,15 @@
|
|
|
320
330
|
<!-- Superpower 1: Live Real-Time Analytics Dashboard -->
|
|
321
331
|
<div class="admin-section" style="border-color: #00d2ff; background: rgba(0, 210, 255, 0.05);">
|
|
322
332
|
<h3 style="font-size: 0.95rem; color: #00d2ff; margin-bottom: 0.6rem;">📊 LIVE HUB SYSTEM ANALYTICS</h3>
|
|
323
|
-
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
|
333
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 0.65rem;">
|
|
324
334
|
<div style="background: #040912; border: 1px solid var(--blue-border); padding: 0.65rem; border-radius: 6px; text-align: center;">
|
|
325
335
|
<div style="font-size: 0.7rem; color: #88aacc;">TOTAL GAMES</div>
|
|
326
336
|
<div style="font-size: 1.3rem; font-weight: 800; color: #00ffaa;" id="admin-stat-games">788</div>
|
|
327
337
|
</div>
|
|
338
|
+
<div style="background: #040912; border: 1px solid var(--blue-border); padding: 0.65rem; border-radius: 6px; text-align: center;">
|
|
339
|
+
<div style="font-size: 0.7rem; color: #88aacc;">ONLINE USERS</div>
|
|
340
|
+
<div style="font-size: 1.3rem; font-weight: 800; color: #00e676;" id="admin-stat-online">1</div>
|
|
341
|
+
</div>
|
|
328
342
|
<div style="background: #040912; border: 1px solid var(--blue-border); padding: 0.65rem; border-radius: 6px; text-align: center;">
|
|
329
343
|
<div style="font-size: 0.7rem; color: #88aacc;">TURBO CACHED</div>
|
|
330
344
|
<div style="font-size: 1.3rem; font-weight: 800; color: #00d2ff;" id="admin-stat-cached">0</div>
|
|
@@ -340,6 +354,21 @@
|
|
|
340
354
|
</div>
|
|
341
355
|
</div>
|
|
342
356
|
|
|
357
|
+
<!-- Superpower 6: Cross-Site Version Control & Auto-Forwarder -->
|
|
358
|
+
<div class="admin-section" style="border-color: #00ffaa; background: #02140a;">
|
|
359
|
+
<h3 style="font-size: 0.95rem; color: #00ffaa; margin-bottom: 0.4rem;">🌐 GLOBAL VERSION CONTROLLER & AUTO-REDIRECT</h3>
|
|
360
|
+
<p style="color: #88ffcc; font-size: 0.8rem; margin-bottom: 0.75rem;">
|
|
361
|
+
When you publish a new version, update this once. Any user visiting ANY old version (1.0.9, 2.0.x, etc.) will be notified and instantly brought to the new site!
|
|
362
|
+
</p>
|
|
363
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.65rem;">
|
|
364
|
+
<input type="text" id="admin-latest-version-input" class="red-chat-input" placeholder="Latest Version (e.g. 2.3.0)" value="2.3.0" style="max-width: 140px;">
|
|
365
|
+
<input type="text" id="admin-latest-url-input" class="red-chat-input" placeholder="New UNPKG URL" value="https://unpkg.com/reaper-arcade-hub@2.3.0/main/index.html" style="flex: 1; min-width: 250px;">
|
|
366
|
+
<button class="btn-primary" style="background: #00e676; color: #02050a; font-weight: 900; border-color: #00ff88;" onclick="app.publishLatestVersionToFirebase()">
|
|
367
|
+
BROADCAST VERSION & FORWARD ALL SITES
|
|
368
|
+
</button>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
343
372
|
<!-- Superpower 2: Global Enforce Turbo Mode (Anti-Lag Broadcast) -->
|
|
344
373
|
<div class="admin-section" style="border-color: #00ffaa; background: #03140c;">
|
|
345
374
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
@@ -412,14 +441,14 @@
|
|
|
412
441
|
</div>
|
|
413
442
|
</div>
|
|
414
443
|
|
|
415
|
-
<!-- Connected Players Directory (With
|
|
444
|
+
<!-- Connected Real Players Directory (With Version & Controls) -->
|
|
416
445
|
<div class="admin-section">
|
|
417
446
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;">
|
|
418
|
-
<h3 style="font-size: 0.95rem; color: #
|
|
447
|
+
<h3 style="font-size: 0.95rem; color: #00e676;">🟢 REALTIME ONLINE PLAYERS (ZERO BOTS)</h3>
|
|
419
448
|
<button onclick="app.renderAdminUsersDirectory()" class="btn-stealth" style="font-size: 0.75rem;">REFRESH LIST</button>
|
|
420
449
|
</div>
|
|
421
|
-
<p style="color: #88aacc; font-size: 0.8rem; margin-bottom: 0.65rem;">
|
|
422
|
-
<div id="admin-users-directory" style="max-height:
|
|
450
|
+
<p style="color: #88aacc; font-size: 0.8rem; margin-bottom: 0.65rem;">Active real players currently connected to the hub. View their current site version, rank, and moderation actions.</p>
|
|
451
|
+
<div id="admin-users-directory" style="max-height: 220px; overflow-y: auto; display: flex; flex-direction: column; gap: 0.4rem;"></div>
|
|
423
452
|
</div>
|
|
424
453
|
|
|
425
454
|
<!-- Custom Access Key Generator -->
|
package/main/js/app.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
// REAPER HUB - Core Engine (3
|
|
1
|
+
// REAPER HUB - Core Engine (v2.3.0 Turbo Edition)
|
|
2
|
+
window.REAPER_VERSION = "2.3.0";
|
|
3
|
+
window.REAPER_LATEST_URL = "https://unpkg.com/reaper-arcade-hub@2.3.0/main/index.html";
|
|
2
4
|
|
|
3
5
|
class ReaperHubApp {
|
|
4
6
|
constructor() {
|
|
@@ -28,6 +30,7 @@ class ReaperHubApp {
|
|
|
28
30
|
this.lastActivityTime = Date.now();
|
|
29
31
|
this.isPausedForInactivity = false;
|
|
30
32
|
this.leaderboardData = [];
|
|
33
|
+
this.onlineUsers = [];
|
|
31
34
|
|
|
32
35
|
// Turbo Anti-Lag Engine & Superpower State
|
|
33
36
|
this.turboMode = localStorage.getItem('reaper_turbo_mode') !== 'false'; // Default TRUE for high FPS
|
|
@@ -218,6 +221,25 @@ class ReaperHubApp {
|
|
|
218
221
|
const select = document.getElementById('admin-slowmode-interval');
|
|
219
222
|
if (select) select.value = this.slowModeInterval.toString();
|
|
220
223
|
});
|
|
224
|
+
window.reaperFirebase.purgeGhostUsers();
|
|
225
|
+
window.reaperFirebase.registerOnlinePresence({
|
|
226
|
+
username: this.currentUser.username,
|
|
227
|
+
rank: this.currentUser.rank,
|
|
228
|
+
version: window.REAPER_VERSION,
|
|
229
|
+
playtime: this.playtimeSeconds
|
|
230
|
+
});
|
|
231
|
+
window.reaperFirebase.listenOnlineUsers((users) => {
|
|
232
|
+
this.onlineUsers = users || [];
|
|
233
|
+
this.renderAdminUsersDirectory();
|
|
234
|
+
this.renderModUsersDirectory();
|
|
235
|
+
const onlineCountEl = document.getElementById('admin-stat-online');
|
|
236
|
+
if (onlineCountEl) onlineCountEl.textContent = this.onlineUsers.length;
|
|
237
|
+
});
|
|
238
|
+
window.reaperFirebase.listenLatestVersion((info) => {
|
|
239
|
+
if (info && info.version && info.version !== window.REAPER_VERSION && info.forceUpgrade !== false) {
|
|
240
|
+
this.handleOldVersionDetected(info);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
// 9. Render UI, Games, Leaderboard, Turbo Engine
|
|
@@ -795,40 +817,192 @@ class ReaperHubApp {
|
|
|
795
817
|
const container = document.getElementById('admin-users-directory');
|
|
796
818
|
if (!container) return;
|
|
797
819
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
820
|
+
// Collect REAL active users only (Zero fake bots/users)
|
|
821
|
+
const userMap = new Map();
|
|
822
|
+
if (this.currentUser && this.currentUser.username && this.currentUser.username !== 'User') {
|
|
823
|
+
userMap.set(this.currentUser.username, {
|
|
824
|
+
username: this.currentUser.username,
|
|
825
|
+
rank: this.currentUser.rank || 'member',
|
|
826
|
+
version: window.REAPER_VERSION || '2.3.0',
|
|
827
|
+
playtime: this.playtimeSeconds || 0
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (this.onlineUsers && this.onlineUsers.length > 0) {
|
|
832
|
+
this.onlineUsers.forEach(u => {
|
|
833
|
+
if (u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon') {
|
|
834
|
+
userMap.set(u.username, u);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const realUsers = Array.from(userMap.values());
|
|
840
|
+
|
|
841
|
+
if (realUsers.length <= 1) {
|
|
842
|
+
container.innerHTML = `
|
|
843
|
+
<div style="color: #88aacc; padding: 1.15rem; text-align: center; font-size: 0.82rem; font-family: var(--font-mono); background: #040912; border: 1px dashed var(--blue-border); border-radius: 6px;">
|
|
844
|
+
🟢 Only you are currently online. (0 fake users / 0 ghost bots)
|
|
845
|
+
</div>
|
|
846
|
+
`;
|
|
847
|
+
return;
|
|
802
848
|
}
|
|
803
849
|
|
|
804
|
-
container.innerHTML =
|
|
850
|
+
container.innerHTML = realUsers.map(u => {
|
|
805
851
|
const isSelf = u.username === this.currentUser.username;
|
|
806
852
|
const currentRank = (u.rank || 'member').toLowerCase();
|
|
807
|
-
const timeStr = this.formatPlaytime(u.seconds || 0);
|
|
853
|
+
const timeStr = this.formatPlaytime(u.playtime || u.seconds || 0);
|
|
854
|
+
const userVer = u.version || '2.3.0';
|
|
855
|
+
const isLatest = userVer === window.REAPER_VERSION;
|
|
856
|
+
|
|
857
|
+
const verBadge = isLatest
|
|
858
|
+
? `<span style="color: #00ffaa; font-size: 0.68rem; font-weight: 800; border: 1px solid #00ffaa; padding: 1px 5px; border-radius: 4px;">v${this.escapeHtml(userVer)} (LATEST)</span>`
|
|
859
|
+
: `<span style="color: #ff3377; font-size: 0.68rem; font-weight: 800; border: 1px solid #ff3377; background: rgba(255, 51, 119, 0.15); padding: 1px 5px; border-radius: 4px;">⚠️ OLD VER (v${this.escapeHtml(userVer)})</span>`;
|
|
808
860
|
|
|
809
861
|
return `
|
|
810
|
-
<div style="display: flex; justify-content: space-between; align-items: center; background: #041022; padding: 0.
|
|
811
|
-
<div>
|
|
812
|
-
<
|
|
862
|
+
<div style="display: flex; justify-content: space-between; align-items: center; background: #041022; padding: 0.5rem 0.75rem; border-radius: 6px; border: 1px solid var(--blue-border); font-size: 0.8rem; margin-bottom: 0.35rem;">
|
|
863
|
+
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
|
|
864
|
+
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #00ffaa; box-shadow: 0 0 8px #00ffaa;"></span>
|
|
865
|
+
<strong style="color: #fff;" class="${currentRank === 'owner' ? 'owner-animated-name' : ''}">${this.escapeHtml(u.username)}</strong>
|
|
813
866
|
<span class="rank-pill rank-${currentRank}">${currentRank.toUpperCase()}</span>
|
|
814
|
-
|
|
867
|
+
${verBadge}
|
|
868
|
+
<span style="color: #88ccff; font-size: 0.75rem;">Playtime: ${timeStr}</span>
|
|
815
869
|
</div>
|
|
816
870
|
<div style="display: flex; gap: 0.35rem;">
|
|
817
871
|
${!isSelf ? `
|
|
818
872
|
${currentRank === 'member' ? `
|
|
819
|
-
<button onclick="app.setUserRank('${u.username}', 'chat_mod')" class="btn-stealth" style="color: #00ffaa; padding: 0.2rem 0.5rem; font-size: 0.72rem;">+ PROMOTE MOD</button>
|
|
873
|
+
<button onclick="app.setUserRank('${this.escapeHtml(u.username)}', 'chat_mod')" class="btn-stealth" style="color: #00ffaa; padding: 0.2rem 0.5rem; font-size: 0.72rem;">+ PROMOTE MOD</button>
|
|
820
874
|
` : currentRank === 'chat_mod' || currentRank === 'mod' ? `
|
|
821
|
-
<button onclick="app.setUserRank('${u.username}', 'member')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">- DE-RANK
|
|
875
|
+
<button onclick="app.setUserRank('${this.escapeHtml(u.username)}', 'member')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">- DE-RANK</button>
|
|
822
876
|
` : ''}
|
|
823
|
-
<button onclick="app.kickUser('${u.username}')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">KICK</button>
|
|
824
|
-
<button onclick="app.banUser('${u.username}')" class="btn-stealth" style="color: #ff5555; padding: 0.2rem 0.5rem; font-size: 0.72rem;">BAN</button>
|
|
825
|
-
` : '<span style="color: #ffd700; font-weight: 800;">YOU</span>'}
|
|
877
|
+
<button onclick="app.kickUser('${this.escapeHtml(u.username)}')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.5rem; font-size: 0.72rem;">KICK</button>
|
|
878
|
+
<button onclick="app.banUser('${this.escapeHtml(u.username)}')" class="btn-stealth" style="color: #ff5555; padding: 0.2rem 0.5rem; font-size: 0.72rem;">BAN</button>
|
|
879
|
+
` : '<span style="color: #ffd700; font-weight: 800; font-size: 0.75rem;">YOU</span>'}
|
|
880
|
+
</div>
|
|
881
|
+
</div>
|
|
882
|
+
`;
|
|
883
|
+
}).join('');
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
renderModUsersDirectory() {
|
|
887
|
+
const container = document.getElementById('mod-users-directory');
|
|
888
|
+
if (!container) return;
|
|
889
|
+
|
|
890
|
+
const userMap = new Map();
|
|
891
|
+
if (this.currentUser && this.currentUser.username && this.currentUser.username !== 'User') {
|
|
892
|
+
userMap.set(this.currentUser.username, {
|
|
893
|
+
username: this.currentUser.username,
|
|
894
|
+
rank: this.currentUser.rank || 'member',
|
|
895
|
+
version: window.REAPER_VERSION || '2.3.0'
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (this.onlineUsers && this.onlineUsers.length > 0) {
|
|
900
|
+
this.onlineUsers.forEach(u => {
|
|
901
|
+
if (u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon') {
|
|
902
|
+
userMap.set(u.username, u);
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const realUsers = Array.from(userMap.values());
|
|
908
|
+
|
|
909
|
+
if (realUsers.length <= 1) {
|
|
910
|
+
container.innerHTML = `
|
|
911
|
+
<div style="color: #88aacc; padding: 0.85rem; text-align: center; font-size: 0.8rem; font-family: var(--font-mono); background: #040912; border: 1px dashed var(--blue-border); border-radius: 6px;">
|
|
912
|
+
🟢 Only you are currently online. (0 fake users)
|
|
913
|
+
</div>
|
|
914
|
+
`;
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
container.innerHTML = realUsers.map(u => {
|
|
919
|
+
const isSelf = u.username === this.currentUser.username;
|
|
920
|
+
const currentRank = (u.rank || 'member').toLowerCase();
|
|
921
|
+
const userVer = u.version || '2.3.0';
|
|
922
|
+
const isLatest = userVer === window.REAPER_VERSION;
|
|
923
|
+
|
|
924
|
+
const verBadge = isLatest
|
|
925
|
+
? `<span style="color: #00ffaa; font-size: 0.65rem; font-weight: 800; border: 1px solid #00ffaa; padding: 1px 4px; border-radius: 4px;">v${this.escapeHtml(userVer)} (LATEST)</span>`
|
|
926
|
+
: `<span style="color: #ff3377; font-size: 0.65rem; font-weight: 800; border: 1px solid #ff3377; background: rgba(255, 51, 119, 0.15); padding: 1px 4px; border-radius: 4px;">⚠️ OLD VER (v${this.escapeHtml(userVer)})</span>`;
|
|
927
|
+
|
|
928
|
+
return `
|
|
929
|
+
<div style="display: flex; justify-content: space-between; align-items: center; background: #041022; padding: 0.4rem 0.65rem; border-radius: 6px; border: 1px solid var(--blue-border); font-size: 0.78rem; margin-bottom: 0.3rem;">
|
|
930
|
+
<div style="display: flex; align-items: center; gap: 0.45rem;">
|
|
931
|
+
<span style="display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #00ffaa;"></span>
|
|
932
|
+
<strong style="color: #fff;">${this.escapeHtml(u.username)}</strong>
|
|
933
|
+
<span class="rank-pill rank-${currentRank}">${currentRank.toUpperCase()}</span>
|
|
934
|
+
${verBadge}
|
|
935
|
+
</div>
|
|
936
|
+
<div>
|
|
937
|
+
${!isSelf ? `
|
|
938
|
+
<button onclick="app.modApplyTimeoutToUser('${this.escapeHtml(u.username)}')" class="btn-stealth" style="color: #ffaa00; padding: 0.2rem 0.4rem; font-size: 0.7rem;">MUTE</button>
|
|
939
|
+
` : '<span style="color: #00ffaa; font-size: 0.7rem;">YOU</span>'}
|
|
826
940
|
</div>
|
|
827
941
|
</div>
|
|
828
942
|
`;
|
|
829
943
|
}).join('');
|
|
830
944
|
}
|
|
831
945
|
|
|
946
|
+
// Cross-Site Old Version Interceptor & Forwarder
|
|
947
|
+
handleOldVersionDetected(info) {
|
|
948
|
+
if (!info || !info.version || info.version === window.REAPER_VERSION) return;
|
|
949
|
+
if (info.forceUpgrade === false) return;
|
|
950
|
+
|
|
951
|
+
const currentVer = window.REAPER_VERSION;
|
|
952
|
+
const latestVer = info.version;
|
|
953
|
+
const latestUrl = info.url || window.REAPER_LATEST_URL;
|
|
954
|
+
|
|
955
|
+
let modal = document.getElementById('version-upgrade-modal');
|
|
956
|
+
if (!modal) {
|
|
957
|
+
modal = document.createElement('div');
|
|
958
|
+
modal.id = 'version-upgrade-modal';
|
|
959
|
+
modal.style.cssText = 'position: fixed; inset: 0; background: rgba(2, 5, 10, 0.97); z-index: 9999999999; display: flex; align-items: center; justify-content: center; padding: 1.5rem; text-align: center; font-family: var(--font-heading); backdrop-filter: blur(12px);';
|
|
960
|
+
document.body.appendChild(modal);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
modal.innerHTML = `
|
|
964
|
+
<div style="max-width: 500px; background: #040e1c; border: 2px solid #00d2ff; border-radius: 12px; padding: 2.5rem 2rem; box-shadow: 0 0 50px rgba(0, 210, 255, 0.4);">
|
|
965
|
+
<div style="font-size: 3rem; margin-bottom: 0.5rem;">⚡</div>
|
|
966
|
+
<div style="font-size: 1.4rem; font-weight: 900; color: #00d2ff; letter-spacing: 0.08em; margin-bottom: 0.5rem;">NEW VERSION ACTIVE</div>
|
|
967
|
+
<p style="color: #adc4e2; font-size: 0.9rem; line-height: 1.6; margin-bottom: 1.25rem;">
|
|
968
|
+
You are currently visiting an older version of Reaper Hub (<strong style="color: #ff5555;">v${currentVer}</strong>).<br>
|
|
969
|
+
The new upgraded site is live at <strong style="color: #00ffaa;">v${latestVer}</strong> with zero lag and fixes.
|
|
970
|
+
</p>
|
|
971
|
+
<div style="color: #00ffaa; font-size: 0.85rem; font-weight: 800; margin-bottom: 1.5rem; font-family: var(--font-mono);">
|
|
972
|
+
🚀 BRINGING YOU TO THE NEW SITE IN 2 SECONDS...
|
|
973
|
+
</div>
|
|
974
|
+
<button class="btn-primary" style="width: 100%; justify-content: center; padding: 0.85rem; font-size: 0.9rem; background: #00d2ff; color: #000; font-weight: 900;" onclick="window.location.replace('${latestUrl}')">
|
|
975
|
+
OPEN NEW SITE NOW ➔
|
|
976
|
+
</button>
|
|
977
|
+
</div>
|
|
978
|
+
`;
|
|
979
|
+
|
|
980
|
+
setTimeout(() => {
|
|
981
|
+
window.location.replace(latestUrl);
|
|
982
|
+
}, 2000);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Root Owner Version Control Broadcast
|
|
986
|
+
async publishLatestVersionToFirebase() {
|
|
987
|
+
const verInput = document.getElementById('admin-latest-version-input');
|
|
988
|
+
const urlInput = document.getElementById('admin-latest-url-input');
|
|
989
|
+
const version = verInput && verInput.value.trim() ? verInput.value.trim() : window.REAPER_VERSION;
|
|
990
|
+
const url = urlInput && urlInput.value.trim() ? urlInput.value.trim() : window.REAPER_LATEST_URL;
|
|
991
|
+
|
|
992
|
+
if (window.reaperFirebase) {
|
|
993
|
+
await window.reaperFirebase.setLatestVersionInfo({
|
|
994
|
+
version: version,
|
|
995
|
+
url: url,
|
|
996
|
+
forceUpgrade: true,
|
|
997
|
+
updatedAt: Date.now()
|
|
998
|
+
});
|
|
999
|
+
// Also broadcast an emergency alert to all legacy versions (like 1.0.9) that listen to reaperGlobalAlert
|
|
1000
|
+
await window.reaperFirebase.setGlobalAlert(`NOTICE: Please update to the latest Reaper site: ${url}`);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
this.showToast(`Version ${version} broadcasted! All older sites will forward here.`);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
832
1006
|
async sendGlobalAlert() {
|
|
833
1007
|
if (this.currentUser.rank !== 'owner') return;
|
|
834
1008
|
const input = document.getElementById('admin-broadcast-msg');
|
|
@@ -1167,24 +1341,18 @@ class ReaperHubApp {
|
|
|
1167
1341
|
window.reaperFirebase.db.ref("reaperPlaytime").on("value", (snap) => {
|
|
1168
1342
|
const val = snap.val();
|
|
1169
1343
|
if (val) {
|
|
1170
|
-
this.leaderboardData = Object.values(val)
|
|
1344
|
+
this.leaderboardData = Object.values(val).filter(u => {
|
|
1345
|
+
return u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon' && (u.seconds || 0) > 0;
|
|
1346
|
+
});
|
|
1171
1347
|
this.leaderboardData.sort((a, b) => (b.seconds || 0) - (a.seconds || 0));
|
|
1172
1348
|
} else {
|
|
1173
|
-
this.leaderboardData = [
|
|
1174
|
-
{ username: this.currentUser.username, seconds: this.playtimeSeconds, rank: this.currentUser.rank }
|
|
1175
|
-
];
|
|
1349
|
+
this.leaderboardData = [];
|
|
1176
1350
|
}
|
|
1177
1351
|
this.renderLeaderboardUI();
|
|
1178
|
-
this.renderAdminUsersDirectory();
|
|
1179
1352
|
});
|
|
1180
1353
|
} catch (e) {
|
|
1181
1354
|
this.renderLeaderboardUI();
|
|
1182
1355
|
}
|
|
1183
|
-
} else {
|
|
1184
|
-
this.leaderboardData = [
|
|
1185
|
-
{ username: this.currentUser.username, seconds: this.playtimeSeconds, rank: this.currentUser.rank }
|
|
1186
|
-
];
|
|
1187
|
-
this.renderLeaderboardUI();
|
|
1188
1356
|
}
|
|
1189
1357
|
}
|
|
1190
1358
|
|
|
@@ -1200,13 +1368,21 @@ class ReaperHubApp {
|
|
|
1200
1368
|
const container = document.getElementById('leaderboard-rows-container');
|
|
1201
1369
|
if (!container) return;
|
|
1202
1370
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1371
|
+
// Filter strictly to real players with recorded playtime
|
|
1372
|
+
const realPlayers = (this.leaderboardData || []).filter(p => {
|
|
1373
|
+
return p && p.username && p.username !== 'User' && p.username !== 'Member' && p.username !== 'Anon' && (p.seconds || 0) > 0;
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
if (realPlayers.length === 0) {
|
|
1377
|
+
container.innerHTML = `
|
|
1378
|
+
<div style="text-align: center; padding: 2rem; color: #88aacc; font-family: var(--font-mono); font-size: 0.85rem;">
|
|
1379
|
+
Play any game to register your playtime on the global leaderboard!
|
|
1380
|
+
</div>
|
|
1381
|
+
`;
|
|
1382
|
+
return;
|
|
1207
1383
|
}
|
|
1208
1384
|
|
|
1209
|
-
container.innerHTML =
|
|
1385
|
+
container.innerHTML = realPlayers.slice(0, 20).map((player, idx) => {
|
|
1210
1386
|
const rankNum = idx + 1;
|
|
1211
1387
|
const timeStr = this.formatPlaytime(player.seconds || 0);
|
|
1212
1388
|
const isSelf = player.username === this.currentUser.username;
|
|
@@ -742,6 +742,102 @@ class FirebaseService {
|
|
|
742
742
|
} catch (e) {}
|
|
743
743
|
}
|
|
744
744
|
}
|
|
745
|
+
|
|
746
|
+
// Real-time Online Users Presence System (Zero Fake Users)
|
|
747
|
+
registerOnlinePresence(userObj) {
|
|
748
|
+
if (!this.db || !userObj || !userObj.username || userObj.username === 'User' || userObj.username === 'guest') return;
|
|
749
|
+
|
|
750
|
+
try {
|
|
751
|
+
const cleanUsername = userObj.username.replace(/[.#$[\]]/g, '_');
|
|
752
|
+
const userPresenceRef = this.db.ref(`reaperOnlineUsers/${cleanUsername}`);
|
|
753
|
+
const connectedRef = this.db.ref('.info/connected');
|
|
754
|
+
|
|
755
|
+
connectedRef.on('value', (snap) => {
|
|
756
|
+
if (snap.val() === true) {
|
|
757
|
+
userPresenceRef.onDisconnect().remove();
|
|
758
|
+
userPresenceRef.set({
|
|
759
|
+
username: userObj.username,
|
|
760
|
+
rank: userObj.rank || 'member',
|
|
761
|
+
version: userObj.version || window.REAPER_VERSION || '2.3.0',
|
|
762
|
+
playtime: userObj.playtime || 0,
|
|
763
|
+
connectedAt: Date.now(),
|
|
764
|
+
lastSeen: Date.now()
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// Keepalive heartbeat every 20 seconds
|
|
770
|
+
if (this.presenceHeartbeat) clearInterval(this.presenceHeartbeat);
|
|
771
|
+
this.presenceHeartbeat = setInterval(() => {
|
|
772
|
+
userPresenceRef.update({
|
|
773
|
+
lastSeen: Date.now(),
|
|
774
|
+
playtime: userObj.playtime || 0
|
|
775
|
+
}).catch(() => {});
|
|
776
|
+
}, 20000);
|
|
777
|
+
} catch (e) {
|
|
778
|
+
console.warn("Online presence error:", e);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
listenOnlineUsers(callback) {
|
|
783
|
+
if (this.db) {
|
|
784
|
+
try {
|
|
785
|
+
this.db.ref("reaperOnlineUsers").on("value", (snap) => {
|
|
786
|
+
const val = snap.val();
|
|
787
|
+
let list = [];
|
|
788
|
+
if (val) {
|
|
789
|
+
const now = Date.now();
|
|
790
|
+
list = Object.values(val).filter(u => {
|
|
791
|
+
return u && u.username && u.username !== 'User' && u.username !== 'Member' && (now - (u.lastSeen || 0) < 60000);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
callback(list);
|
|
795
|
+
});
|
|
796
|
+
} catch (e) {
|
|
797
|
+
callback([]);
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
callback([]);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Cross-Version Global Upgrade & Redirect Detection
|
|
805
|
+
listenLatestVersion(callback) {
|
|
806
|
+
if (this.db) {
|
|
807
|
+
try {
|
|
808
|
+
this.db.ref("reaperLatestVersionInfo").on("value", (snap) => {
|
|
809
|
+
const val = snap.val();
|
|
810
|
+
if (val) {
|
|
811
|
+
callback(val);
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
} catch (e) {}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async setLatestVersionInfo(versionInfo) {
|
|
819
|
+
if (this.db) {
|
|
820
|
+
try {
|
|
821
|
+
await this.db.ref("reaperLatestVersionInfo").set(versionInfo);
|
|
822
|
+
} catch (e) {}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Auto-clean ghost dummy accounts from Firebase DB
|
|
827
|
+
async purgeGhostUsers() {
|
|
828
|
+
if (!this.db) return;
|
|
829
|
+
try {
|
|
830
|
+
const snap = await this.db.ref("reaperPlaytime").once("value");
|
|
831
|
+
const val = snap.val();
|
|
832
|
+
if (val) {
|
|
833
|
+
for (const k of Object.keys(val)) {
|
|
834
|
+
if (!k || k === 'User' || k === 'Member' || k === 'null' || k === 'undefined' || !val[k].username || val[k].username === 'User') {
|
|
835
|
+
await this.db.ref(`reaperPlaytime/${k}`).remove();
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
} catch (e) {}
|
|
840
|
+
}
|
|
745
841
|
}
|
|
746
842
|
|
|
747
843
|
window.reaperFirebase = new FirebaseService();
|
package/package.json
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reaper-arcade-hub",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Private Core Hub",
|
|
5
5
|
"main": "main/index.html",
|
|
6
6
|
"unpkg": "main/index.html",
|
|
7
7
|
"jsdelivr": "main/index.html",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"test": "node js/security-shield.js"
|
|
10
10
|
},
|
|
11
|
-
"keywords": [
|
|
12
|
-
"reaper",
|
|
13
|
-
"games",
|
|
14
|
-
"html5",
|
|
15
|
-
"unblocked",
|
|
16
|
-
"arcade",
|
|
17
|
-
"turbo",
|
|
18
|
-
"antilag",
|
|
19
|
-
"leaderboard",
|
|
20
|
-
"keysystem"
|
|
21
|
-
],
|
|
11
|
+
"keywords": [],
|
|
22
12
|
"author": "asfsaf",
|
|
23
13
|
"license": "MIT",
|
|
24
14
|
"publishConfig": {
|
|
@@ -742,6 +742,102 @@ class FirebaseService {
|
|
|
742
742
|
} catch (e) {}
|
|
743
743
|
}
|
|
744
744
|
}
|
|
745
|
+
|
|
746
|
+
// Real-time Online Users Presence System (Zero Fake Users)
|
|
747
|
+
registerOnlinePresence(userObj) {
|
|
748
|
+
if (!this.db || !userObj || !userObj.username || userObj.username === 'User' || userObj.username === 'guest') return;
|
|
749
|
+
|
|
750
|
+
try {
|
|
751
|
+
const cleanUsername = userObj.username.replace(/[.#$[\]]/g, '_');
|
|
752
|
+
const userPresenceRef = this.db.ref(`reaperOnlineUsers/${cleanUsername}`);
|
|
753
|
+
const connectedRef = this.db.ref('.info/connected');
|
|
754
|
+
|
|
755
|
+
connectedRef.on('value', (snap) => {
|
|
756
|
+
if (snap.val() === true) {
|
|
757
|
+
userPresenceRef.onDisconnect().remove();
|
|
758
|
+
userPresenceRef.set({
|
|
759
|
+
username: userObj.username,
|
|
760
|
+
rank: userObj.rank || 'member',
|
|
761
|
+
version: userObj.version || window.REAPER_VERSION || '2.3.0',
|
|
762
|
+
playtime: userObj.playtime || 0,
|
|
763
|
+
connectedAt: Date.now(),
|
|
764
|
+
lastSeen: Date.now()
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// Keepalive heartbeat every 20 seconds
|
|
770
|
+
if (this.presenceHeartbeat) clearInterval(this.presenceHeartbeat);
|
|
771
|
+
this.presenceHeartbeat = setInterval(() => {
|
|
772
|
+
userPresenceRef.update({
|
|
773
|
+
lastSeen: Date.now(),
|
|
774
|
+
playtime: userObj.playtime || 0
|
|
775
|
+
}).catch(() => {});
|
|
776
|
+
}, 20000);
|
|
777
|
+
} catch (e) {
|
|
778
|
+
console.warn("Online presence error:", e);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
listenOnlineUsers(callback) {
|
|
783
|
+
if (this.db) {
|
|
784
|
+
try {
|
|
785
|
+
this.db.ref("reaperOnlineUsers").on("value", (snap) => {
|
|
786
|
+
const val = snap.val();
|
|
787
|
+
let list = [];
|
|
788
|
+
if (val) {
|
|
789
|
+
const now = Date.now();
|
|
790
|
+
list = Object.values(val).filter(u => {
|
|
791
|
+
return u && u.username && u.username !== 'User' && u.username !== 'Member' && (now - (u.lastSeen || 0) < 60000);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
callback(list);
|
|
795
|
+
});
|
|
796
|
+
} catch (e) {
|
|
797
|
+
callback([]);
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
callback([]);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Cross-Version Global Upgrade & Redirect Detection
|
|
805
|
+
listenLatestVersion(callback) {
|
|
806
|
+
if (this.db) {
|
|
807
|
+
try {
|
|
808
|
+
this.db.ref("reaperLatestVersionInfo").on("value", (snap) => {
|
|
809
|
+
const val = snap.val();
|
|
810
|
+
if (val) {
|
|
811
|
+
callback(val);
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
} catch (e) {}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async setLatestVersionInfo(versionInfo) {
|
|
819
|
+
if (this.db) {
|
|
820
|
+
try {
|
|
821
|
+
await this.db.ref("reaperLatestVersionInfo").set(versionInfo);
|
|
822
|
+
} catch (e) {}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Auto-clean ghost dummy accounts from Firebase DB
|
|
827
|
+
async purgeGhostUsers() {
|
|
828
|
+
if (!this.db) return;
|
|
829
|
+
try {
|
|
830
|
+
const snap = await this.db.ref("reaperPlaytime").once("value");
|
|
831
|
+
const val = snap.val();
|
|
832
|
+
if (val) {
|
|
833
|
+
for (const k of Object.keys(val)) {
|
|
834
|
+
if (!k || k === 'User' || k === 'Member' || k === 'null' || k === 'undefined' || !val[k].username || val[k].username === 'User') {
|
|
835
|
+
await this.db.ref(`reaperPlaytime/${k}`).remove();
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
} catch (e) {}
|
|
840
|
+
}
|
|
745
841
|
}
|
|
746
842
|
|
|
747
843
|
window.reaperFirebase = new FirebaseService();
|