102 lines
3.8 KiB
JavaScript
102 lines
3.8 KiB
JavaScript
'use strict';
|
|
// Serveur de sync communautaire Farm Tracker.
|
|
// Stocke les stats de chaque joueur (identifié par UUID) et renvoie l'agrégat.
|
|
// Lance avec : node server.js (ou PORT=3742 node server.js)
|
|
// Aucune dépendance externe.
|
|
|
|
const http = require('http');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const DATA_FILE = path.join(__dirname, 'sync-data.json');
|
|
const PORT = Number(process.env.PORT) || 3742;
|
|
const MAX_BODY = 10 * 1024 * 1024; // 10 Mo max par requête
|
|
|
|
function loadData() {
|
|
try { return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); }
|
|
catch { return { clients: {} }; }
|
|
}
|
|
|
|
function saveData(data) {
|
|
const tmp = DATA_FILE + '.tmp';
|
|
fs.writeFileSync(tmp, JSON.stringify(data));
|
|
fs.renameSync(tmp, DATA_FILE);
|
|
}
|
|
|
|
function mergeInto(target, source) {
|
|
for (const [mob, mobData] of Object.entries(source.mobs || {})) {
|
|
if (!mob) continue;
|
|
if (!target.mobs[mob]) target.mobs[mob] = { kills: 0, items: {} };
|
|
const t = target.mobs[mob];
|
|
t.kills += Number(mobData.kills) || 0;
|
|
for (const [key, item] of Object.entries(mobData.items || {})) {
|
|
if (!t.items[key]) t.items[key] = { rawName: '', name: '', occurrences: 0, totalQty: 0 };
|
|
t.items[key].occurrences += Number(item.occurrences) || 0;
|
|
t.items[key].totalQty += Number(item.totalQty) || 0;
|
|
if (item.rawName) t.items[key].rawName = item.rawName;
|
|
if (item.name) t.items[key].name = item.name;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recompute l'agrégat à partir des contributions individuelles de chaque client.
|
|
// Chaque client est compté exactement une fois (la dernière soumission).
|
|
function computeAggregate(clients) {
|
|
const agg = { type: 'farm-tracker-dropstats', version: 1, updatedAt: new Date().toISOString(), mobs: {} };
|
|
for (const stats of Object.values(clients)) mergeInto(agg, stats);
|
|
return agg;
|
|
}
|
|
|
|
http.createServer((req, res) => {
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
|
|
if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
|
|
|
|
if (req.method === 'GET' && req.url === '/stats') {
|
|
const data = loadData();
|
|
const aggregate = computeAggregate(data.clients);
|
|
const clientCount = Object.keys(data.clients).length;
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ ok: true, dropStats: aggregate, clientCount }));
|
|
return;
|
|
}
|
|
|
|
if (req.method !== 'POST' || req.url !== '/sync') {
|
|
res.writeHead(404); res.end('Not found'); return;
|
|
}
|
|
|
|
let body = '';
|
|
req.on('data', chunk => {
|
|
body += chunk;
|
|
if (body.length > MAX_BODY) req.destroy();
|
|
});
|
|
req.on('end', () => {
|
|
try {
|
|
const { clientId, dropStats } = JSON.parse(body);
|
|
if (!clientId || typeof clientId !== 'string' || clientId.length > 64)
|
|
throw new Error('clientId invalide');
|
|
if (!dropStats || dropStats.type !== 'farm-tracker-dropstats')
|
|
throw new Error('Format dropStats invalide');
|
|
|
|
const data = loadData();
|
|
data.clients[clientId] = dropStats;
|
|
const aggregate = computeAggregate(data.clients);
|
|
saveData(data);
|
|
|
|
const clientCount = Object.keys(data.clients).length;
|
|
console.log(`[${new Date().toISOString()}] sync client=${clientId.slice(0, 8)}… clients=${clientCount}`);
|
|
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ ok: true, dropStats: aggregate, clientCount }));
|
|
} catch (e) {
|
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ ok: false, error: e.message }));
|
|
}
|
|
});
|
|
req.on('error', () => {});
|
|
}).listen(PORT, '0.0.0.0', () => {
|
|
console.log(`Farm Tracker sync server — port ${PORT} — data: ${DATA_FILE}`);
|
|
});
|