'use strict'; const fs = require('node:fs'); const path = require('node:path'); const net = require('node:net'); const configPath = path.join(__dirname, 'config.json'); const defaultConfig = { host: '0.0.0.0', port: 24469, token: '' }; if (!fs.existsSync(configPath)) { fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2)); } const config = { ...defaultConfig, ...JSON.parse(fs.readFileSync(configPath, 'utf8')) }; const clients = new Set(); function sendJson(socket, payload) { socket.write(JSON.stringify(payload) + '\n'); } function broadcast(payload) { const line = JSON.stringify(payload) + '\n'; for (const socket of clients) { if (!socket.destroyed) { socket.write(line); } } } function systemMessage(message) { return { type: 'system', message, timestamp: Date.now() }; } const server = net.createServer((socket) => { socket.setEncoding('utf8'); clients.add(socket); let buffer = ''; sendJson(socket, systemMessage('Connected to IRC server.')); console.log(`[connect] ${socket.remoteAddress}:${socket.remotePort}`); socket.on('data', (chunk) => { buffer += chunk; let separatorIndex = buffer.indexOf('\n'); while (separatorIndex !== -1) { const rawLine = buffer.slice(0, separatorIndex).trim(); buffer = buffer.slice(separatorIndex + 1); separatorIndex = buffer.indexOf('\n'); if (!rawLine) { continue; } let payload; try { payload = JSON.parse(rawLine); } catch (error) { sendJson(socket, systemMessage(`Bad JSON: ${error.message}`)); continue; } if (config.token && payload.token !== config.token) { sendJson(socket, systemMessage('Invalid token.')); continue; } if (payload.type === 'hello') { const name = payload.player || 'unknown'; sendJson(socket, systemMessage(`Hello, ${name}.`)); continue; } if (payload.type === 'chat') { const clientName = String(payload.client || 'UNKNOWN-CLIENT').trim(); const playerName = String(payload.player || 'unknown').trim(); const message = String(payload.message || '').trim(); if (!message) { sendJson(socket, systemMessage('Empty chat message ignored.')); continue; } const outgoing = { type: 'chat', client: clientName, player: playerName, message, timestamp: Date.now() }; console.log(`[chat] ${clientName} >> ${playerName}: ${message}`); broadcast(outgoing); continue; } if (payload.type === 'marker_set') { const clientName = String(payload.client || 'UNKNOWN-CLIENT').trim(); const playerName = String(payload.player || 'unknown').trim(); const dimensionId = String(payload.dimensionId || 'minecraft:overworld').trim(); const x = Number(payload.x); const y = Number(payload.y); const z = Number(payload.z); if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) { sendJson(socket, systemMessage('Bad marker_set coords.')); continue; } const outgoing = { type: 'marker_set', client: clientName, player: playerName, dimensionId, x, y, z, timestamp: Date.now() }; console.log(`[marker_set] ${clientName} >> ${playerName} @ ${dimensionId} ${x.toFixed(1)},${y.toFixed(1)},${z.toFixed(1)}`); broadcast(outgoing); continue; } if (payload.type === 'marker_remove') { const clientName = String(payload.client || 'UNKNOWN-CLIENT').trim(); const playerName = String(payload.player || 'unknown').trim(); const outgoing = { type: 'marker_remove', client: clientName, player: playerName, timestamp: Date.now() }; console.log(`[marker_remove] ${clientName} >> ${playerName}`); broadcast(outgoing); continue; } sendJson(socket, systemMessage(`Unknown packet type: ${payload.type}`)); } }); socket.on('close', () => { clients.delete(socket); console.log(`[disconnect] ${socket.remoteAddress}:${socket.remotePort}`); }); socket.on('error', (error) => { clients.delete(socket); console.error(`[socket-error] ${socket.remoteAddress}:${socket.remotePort} ${error.message}`); }); }); server.on('error', (error) => { console.error(`[server-error] ${error.message}`); process.exitCode = 1; }); server.listen(config.port, config.host, () => { console.log(`IRC server listening on ${config.host}:${config.port}`); });