hoel/js/server.js
2026-01-22 19:21:59 +01:00

203 lines
6.3 KiB
JavaScript

const express = require('express');
const fs = require('fs');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const LEADERBOARD_FILE = './leaderboard.json';
const INACTIVE_DAYS = 30; // Completely remove from JSON after 30 days
const LEADERBOARD_VISIBLE_DAYS = 5; // Only show on leaderboard if active within 5 days
// Load leaderboard from file
function loadLeaderboard() {
try {
return JSON.parse(fs.readFileSync(LEADERBOARD_FILE, 'utf8'));
} catch (e) {
return [];
}
}
// Save leaderboard to file
function saveLeaderboard(data) {
fs.writeFileSync(LEADERBOARD_FILE, JSON.stringify(data, null, 2));
}
// Clean up inactive players (remove from JSON after 30 days)
function cleanupInactive(leaderboard) {
const thirtyDaysAgo = Date.now() - (INACTIVE_DAYS * 24 * 60 * 60 * 1000);
const beforeCount = leaderboard.length;
const active = leaderboard.filter(player => player.lastSeen > thirtyDaysAgo);
if (active.length < beforeCount) {
console.log(`Permanently removed ${beforeCount - active.length} players (>30 days inactive)`);
}
return active;
}
// Filter players visible on leaderboard (active within 5 days)
function getVisiblePlayers(leaderboard) {
const fiveDaysAgo = Date.now() - (LEADERBOARD_VISIBLE_DAYS * 24 * 60 * 60 * 1000);
const visible = leaderboard.filter(player => player.lastSeen > fiveDaysAgo);
return visible;
}
// Submit checkpoint
app.post('/api/checkpoint', (req, res) => {
const { playerId, name, mass, playTime, levels } = req.body;
// Basic validation
if (!playerId || mass < 0 || playTime < 0) {
return res.status(400).json({ error: 'Invalid data' });
}
let leaderboard = loadLeaderboard();
// Clean up players inactive for 30+ days
leaderboard = cleanupInactive(leaderboard);
const now = Date.now();
// Find or create player
let player = leaderboard.find(p => p.id === playerId);
if (player) {
// Update existing player
player.mass = Math.max(player.mass, mass);
player.playTime = Math.max(player.playTime, playTime);
player.level = Math.max(player.level, levels?.total || 0);
player.name = name || player.name;
player.lastSeen = now;
// Calculate hole age (time since first seen)
player.holeAge = now - player.firstSeen;
} else {
// New player
leaderboard.push({
id: playerId,
name: name || 'Anonymous',
mass: mass,
playTime: playTime,
level: levels?.total || 0,
firstSeen: now,
lastSeen: now,
holeAge: 0
});
}
// Sort by mass and keep top 100 (in JSON file)
leaderboard.sort((a, b) => b.mass - a.mass);
leaderboard = leaderboard.slice(0, 100);
saveLeaderboard(leaderboard);
res.json({ success: true });
});
// Get leaderboard
app.get('/api/leaderboard', (req, res) => {
const type = req.query.type || 'mass';
const requesterId = req.query.playerId; // Get player ID from query parameter
let leaderboard = loadLeaderboard();
// Clean up players inactive for 30+ days
leaderboard = cleanupInactive(leaderboard);
// Update hole ages and online status
const now = Date.now();
const fifteenMinutes = 15 * 60 * 1000;
leaderboard.forEach(player => {
player.holeAge = now - player.firstSeen;
player.isOnline = (now - player.lastSeen) < fifteenMinutes;
});
// Filter to only show players active within 5 days
let visiblePlayers = getVisiblePlayers(leaderboard);
// Sort by requested type
if (type === 'age') {
visiblePlayers.sort((a, b) => b.holeAge - a.holeAge);
} else if (type === 'time') {
visiblePlayers.sort((a, b) => b.playTime - a.playTime);
} else if (type === 'level') {
visiblePlayers.sort((a, b) => b.level - a.level);
} else {
visiblePlayers.sort((a, b) => b.mass - a.mass);
}
// Log who opened the leaderboard
if (requesterId) {
const requester = leaderboard.find(p => p.id === requesterId);
const requesterName = requester ? requester.name : 'Unknown';
const timestamp = new Date().toLocaleString();
console.log(`${timestamp} Score requested by: ${requesterName} (${requesterId.slice(-5)})`);
}
// Save the full leaderboard (including hidden players)
saveLeaderboard(leaderboard);
// Return only visible players (top 50)
res.json(visiblePlayers.slice(0, 50));
});
// Get player info (works even if not visible on leaderboard)
app.get('/api/player/:playerId', (req, res) => {
const playerId = req.params.playerId;
let leaderboard = loadLeaderboard();
const player = leaderboard.find(p => p.id === playerId);
if (!player) {
return res.status(404).json({ error: 'Player not found' });
}
// Update hole age
player.holeAge = Date.now() - player.firstSeen;
// Calculate days since last seen
player.daysSinceLastSeen = Math.floor((Date.now() - player.lastSeen) / (24 * 60 * 60 * 1000));
// Check if visible on leaderboard
const fiveDaysAgo = Date.now() - (LEADERBOARD_VISIBLE_DAYS * 24 * 60 * 60 * 1000);
player.visibleOnLeaderboard = player.lastSeen > fiveDaysAgo;
res.json(player);
});
// Manual cleanup endpoint (optional - for maintenance)
app.post('/api/cleanup', (req, res) => {
let leaderboard = loadLeaderboard();
const beforeCount = leaderboard.length;
leaderboard = cleanupInactive(leaderboard);
saveLeaderboard(leaderboard);
res.json({
removed: beforeCount - leaderboard.length,
remaining: leaderboard.length
});
});
// Stats endpoint (optional - see how many are hidden vs visible)
app.get('/api/stats', (req, res) => {
let leaderboard = loadLeaderboard();
const visiblePlayers = getVisiblePlayers(leaderboard);
res.json({
totalPlayers: leaderboard.length,
visiblePlayers: visiblePlayers.length,
hiddenPlayers: leaderboard.length - visiblePlayers.length
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Players hidden from leaderboard after ${LEADERBOARD_VISIBLE_DAYS} days inactive`);
console.log(`Players permanently removed after ${INACTIVE_DAYS} days inactive`);
});