From ae159f6d6cb2d5ab933730a3c07903452df8c1ef Mon Sep 17 00:00:00 2001 From: WlodekM Date: Tue, 26 Nov 2024 09:51:39 +0200 Subject: begin rewrite --- .vscode/settings.json | 3 + config.ini | 6 +- index.js | 210 -------------------------------------------------- newindex.js | 1 + oldindex.js | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 27 ++++++- server.js | 173 ----------------------------------------- server.ts | 187 ++++++++++++++++++++++++++++++++++++++++++++ user.js | 8 +- 10 files changed, 435 insertions(+), 391 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 index.js create mode 100644 oldindex.js delete mode 100644 server.js create mode 100644 server.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b943dbc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "deno.enable": true +} \ No newline at end of file diff --git a/config.ini b/config.ini index ffd91e1..5fc55eb 100644 --- a/config.ini +++ b/config.ini @@ -19,11 +19,11 @@ annonFormat = "Anonymous#[num]" [profanity] filter = true -removeWords = ["butt", "arse", "balls", "dick"] -addWords = ["kys"] +removeWords = butt, arse, balls, dick +addWords = kys [channels] -channels = ["home", "off-topic", "random", "safe"] +channels = home, off-topic, random, safe [channel-home] ; You can set settings like profanity for specific channels diff --git a/index.js b/index.js deleted file mode 100644 index 6c17622..0000000 --- a/index.js +++ /dev/null @@ -1,210 +0,0 @@ -import { WebSocketServer } from "ws"; -import { getRandomInt } from "./lib.js"; -import { profanity } from "@2toad/profanity"; -import { commands } from "./commands.js"; -import * as accounts from "./accounts.js"; -import cuid from "cuid"; -import fs from "fs"; - -const server = { - config: JSON.parse(String(fs.readFileSync("config.json"))), - channels: ["home", "off-topic", "randomness"], - annonChannels: ["randomness"], - users: {}, - accounts: accounts, -}; - -if (server.config.profanityRemoveWords) profanity.removeWords(server.config.profanityRemoveWords); -if (server.config.profanityAddWords) profanity.addWords(server.config.profanityAddWords); - -const ws = new WebSocketServer({ - port: 9933, -}); - -function sendInChannel(msg, channel) { - for (const userID in server.users) { - const user = server.users[userID]; - if (user.channel == channel) user.socket.send(msg); - } -} - -function format(txt) { - txt = String(txt); - txt = txt.replaceAll("$(serverName)$", server.config.name); - txt = txt.replaceAll("$(userCount)$", Object.keys(server.users).length); - txt = txt.replaceAll("$(max)$", server.config.max); - return txt; -} - -function updateUsers() { - Object.keys(server.users).forEach((user) => { - if (user.subscribedToUsers) { - user.socket.send( - `:json.sub:${JSON.stringify( - Object.values(server.users).map((usr) => { - return { - username: usr.username, - nickname: usr.nickname, - t: usr.t, - channel: user.channel, - displayName: user.name(), - }; - }) - )}` - ); - } - }); -} - -server.updateUsers = updateUsers; -server.format = format; - -ws.on("connection", (socket, request) => { - if (server.config.max && Object.keys(server.users).length >= server.config.max) { - socket.send(format(server.config.fullMessage ?? "Sorry, but the server is full right now, come back later")); - socket.close(1001, "Server full"); - return; - } - let userID = cuid(); - console.info(`${userID} joined the server.`); - socket.send(format(server.config.motd)); - let ip = request.headers["x-forwarded-for"] || request.socket.remoteAddress; - console.log(request.headers["x-forwarded-for"], request.socket.remoteAddress); - let anonID = getRandomInt(0, 99999); - let annonNum = "0".repeat(5 - anonID.toString().length) + anonID.toString() - server.users[userID] = { - username: server.config.annonFormat ? server.config.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum, - nickname: server.config.annonFormat ? server.config.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum, - guest: true, - socket: socket, - joinReq: request, - ip: ip, - t: { - js: Number(new Date()), - unix: Math.floor(new Date().getTime() / 1000), - str: String(new Date()), - }, - channel: "home", - name: function () { - return this.nickname != "" ? this.nickname : this.username; - }, - }; - const user = server.users[userID]; - let ipBanList = JSON.parse(String(fs.readFileSync("db/bannedIps.json"))); - if (ipBanList[user.ip]) { - socket.send("Your IP is banned for " + ipBanList[user.ip]); - socket.close(1002, "Banned"); - return; - } - console.info(`${user.name()} joined the server!`); - sendInChannel(`${user.name()} joined.`, server.users[userID].channel); - server.updateUsers(); - socket.on("close", function (code, reason) { - sendInChannel(`${user.name()} left.`, server.users[userID].channel); - server.updateUsers(); - delete server.users[userID]; - }); - socket.on("message", function (rawData) { - if (rawData.toString().startsWith("/")) { - let args = String(rawData).replace("/", "").split(" "); - let command = args.shift(); - let commandObj = Object.values(commands).find((cmd) => cmd.name == command || cmd.aliases.includes(command)); - console.log(`${user.name()} used /${command}`); - if (!commandObj) return socket.send(`Error: Command "${command}" not found!`); - try { - commandObj.command({ - user, - command, - args, - sendInChannel, - server, - commands, - }); - } catch (error) { - console.error(error); - user.socket.send(`Unexpected error ocurred while running the command`); - } - return; - } - if (rawData.toString().startsWith(":client")) { - let client = String(rawData).replace(":client", ""); - if (!client) return socket.send("Error: client info missing!"); - if (client.length < 2) return socket.send("Error: client info too short!"); - if (client.length >= 100) return socket.send("Error: client info too long!"); - server.users[userID].client = client; - return; - } - if (rawData.toString().startsWith(":jsonGet")) { - let params = String(rawData).split(" "); - params.shift(); - switch (params[0]) { - case "channels": - socket.send(":json.channels>" + JSON.stringify(server.channels)); - break; - case "users": - socket.send( - ":json.users>" + - JSON.stringify( - Object.values(server.users).map((usr) => { - return { - username: usr.username, - nickname: usr.nickname, - t: usr.t, - channel: user.channel, - displayName: user.name(), - }; - }) - ) - ); - break; - case "usersLocal": - socket.send( - JSON.stringify( - Object.values(server.users) - .filter((usr) => usr.channel == user.channel) - .map((usr) => { - return { - username: usr.username, - nickname: usr.nickname, - t: usr.t, - channel: user.channel, - displayName: user.name(), - }; - }) - ) - ); - break; - - default: - socket.send(`unknown "${params[0]}"`); - break; - } - return; - } - if (rawData.toString().startsWith(":jsonSubscribe")) { - let params = String(rawData).split(" "); - params.shift(); - switch (params[0]) { - case "users": - user.subscribedToUsers = true; - break; - - default: - socket.send(`unknown "${params[0]}"`); - break; - } - return; - } - if (server.config.requireLogin && user.guest && !server.annonChannels.includes(user.channel)) return socket.send("This server requires you to log in, use /login to log in or /register to make an account."); - profanity.options.grawlixChar = "*"; - if (!server.config.profanity) rawData = profanity.censor(String(rawData)); - if (rawData.length < 1) return socket.send("Error: message too short!"); - if (rawData.length >= 2000) return socket.send("Error: message too long!"); - sendInChannel(`${user.admin ? '[ADMIN] ' : ''}<${user.name()}${user.guest ? " (guest)" : ""}> ${rawData}`, server.users[userID].channel); - console.log(`(#${server.users[userID].channel}) <${user.name()}> ${rawData}`); - }); -}); - -ws.on("listening", () => { - console.info("[INFO] Server started"); -}); diff --git a/newindex.js b/newindex.js index 536264f..a2d2763 100644 --- a/newindex.js +++ b/newindex.js @@ -1,4 +1,5 @@ import Server from './server.js'; import ini from "ini" +import fs from "fs" const server = new Server(ini.parse(String(fs.readFileSync("config.ini")))) diff --git a/oldindex.js b/oldindex.js new file mode 100644 index 0000000..6c17622 --- /dev/null +++ b/oldindex.js @@ -0,0 +1,210 @@ +import { WebSocketServer } from "ws"; +import { getRandomInt } from "./lib.js"; +import { profanity } from "@2toad/profanity"; +import { commands } from "./commands.js"; +import * as accounts from "./accounts.js"; +import cuid from "cuid"; +import fs from "fs"; + +const server = { + config: JSON.parse(String(fs.readFileSync("config.json"))), + channels: ["home", "off-topic", "randomness"], + annonChannels: ["randomness"], + users: {}, + accounts: accounts, +}; + +if (server.config.profanityRemoveWords) profanity.removeWords(server.config.profanityRemoveWords); +if (server.config.profanityAddWords) profanity.addWords(server.config.profanityAddWords); + +const ws = new WebSocketServer({ + port: 9933, +}); + +function sendInChannel(msg, channel) { + for (const userID in server.users) { + const user = server.users[userID]; + if (user.channel == channel) user.socket.send(msg); + } +} + +function format(txt) { + txt = String(txt); + txt = txt.replaceAll("$(serverName)$", server.config.name); + txt = txt.replaceAll("$(userCount)$", Object.keys(server.users).length); + txt = txt.replaceAll("$(max)$", server.config.max); + return txt; +} + +function updateUsers() { + Object.keys(server.users).forEach((user) => { + if (user.subscribedToUsers) { + user.socket.send( + `:json.sub:${JSON.stringify( + Object.values(server.users).map((usr) => { + return { + username: usr.username, + nickname: usr.nickname, + t: usr.t, + channel: user.channel, + displayName: user.name(), + }; + }) + )}` + ); + } + }); +} + +server.updateUsers = updateUsers; +server.format = format; + +ws.on("connection", (socket, request) => { + if (server.config.max && Object.keys(server.users).length >= server.config.max) { + socket.send(format(server.config.fullMessage ?? "Sorry, but the server is full right now, come back later")); + socket.close(1001, "Server full"); + return; + } + let userID = cuid(); + console.info(`${userID} joined the server.`); + socket.send(format(server.config.motd)); + let ip = request.headers["x-forwarded-for"] || request.socket.remoteAddress; + console.log(request.headers["x-forwarded-for"], request.socket.remoteAddress); + let anonID = getRandomInt(0, 99999); + let annonNum = "0".repeat(5 - anonID.toString().length) + anonID.toString() + server.users[userID] = { + username: server.config.annonFormat ? server.config.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum, + nickname: server.config.annonFormat ? server.config.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum, + guest: true, + socket: socket, + joinReq: request, + ip: ip, + t: { + js: Number(new Date()), + unix: Math.floor(new Date().getTime() / 1000), + str: String(new Date()), + }, + channel: "home", + name: function () { + return this.nickname != "" ? this.nickname : this.username; + }, + }; + const user = server.users[userID]; + let ipBanList = JSON.parse(String(fs.readFileSync("db/bannedIps.json"))); + if (ipBanList[user.ip]) { + socket.send("Your IP is banned for " + ipBanList[user.ip]); + socket.close(1002, "Banned"); + return; + } + console.info(`${user.name()} joined the server!`); + sendInChannel(`${user.name()} joined.`, server.users[userID].channel); + server.updateUsers(); + socket.on("close", function (code, reason) { + sendInChannel(`${user.name()} left.`, server.users[userID].channel); + server.updateUsers(); + delete server.users[userID]; + }); + socket.on("message", function (rawData) { + if (rawData.toString().startsWith("/")) { + let args = String(rawData).replace("/", "").split(" "); + let command = args.shift(); + let commandObj = Object.values(commands).find((cmd) => cmd.name == command || cmd.aliases.includes(command)); + console.log(`${user.name()} used /${command}`); + if (!commandObj) return socket.send(`Error: Command "${command}" not found!`); + try { + commandObj.command({ + user, + command, + args, + sendInChannel, + server, + commands, + }); + } catch (error) { + console.error(error); + user.socket.send(`Unexpected error ocurred while running the command`); + } + return; + } + if (rawData.toString().startsWith(":client")) { + let client = String(rawData).replace(":client", ""); + if (!client) return socket.send("Error: client info missing!"); + if (client.length < 2) return socket.send("Error: client info too short!"); + if (client.length >= 100) return socket.send("Error: client info too long!"); + server.users[userID].client = client; + return; + } + if (rawData.toString().startsWith(":jsonGet")) { + let params = String(rawData).split(" "); + params.shift(); + switch (params[0]) { + case "channels": + socket.send(":json.channels>" + JSON.stringify(server.channels)); + break; + case "users": + socket.send( + ":json.users>" + + JSON.stringify( + Object.values(server.users).map((usr) => { + return { + username: usr.username, + nickname: usr.nickname, + t: usr.t, + channel: user.channel, + displayName: user.name(), + }; + }) + ) + ); + break; + case "usersLocal": + socket.send( + JSON.stringify( + Object.values(server.users) + .filter((usr) => usr.channel == user.channel) + .map((usr) => { + return { + username: usr.username, + nickname: usr.nickname, + t: usr.t, + channel: user.channel, + displayName: user.name(), + }; + }) + ) + ); + break; + + default: + socket.send(`unknown "${params[0]}"`); + break; + } + return; + } + if (rawData.toString().startsWith(":jsonSubscribe")) { + let params = String(rawData).split(" "); + params.shift(); + switch (params[0]) { + case "users": + user.subscribedToUsers = true; + break; + + default: + socket.send(`unknown "${params[0]}"`); + break; + } + return; + } + if (server.config.requireLogin && user.guest && !server.annonChannels.includes(user.channel)) return socket.send("This server requires you to log in, use /login to log in or /register to make an account."); + profanity.options.grawlixChar = "*"; + if (!server.config.profanity) rawData = profanity.censor(String(rawData)); + if (rawData.length < 1) return socket.send("Error: message too short!"); + if (rawData.length >= 2000) return socket.send("Error: message too long!"); + sendInChannel(`${user.admin ? '[ADMIN] ' : ''}<${user.name()}${user.guest ? " (guest)" : ""}> ${rawData}`, server.users[userID].channel); + console.log(`(#${server.users[userID].channel}) <${user.name()}> ${rawData}`); + }); +}); + +ws.on("listening", () => { + console.info("[INFO] Server started"); +}); diff --git a/package.json b/package.json index 06f596a..344cf6b 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "@2toad/profanity": "^2.2.0", + "@types/node": "^22.10.0", "cuid": "^3.0.0", "ini": "^5.0.0", "ws": "^8.17.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a57313..be20854 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,18 @@ importers: '@2toad/profanity': specifier: ^2.2.0 version: 2.5.0 + '@types/node': + specifier: ^22.10.0 + version: 22.10.0 cuid: specifier: ^3.0.0 version: 3.0.0 ini: specifier: ^5.0.0 version: 5.0.0 + typescript: + specifier: ^5.0.0 + version: 5.6.3 ws: specifier: ^8.17.1 version: 8.18.0 @@ -40,6 +46,9 @@ packages: '@types/node@20.12.14': resolution: {integrity: sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==} + '@types/node@22.10.0': + resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==} + '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} @@ -59,9 +68,17 @@ packages: engines: {node: '>=14'} hasBin: true + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -86,9 +103,13 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@22.10.0': + dependencies: + undici-types: 6.20.0 + '@types/ws@8.5.13': dependencies: - '@types/node': 20.12.14 + '@types/node': 22.10.0 bun-types@1.1.34: dependencies: @@ -101,6 +122,10 @@ snapshots: prettier@3.3.3: {} + typescript@5.6.3: {} + undici-types@5.26.5: {} + undici-types@6.20.0: {} + ws@8.18.0: {} diff --git a/server.js b/server.js deleted file mode 100644 index e5653c8..0000000 --- a/server.js +++ /dev/null @@ -1,173 +0,0 @@ -import { WebSocketServer } from "ws"; -import { profanity } from "@2toad/profanity"; -import { commands } from "./commands.js"; -import * as accounts from "./accounts.js"; -import fs from "fs"; -import User from "./user.js"; -import handleJsonMessage from './jsondata.js' - -export default class Server { - sendInChannel(msg, channel, server=this) { - // console.log('this is a', this) - for (const userID in server.users) { - const user = server.users[userID]; - if (user.channel == channel) user.socket.send(msg); - } - } - - format(txt) { - txt = String(txt); - txt = txt.replaceAll("$(serverName)$", this.config.name); - txt = txt.replaceAll("$(userCount)$", Object.keys(this.users).length); - for (const configName in this.config) { - if (Object.prototype.hasOwnProperty.call(this.config, configName)) { - const configValue = this.config[configName]; - if(typeof configValue != 'string' && typeof configValue != 'number') continue; - txt = txt.replaceAll(`$(${configName})$`, configValue); - } - } - return txt; - } - - updateUsers() { - Object.keys(this.users).forEach((user) => { - if (user.subscribedToUsers) { - user.socket.send( - `:json.sub:${JSON.stringify( - Object.values(this.users).map((usr) => { - return { - username: usr.username, - nickname: usr.nickname, - t: usr.t, - channel: user.channel, - displayName: user.name(), - }; - }) - )}` - ); - } - }); - } - - /** - * @typedef ServerConfig - * @property {Number} port - * @property {String} name - * @property {String} motd - * @property {String} fullMessage - * @property {Number} max - * - * @typedef Config - * @property {ServerConfig} server - */ - - /** - * A wsChat server - * @param {{ - * name: String, - * motd: String, - * max: Number, - * owner: String, - * saveIP: Boolean, - * requireLogin: Boolean, - * profanity: Boolean, - * profanityRemoveWords: String[], - * profanityAddWords: String[], - * fullMessage: String, - * annonFormat: String, - * port: Number, - * channels: String[], - * annonChannels: String[] - * }} config - */ - constructor (config) { - this.config = config; - this.channels = this.config.channels; - this.annonChannels = this.config.annonChannels; - this.users = [] - this.accounts = accounts; - this.ws = new WebSocketServer({ - port: this.config.port, - }); - - if (this.config.profanityRemoveWords) profanity.removeWords(this.config.profanityRemoveWords); - if (this.config.profanityAddWords) profanity.addWords(this.config.profanityAddWords); - - let server = this - - this.ws.on("connection", (socket, request) => { - try { - if (server.config.max && Object.keys(server.users).length >= server.config.max) { - socket.send(server.format(server.config.fullMessage ?? "Sorry, but the server is full right now, come back later")); - socket.close(1001, "Server full"); - return; - } - const user = new User(request, socket, server) - server.users[user.id] = user - let ipBanList = JSON.parse(String(fs.readFileSync("db/bannedIps.json"))); - if (ipBanList[user.ip]) { - socket.send("Your IP is banned for " + ipBanList[user.ip]); - socket.close(1002, "Banned"); - return; - } - socket.send(server.format(server.config.motd)); - console.info(`${user.name()}[${user.id}] joined the server!`); - server.sendInChannel(`${user.name()} joined.`, server.users[user.id].channel); - server.updateUsers(); - socket.on("close", function (code, reason) { - server.sendInChannel(`${user.name()} left.`, server.users[user.id].channel); - server.updateUsers(); - delete server.users[user.id]; - }); - socket.on("message", function (rawData) { - if (rawData.toString().startsWith("/")) { - let args = String(rawData).replace("/", "").split(" "); - let command = args.shift(); - let commandObj = Object.values(commands).find((cmd) => cmd.name == command || cmd.aliases.includes(command)); - console.log(`${user.name()} used /${command}`); - if (!commandObj) return socket.send(`Error: Command "${command}" not found!`); - try { - commandObj.command.call(server, { - user, - command, - args, - sendInChannel: function (msg, channel) { - console.log(msg, channel) - server.sendInChannel(msg, channel, server); - }, - server: server, - commands, - }); - } catch (error) { - console.error(error); - user.socket.send(`Unexpected error ocurred while running the command`); - } - return; - } - if (rawData.toString().startsWith(":client")) { - let client = String(rawData).replace(":client", ""); - if (!client) return socket.send("Error: client info missing!"); - if (client.length < 2) return socket.send("Error: client info too short!"); - if (client.length >= 100) return socket.send("Error: client info too long!"); - user.client = client; - return; - } - if(handleJsonMessage(server, rawData, user)) return; - if (server.config.requireLogin && user.guest && !server.annonChannels.includes(user.channel)) return socket.send("This server requires you to log in, use /login to log in or /register to make an account."); - profanity.options.grawlixChar = "*"; - if (!server.config.profanity) rawData = profanity.censor(String(rawData)); - if (rawData.length < 1) return socket.send("Error: message too short!"); - if (rawData.length >= 2000) return socket.send("Error: message too long!"); - server.sendInChannel(`${user.admin ? '[ADMIN] ' : ''}<${user.name()}${user.guest ? " (guest)" : ""}> ${rawData}`, user.channel); - console.log(`(#${user.channel}) <${user.name()}> ${rawData}`); - }); - } catch (error) { - socket.send(`ERROR ${error}`); - socket.close() - } - }); - this.ws.on("listening", () => { - console.info("Server started!"); - }); - } -} \ No newline at end of file diff --git a/server.ts b/server.ts new file mode 100644 index 0000000..f47abb2 --- /dev/null +++ b/server.ts @@ -0,0 +1,187 @@ +import { WebSocketServer } from "ws"; +import { profanity } from "@2toad/profanity"; +import { commands } from "./commands.js"; +import * as accounts from "./accounts.js"; +import fs from "node:fs"; +import User from "./user.js"; +import handleJsonMessage from './jsondata.js' +import { EventEmitter } from "node:events"; + +class Events { // JSON events for clients + +} + +type ServerConfig = { + port: number + name: string + motd: string + fullMessage: string + max: number +} + +type AccountsConfig = { + owner: string + saveIP: boolean + requireLogin: boolean + annonFormat: boolean +} + +type ProfanityConfig = { + filter: boolean + removeWords: string + addWords: string +} + +type ChannelsConfig = { + channels: string +} + +type Config = { + server: ServerConfig + accounts: AccountsConfig + channels: ChannelsConfig + profanity: ProfanityConfig +} + +export default class Server { + config: Config; + channels: string[]; + users: Map = new Map(); + accounts = accounts; + ws: WebSocketServer&EventEmitter; + /** + * A wsChat server + */ + constructor (config: Config) { + this.config = config; + this.channels = this.config.channels.channels.split(/, */g); + // this.annonChannels = this.config.annonChannels.split(/, */g); + this.ws = new WebSocketServer({ + port: this.config.server.port, + }); + + if (this.config.profanity.removeWords) profanity.removeWords(this.config.profanity.removeWords.split(/, */g)); + if (this.config.profanity.addWords) profanity.addWords(this.config.profanity.addWords.split(/, */g)); + + let server = this; + + if (!fs.existsSync("db/bannedIps.json")) fs.writeFileSync("db/bannedIps.json", "{}"); + + this.ws.on("connection", (socket: WebSocket, request) => { + try { + if (server.config.server.max && Object.keys(server.users).length >= server.config.server.max) { + socket.send(server.format(server.config.server.fullMessage ?? "Sorry, but the server is full right now, come back later")); + socket.close(1001, "Server full"); + return; + } + const user: User = new User(request, socket, server) + server.users[user.id] = user + let ipBanList = JSON.parse(String(fs.readFileSync("db/bannedIps.json"))); + if (ipBanList[user.ip]) { + socket.send("Your IP is banned for " + ipBanList[user.ip]); + socket.close(1002, "Banned"); + return; + } + socket.send(server.format(server.config.server.motd)); + console.info(`${user.name()}[${user.id}] joined the server!`); + server.sendInChannel(`${user.name()} joined.`, server.users[user.id].channel); + server.updateUsers(); + socket.on("close", function (code, reason) { + server.sendInChannel(`${user.name()} left.`, server.users[user.id].channel); + server.updateUsers(); + delete server.users[user.id]; + }); + socket.on("message", function (rawData) { + if (rawData.toString().startsWith("/")) { + let args = String(rawData).replace("/", "").split(" "); + let command = args.shift(); + let commandObj = Object.values(commands).find((cmd) => cmd.name == command || cmd.aliases.includes(command)); + console.log(`${user.name()} used /${command}`); + if (!commandObj) return socket.send(`Error: Command "${command}" not found!`); + try { + commandObj.command.call(server, { + user, + command, + args, + sendInChannel: function (msg, channel) { + console.log(msg, channel) + server.sendInChannel(msg, channel, server); + }, + server: server, + commands, + }); + } catch (error) { + console.error(error); + user.socket.send(`Unexpected error ocurred while running the command`); + } + return; + } + if (rawData.toString().startsWith(":client")) { + let client = String(rawData).replace(":client", ""); + if (!client) return socket.send("Error: client info missing!"); + if (client.length < 2) return socket.send("Error: client info too short!"); + if (client.length >= 100) return socket.send("Error: client info too long!"); + user.client = client; + return; + } + if(handleJsonMessage(server, rawData, user)) return; + if (server.config.accounts.requireLogin && user.guest && !server.annonChannels.includes(user.channel)) return socket.send("This server requires you to log in, use /login to log in or /register to make an account."); + profanity.options.grawlixChar = "*"; + if (server.config.profanity.filter) rawData = profanity.censor(String(rawData)); + if (rawData.length < 1) return socket.send("Error: message too short!"); + if (rawData.length >= 2000) return socket.send("Error: message too long!"); + server.sendInChannel(`${user.admin ? '[ADMIN] ' : ''}<${user.name()}${user.guest ? " (guest)" : ""}> ${rawData}`, user.channel); + console.log(`(#${user.channel}) <${user.name()}> ${rawData}`); + }); + } catch (error) { + socket.send(`ERROR ${error}`); + socket.close() + } + }); + this.ws.on("listening", () => { + console.info("Server started!"); + }); + } + + sendInChannel(msg, channel, server=this) { + // console.log('this is a', this) + for (const userID in server.users) { + const user = server.users[userID]; + if (user.channel == channel) user.socket.send(msg); + } + } + + format(txt) { + txt = String(txt); + txt = txt.replaceAll("$(serverName)$", this.config.server.name); + txt = txt.replaceAll("$(userCount)$", Object.keys(this.users).length); + for (const configName in this.config.server) { + if (Object.prototype.hasOwnProperty.call(this.config.server, configName)) { + const configValue = this.config.server[configName]; + if(typeof configValue != 'string' && typeof configValue != 'number') continue; + txt = txt.replaceAll(`$(${configName})$`, configValue); + } + } + return txt; + } + + updateUsers() { + Object.keys(this.users).forEach((user) => { + if (user.subscribedToUsers) { + user.socket.send( + `:json.sub:${JSON.stringify( + Object.values(this.users).map((usr) => { + return { + username: usr.username, + nickname: usr.nickname, + t: usr.t, + channel: user.channel, + displayName: user.name(), + }; + }) + )}` + ); + } + }); + } +} \ No newline at end of file diff --git a/user.js b/user.js index d3254f7..c801659 100644 --- a/user.js +++ b/user.js @@ -6,14 +6,14 @@ import cuid from "cuid"; export default class User { /** * the user class - * @param {import("http").IncomingMessage} request request + * @param {import("node:http").IncomingMessage} request request * @param {WebSocket} socket socket - * @param {import("./server.js").Server} server the server + * @param {import("./server.ts").Server} server the server */ constructor (request, socket, server) { this.id = cuid(); - let anonID = getRandomInt(0, 99999); - let annonNum = "0".repeat(5 - anonID.toString().length) + anonID.toString() + const anonID = getRandomInt(0, 99999); + const annonNum = "0".repeat(5 - anonID.toString().length) + anonID.toString() this.username = server.config.annonFormat ? server.config.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum; this.nickname = server.config.annonFormat ? server.config.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum; this.guest = true; -- cgit 1.4.1-2-gfad0