Objekt-Anzahl stufenweise, Ban-CLI, Live-Abbruch bei privat
- Objekt-Anzahl waechst ab STEP_START zusaetzlich schrittweise (Deckel 100), Mindestgroesse leicht gesenkt fuer dichte Felder - Ban per CLI: ban/unban/bans (namensbasiert), gebannte Namen koennen keinen Score eintragen (/api/score -> 403) und werden beim Bannen aus der Rangliste entfernt - Privat-Modus bricht laufende Spiele ab: Client pollt /api/status und laedt neu, sobald Spielen auf dem Pfad nicht mehr erlaubt ist (geheimer Pfad wird nicht verraten) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
import { readLeaderboard, buildBoards, deletePlayer, clearAll, clearToday } from './leaderboardStore.js';
|
||||
import { getMode, setMode } from './modeStore.js';
|
||||
import { banPlayer, unbanPlayer, readBans } from './banStore.js';
|
||||
|
||||
const [, , cmd, ...rest] = process.argv;
|
||||
const arg = rest.join(' ').trim();
|
||||
@@ -51,6 +52,34 @@ switch (cmd) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ban': {
|
||||
if (!arg) {
|
||||
console.error('Bitte einen Namen angeben: node server/admin.js ban "Name"');
|
||||
process.exit(1);
|
||||
}
|
||||
const added = banPlayer(arg);
|
||||
const removed = deletePlayer(arg); // bestehende Eintraege gleich entfernen
|
||||
if (added) console.log(`🚫 "${arg}" gebannt (kann keinen Score mehr eintragen).`);
|
||||
else console.log(`"${arg}" war bereits gebannt.`);
|
||||
if (removed) console.log(` ${removed} bestehende(r) Eintrag/Eintraege entfernt.`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unban': {
|
||||
if (!arg) {
|
||||
console.error('Bitte einen Namen angeben: node server/admin.js unban "Name"');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(unbanPlayer(arg) ? `✅ "${arg}" entsperrt.` : `"${arg}" war nicht gebannt.`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'bans': {
|
||||
const bans = readBans();
|
||||
console.log(bans.length ? '🚫 Gebannt:\n' + bans.map((b) => ` - ${b}`).join('\n') : 'Keine Bans.');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'mode': {
|
||||
const sub = (rest[0] || '').toLowerCase();
|
||||
if (sub === 'public') {
|
||||
@@ -78,6 +107,11 @@ Rangliste:
|
||||
node server/admin.js clear-today nur die heutige Rangliste leeren
|
||||
node server/admin.js clear-all komplette Rangliste leeren
|
||||
|
||||
Bannen (namensbasiert):
|
||||
node server/admin.js ban "<name>" Spieler sperren (+ Eintraege entfernen)
|
||||
node server/admin.js unban "<name>" Sperre aufheben
|
||||
node server/admin.js bans gebannte Namen anzeigen
|
||||
|
||||
Betriebsmodus (Spiel oeffentlich / privat):
|
||||
node server/admin.js mode aktuellen Modus anzeigen
|
||||
node server/admin.js mode public Spiel oeffentlich unter "/"
|
||||
|
||||
51
server/banStore.js
Normal file
51
server/banStore.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// Namensbasierte Sperrliste, persistent in data/bans.json.
|
||||
// Ein gebannter Name kann keinen Score mehr eintragen (siehe /api/score) und seine
|
||||
// bestehenden Eintraege werden beim Bannen entfernt. Vergleich case-insensitiv.
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const DATA_DIR = join(__dirname, '..', 'data');
|
||||
const BANS_FILE = join(DATA_DIR, 'bans.json');
|
||||
|
||||
const norm = (name) => String(name || '').trim().toLowerCase();
|
||||
|
||||
export function readBans() {
|
||||
try {
|
||||
if (!existsSync(BANS_FILE)) return [];
|
||||
const data = JSON.parse(readFileSync(BANS_FILE, 'utf8'));
|
||||
return Array.isArray(data) ? data : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function writeBans(list) {
|
||||
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
||||
writeFileSync(BANS_FILE, JSON.stringify(list, null, 2));
|
||||
}
|
||||
|
||||
export function isBanned(name) {
|
||||
return readBans().includes(norm(name));
|
||||
}
|
||||
|
||||
export function banPlayer(name) {
|
||||
const n = norm(name);
|
||||
if (!n) return false;
|
||||
const bans = readBans();
|
||||
if (bans.includes(n)) return false; // schon gebannt
|
||||
bans.push(n);
|
||||
writeBans(bans);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function unbanPlayer(name) {
|
||||
const n = norm(name);
|
||||
const bans = readBans();
|
||||
const kept = bans.filter((b) => b !== n);
|
||||
if (kept.length === bans.length) return false; // war nicht gebannt
|
||||
writeBans(kept);
|
||||
return true;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { readLeaderboard, buildBoards, addScore } from './leaderboardStore.js';
|
||||
import { getMode } from './modeStore.js';
|
||||
import { isBanned } from './banStore.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(__dirname, '..');
|
||||
@@ -117,6 +118,14 @@ app.post('/api/game/hit', (req, res) => {
|
||||
res.json({ counted: true });
|
||||
});
|
||||
|
||||
// Darf auf dem aktuellen Pfad gespielt werden? (Client sendet nur seinen Pfad-Slug,
|
||||
// der geheime Pfad wird NICHT preisgegeben.) Dient zum Abbruch laufender Spiele.
|
||||
app.get('/api/status', (req, res) => {
|
||||
const m = getMode();
|
||||
const slug = String(req.query.path || '').replace(/^\/+/, '');
|
||||
res.json({ allowed: m.mode === 'public' || slug === m.path });
|
||||
});
|
||||
|
||||
// Ranglisten: Allzeit-Top-5 + Heute-Top-10
|
||||
app.get('/api/leaderboard', (req, res) => {
|
||||
res.json(buildBoards(readLeaderboard()));
|
||||
@@ -134,6 +143,7 @@ app.post('/api/score', (req, res) => {
|
||||
const score = Math.max(0, Math.min(g.hits, maxByTime, ABS_CAP));
|
||||
|
||||
const cleanName = String(name || 'Anonym').trim().slice(0, 20) || 'Anonym';
|
||||
if (isBanned(cleanName)) return res.status(403).json({ error: 'Name gesperrt.' });
|
||||
res.json(addScore(cleanName, score));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user