Add Fabric client IRC chat mod and Drone pipeline

This commit is contained in:
2026-06-21 14:31:38 +06:00
commit 910c58c30b
13 changed files with 837 additions and 0 deletions

6
server/config.json Normal file
View File

@@ -0,0 +1,6 @@
{
"host": "0.0.0.0",
"port": 24469,
"token": ""
}

132
server/irc-server.js Normal file
View File

@@ -0,0 +1,132 @@
'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;
}
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}`);
});