reaper-arcade-hub 2.2.0 → 2.4.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/css/styles.css +209 -0
- package/index.html +77 -40
- package/js/app.js +238 -43
- package/js/firebase-client.js +96 -0
- package/main/css/styles.css +209 -0
- package/main/index.html +77 -40
- package/main/js/app.js +238 -43
- package/main/js/firebase-client.js +96 -0
- package/package.json +3 -13
- package/verify/css/styles.css +209 -0
- package/verify/js/firebase-client.js +96 -0
package/js/app.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
// REAPER HUB - Core Engine (
|
|
1
|
+
// REAPER HUB - Core Engine (v2.4.0 Next-Gen UI & Instant Auto-Redirect)
|
|
2
|
+
window.REAPER_VERSION = "2.4.0";
|
|
3
|
+
window.REAPER_LATEST_URL = "https://unpkg.com/reaper-arcade-hub@2.4.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
|
|
@@ -508,6 +530,21 @@ class ReaperHubApp {
|
|
|
508
530
|
alert("[ACCESS DENIED]\nInvalid, exhausted, or expired Key/Token. Please obtain a valid key from the Owner.");
|
|
509
531
|
}
|
|
510
532
|
|
|
533
|
+
async pasteKeyFromClipboard() {
|
|
534
|
+
const input = document.getElementById('gate-token-input');
|
|
535
|
+
if (!input) return;
|
|
536
|
+
try {
|
|
537
|
+
const text = await navigator.clipboard.readText();
|
|
538
|
+
if (text) {
|
|
539
|
+
input.value = text.trim();
|
|
540
|
+
this.submitGateToken();
|
|
541
|
+
}
|
|
542
|
+
} catch (e) {
|
|
543
|
+
input.focus();
|
|
544
|
+
this.showToast("Paste manually or press Enter.");
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
511
548
|
// ==========================================
|
|
512
549
|
// CUSTOM KEY CREATION WITH MAX USES & DELETION
|
|
513
550
|
// ==========================================
|
|
@@ -795,40 +832,189 @@ class ReaperHubApp {
|
|
|
795
832
|
const container = document.getElementById('admin-users-directory');
|
|
796
833
|
if (!container) return;
|
|
797
834
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
835
|
+
// Collect REAL active users only (Zero fake bots/users)
|
|
836
|
+
const userMap = new Map();
|
|
837
|
+
if (this.currentUser && this.currentUser.username && this.currentUser.username !== 'User') {
|
|
838
|
+
userMap.set(this.currentUser.username, {
|
|
839
|
+
username: this.currentUser.username,
|
|
840
|
+
rank: this.currentUser.rank || 'member',
|
|
841
|
+
version: window.REAPER_VERSION || '2.3.0',
|
|
842
|
+
playtime: this.playtimeSeconds || 0
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (this.onlineUsers && this.onlineUsers.length > 0) {
|
|
847
|
+
this.onlineUsers.forEach(u => {
|
|
848
|
+
if (u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon') {
|
|
849
|
+
userMap.set(u.username, u);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const realUsers = Array.from(userMap.values());
|
|
855
|
+
|
|
856
|
+
if (realUsers.length <= 1) {
|
|
857
|
+
container.innerHTML = `
|
|
858
|
+
<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;">
|
|
859
|
+
🟢 Only you are currently online. (0 fake users / 0 ghost bots)
|
|
860
|
+
</div>
|
|
861
|
+
`;
|
|
862
|
+
return;
|
|
802
863
|
}
|
|
803
864
|
|
|
804
|
-
container.innerHTML =
|
|
865
|
+
container.innerHTML = realUsers.map(u => {
|
|
805
866
|
const isSelf = u.username === this.currentUser.username;
|
|
806
867
|
const currentRank = (u.rank || 'member').toLowerCase();
|
|
807
|
-
const timeStr = this.formatPlaytime(u.seconds || 0);
|
|
868
|
+
const timeStr = this.formatPlaytime(u.playtime || u.seconds || 0);
|
|
869
|
+
const userVer = u.version || '2.3.0';
|
|
870
|
+
const isLatest = userVer === window.REAPER_VERSION;
|
|
871
|
+
|
|
872
|
+
const verBadge = isLatest
|
|
873
|
+
? `<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>`
|
|
874
|
+
: `<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
875
|
|
|
809
876
|
return `
|
|
810
|
-
<div style="display: flex; justify-content: space-between; align-items: center; background: #041022; padding: 0.
|
|
811
|
-
<div>
|
|
812
|
-
<
|
|
877
|
+
<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;">
|
|
878
|
+
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;">
|
|
879
|
+
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #00ffaa; box-shadow: 0 0 8px #00ffaa;"></span>
|
|
880
|
+
<strong style="color: #fff;" class="${currentRank === 'owner' ? 'owner-animated-name' : ''}">${this.escapeHtml(u.username)}</strong>
|
|
813
881
|
<span class="rank-pill rank-${currentRank}">${currentRank.toUpperCase()}</span>
|
|
814
|
-
|
|
882
|
+
${verBadge}
|
|
883
|
+
<span style="color: #88ccff; font-size: 0.75rem;">Playtime: ${timeStr}</span>
|
|
815
884
|
</div>
|
|
816
885
|
<div style="display: flex; gap: 0.35rem;">
|
|
817
886
|
${!isSelf ? `
|
|
818
887
|
${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>
|
|
888
|
+
<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
889
|
` : 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
|
|
890
|
+
<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
891
|
` : ''}
|
|
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>'}
|
|
892
|
+
<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>
|
|
893
|
+
<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>
|
|
894
|
+
` : '<span style="color: #ffd700; font-weight: 800; font-size: 0.75rem;">YOU</span>'}
|
|
826
895
|
</div>
|
|
827
896
|
</div>
|
|
828
897
|
`;
|
|
829
898
|
}).join('');
|
|
830
899
|
}
|
|
831
900
|
|
|
901
|
+
renderModUsersDirectory() {
|
|
902
|
+
const container = document.getElementById('mod-users-directory');
|
|
903
|
+
if (!container) return;
|
|
904
|
+
|
|
905
|
+
const userMap = new Map();
|
|
906
|
+
if (this.currentUser && this.currentUser.username && this.currentUser.username !== 'User') {
|
|
907
|
+
userMap.set(this.currentUser.username, {
|
|
908
|
+
username: this.currentUser.username,
|
|
909
|
+
rank: this.currentUser.rank || 'member',
|
|
910
|
+
version: window.REAPER_VERSION || '2.3.0'
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (this.onlineUsers && this.onlineUsers.length > 0) {
|
|
915
|
+
this.onlineUsers.forEach(u => {
|
|
916
|
+
if (u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon') {
|
|
917
|
+
userMap.set(u.username, u);
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const realUsers = Array.from(userMap.values());
|
|
923
|
+
|
|
924
|
+
if (realUsers.length <= 1) {
|
|
925
|
+
container.innerHTML = `
|
|
926
|
+
<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;">
|
|
927
|
+
🟢 Only you are currently online. (0 fake users)
|
|
928
|
+
</div>
|
|
929
|
+
`;
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
container.innerHTML = realUsers.map(u => {
|
|
934
|
+
const isSelf = u.username === this.currentUser.username;
|
|
935
|
+
const currentRank = (u.rank || 'member').toLowerCase();
|
|
936
|
+
const userVer = u.version || '2.3.0';
|
|
937
|
+
const isLatest = userVer === window.REAPER_VERSION;
|
|
938
|
+
|
|
939
|
+
const verBadge = isLatest
|
|
940
|
+
? `<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>`
|
|
941
|
+
: `<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>`;
|
|
942
|
+
|
|
943
|
+
return `
|
|
944
|
+
<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;">
|
|
945
|
+
<div style="display: flex; align-items: center; gap: 0.45rem;">
|
|
946
|
+
<span style="display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #00ffaa;"></span>
|
|
947
|
+
<strong style="color: #fff;">${this.escapeHtml(u.username)}</strong>
|
|
948
|
+
<span class="rank-pill rank-${currentRank}">${currentRank.toUpperCase()}</span>
|
|
949
|
+
${verBadge}
|
|
950
|
+
</div>
|
|
951
|
+
<div>
|
|
952
|
+
${!isSelf ? `
|
|
953
|
+
<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>
|
|
954
|
+
` : '<span style="color: #00ffaa; font-size: 0.7rem;">YOU</span>'}
|
|
955
|
+
</div>
|
|
956
|
+
</div>
|
|
957
|
+
`;
|
|
958
|
+
}).join('');
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Cross-Site Old Version Interceptor & Forwarder
|
|
962
|
+
handleOldVersionDetected(info) {
|
|
963
|
+
if (!info || !info.version || info.version === window.REAPER_VERSION) return;
|
|
964
|
+
if (info.forceUpgrade === false) return;
|
|
965
|
+
|
|
966
|
+
const currentVer = window.REAPER_VERSION;
|
|
967
|
+
const latestVer = info.version;
|
|
968
|
+
const latestUrl = info.url || window.REAPER_LATEST_URL;
|
|
969
|
+
|
|
970
|
+
let modal = document.getElementById('version-upgrade-modal');
|
|
971
|
+
if (!modal) {
|
|
972
|
+
modal = document.createElement('div');
|
|
973
|
+
modal.id = 'version-upgrade-modal';
|
|
974
|
+
modal.style.cssText = 'position: fixed; inset: 0; background: rgba(2, 5, 10, 0.98); 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(20px);';
|
|
975
|
+
document.body.appendChild(modal);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
modal.innerHTML = `
|
|
979
|
+
<div style="max-width: 480px; background: #040e1c; border: 2px solid #00ffaa; border-radius: 16px; padding: 2.5rem 2rem; box-shadow: 0 0 60px rgba(0, 255, 170, 0.5);">
|
|
980
|
+
<div style="font-size: 3.5rem; margin-bottom: 0.5rem;">⚡</div>
|
|
981
|
+
<div style="font-size: 1.5rem; font-weight: 900; color: #00ffaa; letter-spacing: 0.08em; margin-bottom: 0.5rem;">UPGRADING TO NEW VERSION</div>
|
|
982
|
+
<p style="color: #adc4e2; font-size: 0.9rem; line-height: 1.6; margin-bottom: 1.25rem;">
|
|
983
|
+
You were on an older site build (<strong style="color: #ff5555;">v${currentVer}</strong>).<br>
|
|
984
|
+
Bringing you to the new high-speed version <strong style="color: #00ffaa;">v${latestVer}</strong> right now...
|
|
985
|
+
</p>
|
|
986
|
+
<div style="color: #00d2ff; font-size: 0.88rem; font-weight: 800; font-family: var(--font-mono);">
|
|
987
|
+
🚀 REDIRECTING NOW...
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
`;
|
|
991
|
+
|
|
992
|
+
setTimeout(() => {
|
|
993
|
+
window.location.replace(latestUrl);
|
|
994
|
+
}, 400);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Root Owner Version Control Broadcast
|
|
998
|
+
async publishLatestVersionToFirebase() {
|
|
999
|
+
const verInput = document.getElementById('admin-latest-version-input');
|
|
1000
|
+
const urlInput = document.getElementById('admin-latest-url-input');
|
|
1001
|
+
const version = verInput && verInput.value.trim() ? verInput.value.trim() : window.REAPER_VERSION;
|
|
1002
|
+
const url = urlInput && urlInput.value.trim() ? urlInput.value.trim() : window.REAPER_LATEST_URL;
|
|
1003
|
+
|
|
1004
|
+
if (window.reaperFirebase) {
|
|
1005
|
+
await window.reaperFirebase.setLatestVersionInfo({
|
|
1006
|
+
version: version,
|
|
1007
|
+
url: url,
|
|
1008
|
+
forceUpgrade: true,
|
|
1009
|
+
updatedAt: Date.now()
|
|
1010
|
+
});
|
|
1011
|
+
// Also broadcast an emergency alert to all legacy versions (like 1.0.9) that listen to reaperGlobalAlert
|
|
1012
|
+
await window.reaperFirebase.setGlobalAlert(`NOTICE: Please update to the latest Reaper site: ${url}`);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
this.showToast(`Version ${version} broadcasted! All older sites will forward here.`);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
832
1018
|
async sendGlobalAlert() {
|
|
833
1019
|
if (this.currentUser.rank !== 'owner') return;
|
|
834
1020
|
const input = document.getElementById('admin-broadcast-msg');
|
|
@@ -1053,31 +1239,38 @@ class ReaperHubApp {
|
|
|
1053
1239
|
}
|
|
1054
1240
|
}, 1100);
|
|
1055
1241
|
|
|
1242
|
+
const terminalText = document.getElementById('splash-terminal-text');
|
|
1243
|
+
|
|
1056
1244
|
let progress = 0;
|
|
1057
|
-
const interval =
|
|
1245
|
+
const interval = 18; // Super snappy 1.8-second cyber boot sequence
|
|
1058
1246
|
|
|
1059
1247
|
const timer = setInterval(() => {
|
|
1060
1248
|
progress += 1;
|
|
1061
1249
|
bar.style.width = `${progress}%`;
|
|
1062
1250
|
|
|
1063
|
-
if (progress <
|
|
1064
|
-
if (status) status.textContent = `[${progress}%] INITIALIZING REAPER
|
|
1065
|
-
|
|
1251
|
+
if (progress < 25) {
|
|
1252
|
+
if (status) status.textContent = `[${progress}%] INITIALIZING REAPER KERNEL...`;
|
|
1253
|
+
if (terminalText) terminalText.textContent = "[KERNEL] WebGL 2.0 & Canvas Accelerator Active";
|
|
1254
|
+
} else if (progress < 50) {
|
|
1066
1255
|
if (status) status.textContent = `[${progress}%] CONNECTING REALTIME FIREBASE DB...`;
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
if (
|
|
1071
|
-
} else if (progress <
|
|
1072
|
-
if (status) status.textContent = `[${progress}%]
|
|
1256
|
+
if (terminalText) terminalText.textContent = "[DATABASE] Firebase Realtime Sync Established";
|
|
1257
|
+
} else if (progress < 75) {
|
|
1258
|
+
if (status) status.textContent = `[${progress}%] UNBLOCKING GITHUB PAGES CDN...`;
|
|
1259
|
+
if (terminalText) terminalText.textContent = "[CDN] High-Speed GitHub Pages Stream Ready";
|
|
1260
|
+
} else if (progress < 95) {
|
|
1261
|
+
if (status) status.textContent = `[${progress}%] TURBO PRE-CACHING 788 GAMES...`;
|
|
1262
|
+
if (terminalText) terminalText.textContent = "[TURBO ENGINE] 788 Games Cached & Ready (60 FPS)";
|
|
1073
1263
|
} else {
|
|
1074
1264
|
if (status) status.textContent = `[100%] ALL SYSTEMS ONLINE`;
|
|
1265
|
+
if (terminalText) terminalText.textContent = "[BOOT] Core Online. Welcome to Reaper Hub!";
|
|
1075
1266
|
clearInterval(timer);
|
|
1076
1267
|
clearInterval(quoteInterval);
|
|
1077
1268
|
setTimeout(() => {
|
|
1269
|
+
splash.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
|
|
1078
1270
|
splash.style.opacity = '0';
|
|
1079
|
-
|
|
1080
|
-
|
|
1271
|
+
splash.style.transform = 'scale(1.03)';
|
|
1272
|
+
setTimeout(() => { splash.style.display = 'none'; }, 400);
|
|
1273
|
+
}, 200);
|
|
1081
1274
|
}
|
|
1082
1275
|
}, interval);
|
|
1083
1276
|
}
|
|
@@ -1167,24 +1360,18 @@ class ReaperHubApp {
|
|
|
1167
1360
|
window.reaperFirebase.db.ref("reaperPlaytime").on("value", (snap) => {
|
|
1168
1361
|
const val = snap.val();
|
|
1169
1362
|
if (val) {
|
|
1170
|
-
this.leaderboardData = Object.values(val)
|
|
1363
|
+
this.leaderboardData = Object.values(val).filter(u => {
|
|
1364
|
+
return u && u.username && u.username !== 'User' && u.username !== 'Member' && u.username !== 'Anon' && (u.seconds || 0) > 0;
|
|
1365
|
+
});
|
|
1171
1366
|
this.leaderboardData.sort((a, b) => (b.seconds || 0) - (a.seconds || 0));
|
|
1172
1367
|
} else {
|
|
1173
|
-
this.leaderboardData = [
|
|
1174
|
-
{ username: this.currentUser.username, seconds: this.playtimeSeconds, rank: this.currentUser.rank }
|
|
1175
|
-
];
|
|
1368
|
+
this.leaderboardData = [];
|
|
1176
1369
|
}
|
|
1177
1370
|
this.renderLeaderboardUI();
|
|
1178
|
-
this.renderAdminUsersDirectory();
|
|
1179
1371
|
});
|
|
1180
1372
|
} catch (e) {
|
|
1181
1373
|
this.renderLeaderboardUI();
|
|
1182
1374
|
}
|
|
1183
|
-
} else {
|
|
1184
|
-
this.leaderboardData = [
|
|
1185
|
-
{ username: this.currentUser.username, seconds: this.playtimeSeconds, rank: this.currentUser.rank }
|
|
1186
|
-
];
|
|
1187
|
-
this.renderLeaderboardUI();
|
|
1188
1375
|
}
|
|
1189
1376
|
}
|
|
1190
1377
|
|
|
@@ -1200,13 +1387,21 @@ class ReaperHubApp {
|
|
|
1200
1387
|
const container = document.getElementById('leaderboard-rows-container');
|
|
1201
1388
|
if (!container) return;
|
|
1202
1389
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1390
|
+
// Filter strictly to real players with recorded playtime
|
|
1391
|
+
const realPlayers = (this.leaderboardData || []).filter(p => {
|
|
1392
|
+
return p && p.username && p.username !== 'User' && p.username !== 'Member' && p.username !== 'Anon' && (p.seconds || 0) > 0;
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
if (realPlayers.length === 0) {
|
|
1396
|
+
container.innerHTML = `
|
|
1397
|
+
<div style="text-align: center; padding: 2rem; color: #88aacc; font-family: var(--font-mono); font-size: 0.85rem;">
|
|
1398
|
+
Play any game to register your playtime on the global leaderboard!
|
|
1399
|
+
</div>
|
|
1400
|
+
`;
|
|
1401
|
+
return;
|
|
1207
1402
|
}
|
|
1208
1403
|
|
|
1209
|
-
container.innerHTML =
|
|
1404
|
+
container.innerHTML = realPlayers.slice(0, 20).map((player, idx) => {
|
|
1210
1405
|
const rankNum = idx + 1;
|
|
1211
1406
|
const timeStr = this.formatPlaytime(player.seconds || 0);
|
|
1212
1407
|
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();
|