summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json3
-rw-r--r--accounts.ts (renamed from accounts.js)38
-rw-r--r--commands.ts (renamed from commands.js)42
-rw-r--r--config.ini6
-rw-r--r--jsondata.js2
-rw-r--r--newindex.js5
-rw-r--r--oldindex.js (renamed from index.js)4
-rw-r--r--package.json2
-rw-r--r--pnpm-lock.yaml30
-rw-r--r--ranks.js10
-rw-r--r--server.js173
-rw-r--r--server.ts208
-rw-r--r--timestamp.ts10
-rw-r--r--user.js33
-rw-r--r--user.ts42
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