diff options
-rw-r--r-- | .vscode/settings.json | 3 | ||||
-rw-r--r-- | accounts.ts (renamed from accounts.js) | 38 | ||||
-rw-r--r-- | commands.ts (renamed from commands.js) | 42 | ||||
-rw-r--r-- | config.ini | 6 | ||||
-rw-r--r-- | jsondata.js | 2 | ||||
-rw-r--r-- | newindex.js | 5 | ||||
-rw-r--r-- | oldindex.js (renamed from index.js) | 4 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | pnpm-lock.yaml | 30 | ||||
-rw-r--r-- | ranks.js | 10 | ||||
-rw-r--r-- | server.js | 173 | ||||
-rw-r--r-- | server.ts | 208 | ||||
-rw-r--r-- | timestamp.ts | 10 | ||||
-rw-r--r-- | user.js | 33 | ||||
-rw-r--r-- | user.ts | 42 |
15 files changed, 351 insertions, 257 deletions
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/accounts.js b/accounts.ts index fe9d164..1dc96ac 100644 --- a/accounts.js +++ b/accounts.ts @@ -1,6 +1,8 @@ -import { createHash } from "crypto"; -import cuid from "cuid"; -import fs from "fs"; +import { createHash } from "node:crypto"; +import cuid2 from "@paralleldrive/cuid2"; +import fs from "node:fs"; + +const cuid = cuid2.init() if (!fs.existsSync("db")) fs.mkdirSync("db"); if (!fs.existsSync("db/users.json")) fs.writeFileSync("db/users.json", "{}"); @@ -19,26 +21,22 @@ function syncDB() { * @param {String} username Username to check * @returns {Boolean} */ -export function checkAccount(username) { +export function checkAccount(username: string) { return db[username] != undefined; } /** * Does a loose check on if the account exists - * @param {String} username Username to check - * @returns {Boolean} */ -export function checkAccountLoose(username) { - return Object.keys(db).find(n => n.toLowerCase() == username.toLowerCase()); +export function checkAccountLoose(username: string): boolean { + return Object.keys(db).find(n => n.toLowerCase() == username.toLowerCase()) ? true : false; } /** * Create an account - * @param {String} username The username - * @param {String} password The password */ -export function createAccount(username, password, admin = false) { - let hashedPassword = createHash("sha256").update(password).digest("hex"); +export function createAccount(username: string, password: string, admin: boolean = false) { + const hashedPassword = createHash("sha256").update(password).digest("hex"); db[username] = { admin, id: cuid(), @@ -51,10 +49,8 @@ export function createAccount(username, password, admin = false) { /** * Log IP address (for IP bans) - * @param {String} username Username - * @param {String} ip IP address */ -export function logIP(username, ip) { +export function logIP(username: string, ip: string) { if (!db[username].ips) db[username].ips = []; if (!db[username].ips.includes(ip)) db[username].ips.push(ip); syncDB(); @@ -62,22 +58,20 @@ export function logIP(username, ip) { /** * Check if password is correct - * @param {String} username The username - * @param {String} password The password * @returns {Boolean} */ -export function checkPassword(username, password) { - let hashedPassword = createHash("sha256").update(password).digest("hex"); +export function checkPassword(username: string, password: string) { + const hashedPassword = createHash("sha256").update(password).digest("hex"); return db[username]?.password === hashedPassword; } /** * Get account data * @param {String} username The username - * @returns {Object} + * @returns {User} */ -export function getAccountData(username) { - let returnData = JSON.parse(JSON.stringify(db[username])); +export function getAccountData(username: string) { + const returnData = JSON.parse(JSON.stringify(db[username])); returnData.password = null; return returnData; } diff --git a/commands.js b/commands.ts index 5a8200f..095c534 100644 --- a/commands.js +++ b/commands.ts @@ -1,6 +1,18 @@ -import fs from "fs"; +import fs from "node:fs"; +import type User from "./user.ts"; +import type Server from "./server.ts"; -export const commands = { +// NOTE - temporary, will make class later +export type Command = { + name: string, + usage?: string, + description: string, + aliases: string[], + command: ({ user, server, args, sendInChannel, commands }: + { user: User, server: Server, args: string[], sendInChannel: (msg: string, channel: string, server?: Server) => void, commands: {[key: string]: Command} }) => void +} + +export const commands: {[key: string]: Command} = { join: { name: "join", usage: "/join <channel>", @@ -46,8 +58,8 @@ export const commands = { usage: "/about", description: "Shows info about wsChat", aliases: [], - command({ user, args, sendInChannel }) { - let packagejson = JSON.parse(fs.readFileSync("package.json").toString()) + command({ user }) { + const packagejson = JSON.parse(fs.readFileSync("package.json").toString()) user.socket.send(`wsChat v${packagejson.version}\nGithub: https://github.com/WlodekM/wsChat`); }, }, @@ -58,9 +70,9 @@ export const commands = { aliases: [], command({ user, server, args }) { if (args.length < 1) return user.socket.send("Please provide username"); - if (!Object.values(server.users).find((usr) => usr.username == args[0])) return user.socket.send("User not found"); - let userFound = Object.values(server.users).find((usr) => usr.username == args[0]); - userFound.id = Object.keys(server.users).find((usr) => server.users[usr].username == args[0]); + if (![...server.users.entries()].find(([_, usr]) => usr.username == args[0])) return user.socket.send("User not found"); + const userFound = Object.values(server.users).find((usr) => usr.username == args[0]); + userFound.id = Object.keys(server.users).find((usr) => (server.users.get(usr) as User).username == args[0]); user.socket.send(`${userFound.username}\nClient: ${userFound.client ?? "<Unknown>"}\nID: ${userFound.id}`); }, }, @@ -83,7 +95,7 @@ export const commands = { usage: "/?", description: "Shows all commands", aliases: ["?"], - command({ user, args, commands }) { + command({ user }) { user.socket.send( `Commands available:\n${Object.values(commands) .map((cmd) => `* /${cmd.name} (Aliases: ${cmd.aliases.join(", ") || "<None>"})`) @@ -100,7 +112,7 @@ export const commands = { if (args.length < 2) return user.socket.send(`Usage: /login <username> <password>`); if (!server.accounts.checkAccount(args[0])) return user.socket.send(`Account "${args[0]}" not found!`); if (!server.accounts.checkPassword(args[0], args[1])) return user.socket.send(`Password incorrect.`); - let ipBanList = JSON.parse(String(fs.readFileSync("db/bannedIps.json"))); + const ipBanList = JSON.parse(String(fs.readFileSync("db/bannedIps.json"))); if (server.config.saveIP) server.accounts.logIP(args[0], user.ip); if (ipBanList['account:'+args[0]] != undefined) { ipBanList[user.ip] = ipBanList['account:'+args[0]]; @@ -144,11 +156,11 @@ export const commands = { usage: "/pm <user> [message]", description: "Send a private message to a user", aliases: [], - command({ server, args, user, sendInChannel }) { + command({ server, args, user }) { if (args.length < 1) return user.socket.send("Please provide username"); - if (!Object.values(server.users).find((usr) => usr.username == args[0])) return user.socket.send("User not found"); - let userFound = Object.values(server.users).find((usr) => usr.username == args[0]); - userFound.id = Object.keys(server.users).find((usr) => server.users[usr].username == args[0]); + // yes i am that lazy + const userFound = ([...server.users.entries()].find(([_, usr]) => usr.username == args[0]) ?? [undefined, undefined])[1]; + if (!userFound) return user.socket.send("User not found"); args.shift(); userFound.socket.send(`${user.username} -> You : ${args.join(" ")}`); user.socket.send(`You -> ${userFound.username} : ${args.join(" ")}`); @@ -156,11 +168,11 @@ export const commands = { }, }; -export function register(cmd, data) { +export function register(cmd: string, data: Command) { commands[cmd] = data; } -let commandFiles = fs +const commandFiles = fs .readdirSync("commands") .filter((filename) => filename.endsWith(".js")) .map((file) => file.replace(/\.js$/gim, "")); 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/jsondata.js b/jsondata.js index e43af86..4465885 100644 --- a/jsondata.js +++ b/jsondata.js @@ -1,7 +1,7 @@ /** * handles json data * @param {Buffer} rawData the raw message data - * @param {import("./user.js").default} user the user + * @param {import("./user.ts").default} user the user * @returns {Boolean} */ export default function handleMessage(server, rawData, user) { diff --git a/newindex.js b/newindex.js index 536264f..3e07868 100644 --- a/newindex.js +++ b/newindex.js @@ -1,4 +1,5 @@ -import Server from './server.js'; +import Server from './server.ts'; import ini from "ini" +import fs from "node:fs" -const server = new Server(ini.parse(String(fs.readFileSync("config.ini")))) +new Server(ini.parse(String(fs.readFileSync("config.ini")))) diff --git a/index.js b/oldindex.js index 400cec5..8acaa19 100644 --- a/index.js +++ b/oldindex.js @@ -1,8 +1,8 @@ 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 { commands } from "./commands.ts"; +import * as accounts from "./accounts.ts"; import cuid2 from "@paralleldrive/cuid2"; import fs from "fs"; diff --git a/package.json b/package.json index c908579..97eef83 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ }, "dependencies": { "@2toad/profanity": "^2.2.0", + "@types/node": "^22.10.0", + "@types/ws": "^8.5.13", "@paralleldrive/cuid2": "^2.2.2", "ini": "^5.0.0", "ws": "^8.17.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a8fb47..6b1dc23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,21 @@ importers: '@2toad/profanity': specifier: ^2.2.0 version: 2.5.0 + '@types/node': + specifier: ^22.10.0 + version: 22.10.0 + '@types/ws': + specifier: ^8.5.13 + version: 8.5.13 '@paralleldrive/cuid2': specifier: ^2.2.2 version: 2.2.2 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 @@ -47,6 +56,9 @@ packages: '@types/[email protected]': resolution: {integrity: sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==} + '@types/[email protected]': + resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==} + '@types/[email protected]': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} @@ -62,9 +74,17 @@ packages: engines: {node: '>=14'} hasBin: true + [email protected]: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + [email protected]: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + [email protected]: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + [email protected]: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -95,9 +115,13 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/[email protected]': + dependencies: + undici-types: 6.20.0 + '@types/[email protected]': dependencies: - '@types/node': 20.12.14 + '@types/node': 22.10.0 [email protected]: dependencies: @@ -108,6 +132,10 @@ snapshots: [email protected]: {} + [email protected]: {} + [email protected]: {} + [email protected]: {} + [email protected]: {} diff --git a/ranks.js b/ranks.js index 6f7d9b4..78cab46 100644 --- a/ranks.js +++ b/ranks.js @@ -1,7 +1,7 @@ -import fs from 'fs'; -import path from 'path'; -import * as accounts from './accounts.js' -import { commands } from "./commands.js"; +import fs from 'node:fs'; +import path from 'node:path'; +import * as accounts from './accounts.ts' +import { commands } from "./commands.ts"; export function getRankData(name) { if (!/^[^\/\\]*$/g.exec(name)) return null; @@ -12,7 +12,7 @@ export function getRankData(name) { export function canUserDoCommand(command, username, guest=false) { let permissionLevel = 0; if (!guest) { - let accountData = accounts.getAccountData(username); + const accountData = accounts.getAccountData(username); if (getRankData(accountData?.admin ? 'admin' : accountData?.rank)) permissionLevel = getRankData(accountData?.admin ? 'admin' : accountData?.rank).level; } // Banned users can be given a rank with negative permissions so that no commands can be ran 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<users>:${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 <username> <password> to log in or /register <username> <password> 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..f3bc077 --- /dev/null +++ b/server.ts @@ -0,0 +1,208 @@ +import { WebSocketServer, WebSocket } from "ws"; +import { profanity } from "@2toad/profanity"; +import { commands, type Command } from "./commands.ts"; +import * as accounts from "./accounts.ts"; +import fs from "node:fs"; +import User from "./user.ts"; +import handleJsonMessage from './jsondata.js' +import { EventEmitter } from "node:events"; + +// TODO - actually make this +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: string +} + +type ProfanityConfig = { + filter: boolean + removeWords: string + addWords: string +} + +type ChannelsConfig = { + channels: string +} + +type ChannelConfig = { + slowmode?: number + requireLogin?: boolean + profanity?: boolean +} + +type addPrefixToObject<T, P extends string> = { + [K in keyof T as K extends string ? `${P}${K}` : never]: T[K] +} + +type Config = { + server: ServerConfig + accounts: AccountsConfig + channels: ChannelsConfig + profanity: ProfanityConfig + [key: string]: ServerConfig | AccountsConfig | ChannelsConfig | ProfanityConfig | ChannelConfig; +} + +export default class Server { + config: Config; + channels: string[]; + users: Map<string, User> = 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)); + + // deno-lint-ignore no-this-alias + const 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.set(user.id, user); + type IPbanList = { + [key: string]: string + } + const ipBanList: IPbanList = JSON.parse(String(fs.readFileSync("db/bannedIps.json"))); + if (user.ip && ipBanList[user.ip[0]]) { + socket.send("Your IP is banned for " + ipBanList[user.ip[0]]); + 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.`, user.channel); + server.updateUsers(); + socket.on("close", function () { + server.sendInChannel(`${user.name()} left.`, user.channel); + server.updateUsers(); + server.users.delete(user.id); + }); + socket.on("message", function (rawData) { + let data: string = rawData.toString() + if (data.toString().startsWith("/")) { + const args: string[] = String(data).replace("/", "").split(" "); + const command: string = args.shift() as string; + // TODO: make command class + const commandObj: Command = Object.values(commands).find((cmd) => cmd.name == command || cmd.aliases.includes(command)) as 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: string, channel: string) { + 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 (data.toString().startsWith(":client")) { + const client = String(data).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, data, user)) return; + const thisChannelConfig: ChannelConfig | undefined = server.config['channel-'+user.channel] as ChannelConfig; + if (server.config.accounts.requireLogin && user.guest && thisChannelConfig?.requireLogin != undefined && !thisChannelConfig.requireLogin) + return socket.send("This server requires you to log in, use /login <username> <password> to log in or /register <username> <password> to make an account."); + profanity.options.grawlixChar = "*"; + if (server.config.profanity.filter) data = profanity.censor(String(data)); + if (data.length < 1) return socket.send("Error: message too short!"); + if (data.length >= 2000) return socket.send("Error: message too long!"); + server.sendInChannel(`${user.admin ? '[ADMIN] ' : ''}<${user.name()}${user.guest ? " (guest)" : ""}> ${data}`, user.channel); + console.log(`(#${user.channel}) <${user.name()}> ${data}`); + }); + } catch (error) { + socket.send(`ERROR ${error}`); + socket.close() + } + }); + this.ws.on("listening", () => { + console.info("Server started!"); + }); + } + + sendInChannel(msg: string, channel: string, server=this) { + // console.log('this is a', this) + for (const [userID] of server.users.entries()) { + const user = server.users.get(userID) as User; + if (user.channel == channel) user.socket.send(msg); + } + } + + format(txt: string) { + txt = txt.replaceAll("$(serverName)$", this.config.server.name); + txt = txt.replaceAll("$(userCount)$", [...this.users.entries()].length.toString()); + // 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; + } + + //TODO - finish event system, rewrite this + // updateUsers() { + // Object.keys(this.users).forEach((user) => { + // if (user.subscribedToUsers) { + // user.socket.send( + // `:json.sub<users>:${JSON.stringify( + // Object.values(this.users).map((usr) => { + // return { + // username: usr.username, + // nickname: usr.nickname, + // t: usr.t, + // channel: user.channel, + // displayName: user.name(), + // }; + // }) + // )}` + // ); + // } + // }); + // } + updateUsers() {} +} \ No newline at end of file diff --git a/timestamp.ts b/timestamp.ts new file mode 100644 index 0000000..cfa9a05 --- /dev/null +++ b/timestamp.ts @@ -0,0 +1,10 @@ +export default class Timespamp { + js: number; + unix: number; + str: string; + constructor (date: Date) { + this.js = Number(date) + this.unix = Math.floor(date.getTime() / 1000) + this.str = String(date) + } +} \ No newline at end of file diff --git a/user.js b/user.js deleted file mode 100644 index d3254f7..0000000 --- a/user.js +++ /dev/null @@ -1,33 +0,0 @@ -// because why not make *more files - -import { getRandomInt } from "./lib.js"; -import cuid from "cuid"; - -export default class User { - /** - * the user class - * @param {import("http").IncomingMessage} request request - * @param {WebSocket} socket socket - * @param {import("./server.js").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() - 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; - this.socket = socket; - this.joinReq = request; - this.ip = request.headers["x-forwarded-for"] || request.socket.remoteAddress; - this.t = { - js: Number(new Date()), - unix: Math.floor(new Date().getTime() / 1000), - str: String(new Date()), - }; - this.channel = "home"; - this.name = function () { - return this.nickname != "" ? this.nickname : this.username; - }; - } -} \ No newline at end of file diff --git a/user.ts b/user.ts new file mode 100644 index 0000000..910e8a4 --- /dev/null +++ b/user.ts @@ -0,0 +1,42 @@ +// because why not make *more files + +import { getRandomInt } from "./lib.js"; +import cuid2 from "@paralleldrive/cuid2"; +import type { WebSocket } from 'ws' +import type { IncomingMessage } from "node:http"; +import type Server from "./server.ts"; +import Timespamp from "./timestamp.ts"; + +const cuid = cuid2.init() + +export default class User { + id: string = cuid(); + username: string; + nickname: string; + guest: boolean; + socket: WebSocket; + joinReq: IncomingMessage; + ip: string; + t: Timespamp; + channel: string; + client?: string = undefined; + admin: boolean = false; + /** + * the user class + */ + constructor (request: IncomingMessage, socket: WebSocket, server: Server) { + const anonID = getRandomInt(0, 99999); + const annonNum = "0".repeat(5 - anonID.toString().length) + anonID.toString() + this.username = server.config.accounts.annonFormat ? server.config.accounts.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum; + this.nickname = server.config.accounts.annonFormat ? server.config.accounts.annonFormat.replace('[num]', annonNum) : 'Anonymous' + annonNum; + this.guest = true; + this.socket = socket; + this.joinReq = request; + this.ip = (request.headers["x-forwarded-for"] ?? [null])[0] ?? request.socket.remoteAddress ?? ''; + this.t = new Timespamp(new Date()); + this.channel = "home"; + } + name () { + return this.nickname != "" ? this.nickname : this.username; + } +} \ No newline at end of file |