diff options
author | WlodekM <[email protected]> | 2024-08-09 20:46:04 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-08-09 20:46:04 +0300 |
commit | 80d45efdf7fe6204de5378b2e5536d241d9996df (patch) | |
tree | d602a536ecd4ed6e6f4b86bf8c68499c873297e3 | |
parent | 87cb115ac9d37199c9da8487a3810c98544ac240 (diff) |
v1.3.0 (also use prettier (bad idea (why doesnt it have an undo command) ) )
-rw-r--r-- | .prettierrc | 8 | ||||
-rw-r--r-- | README.md | 16 | ||||
-rw-r--r-- | accounts.js | 71 | ||||
-rw-r--r-- | client.html | 97 | ||||
-rw-r--r-- | commands.js | 207 | ||||
-rw-r--r-- | commands/motd.js | 8 | ||||
-rw-r--r-- | config.json | 9 | ||||
-rw-r--r-- | index.js | 210 | ||||
-rw-r--r-- | jsconfig.json | 44 | ||||
-rw-r--r-- | lib.js | 2 | ||||
-rw-r--r-- | package-lock.json | 120 | ||||
-rw-r--r-- | package.json | 45 |
12 files changed, 594 insertions, 243 deletions
diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1491ea5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": false, + "printWidth": 9999, + "endOfLine": "lf" +} diff --git a/README.md b/README.md index e08f31f..87e2ecc 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ node index.js ## TODO -- ~~Make commands be in an object instead of a switch statement~~ -- ~~/help command~~ -- ~~/about command~~ -- ~~Config (JSON)~~ -- ~~MOTD~~ -- ~~Custom commands (plugins)~~ -- ~~Auth (optional)~~ +- ~~Make commands be in an object instead of a switch statement~~ +- ~~/help command~~ +- ~~/about command~~ +- ~~Config (JSON)~~ +- ~~MOTD~~ +- ~~Custom commands (plugins)~~ +- ~~Auth (optional)~~ ## IN PROGRESS -- JSON support (for clients and stuff) +- JSON support (for clients and stuff) diff --git a/accounts.js b/accounts.js index f45ab6e..27de609 100644 --- a/accounts.js +++ b/accounts.js @@ -1,31 +1,74 @@ -import { createHash } from "crypto" -import cuid from 'cuid'; -import fs from "fs" +import { createHash } from "crypto"; +import cuid from "cuid"; +import fs from "fs"; -if(!fs.existsSync("db")) fs.mkdirSync("db"); -if(!fs.existsSync("db/users.json")) fs.writeFileSync("db/users.json", "{}"); +if (!fs.existsSync("db")) fs.mkdirSync("db"); +if (!fs.existsSync("db/users.json")) fs.writeFileSync("db/users.json", "{}"); -const db = JSON.parse(fs.readFileSync("db/users.json").toString()) +const db = JSON.parse(fs.readFileSync("db/users.json").toString()); +/** + * Syncs DB to file + */ function syncDB() { - fs.writeFileSync("db/users.json", JSON.stringify(db)) + fs.writeFileSync("db/users.json", JSON.stringify(db)); } +/** + * Checks if account exists + * @param {String} username Username to check + * @returns {Boolean} + */ export function checkAccount(username) { - return db[username] != undefined + return db[username] != undefined; } -export function createAccount(username, password) { - let hashedPassword = createHash('sha256').update(password).digest('hex'); +/** + * 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"); db[username] = { + admin, id: cuid(), password: hashedPassword, - t: Number(new Date()) / 1000 + ips: [], + t: Number(new Date()) / 1000, }; syncDB(); } +/** + * Log IP address (for IP bans) + * @param {String} username Username + * @param {String} ip IP address + */ +export function logIP(username, ip) { + if (!db[username].ips) db[username].ips = []; + if (!db[username].ips.includes(ip)) db[username].ips.push(ip); + syncDB(); +} + +/** + * 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'); - return db[username]?.password === hashedPassword -} \ No newline at end of file + let hashedPassword = createHash("sha256").update(password).digest("hex"); + return db[username]?.password === hashedPassword; +} + +/** + * Get account data + * @param {String} username The username + * @returns {Object} + */ +export function getAccountData(username) { + let returnData = JSON.parse(JSON.stringify(db[username])); + returnData.password = null; + return returnData; +} diff --git a/client.html b/client.html index db7a4d3..ed5f9d4 100644 --- a/client.html +++ b/client.html @@ -1,34 +1,63 @@ <div class="container"> - <div class="title">wsChat</div> - <div class="logs"> + <div class="sidebar"> + Channels + <ul id="channels"></ul> </div> - <div class="msg"> - <form id="msg"> - <input type="text" id="msg-content" autocomplete="off"> - <input type="submit"> - </form> + <div class="flex-col"> + <div class="title">wsChat</div> + <div class="logs"></div> + <div class="msg"> + <form id="msg"> + <input type="text" id="msg-content" autocomplete="off" /> + <input type="submit" /> + </form> + </div> </div> </div> <script> const logs = document.querySelector(".logs"); const msgForm = document.querySelector("#msg"); - const server = prompt("Enter server URL", "ws://127.0.0.1:9933") + const server = prompt("Enter server URL", "ws://127.0.0.1:9933"); const ws = new WebSocket(server); // CHANGE ME - ws.onopen = openEv => { - if(logs.innerText.length != 0) logs.innerText += "\n"; + let gettingChannels = false; + let leavingAChannel = false; + ws.onopen = (openEv) => { + if (logs.innerText.length != 0) logs.innerText += "\n"; logs.innerText += `Connected to wsChat server at ${ws.url}`; document.querySelector(".title").innerHTML += ` - ${new URL(ws.url).host}`; - } - ws.onmessage = msg => {; + gettingChannels = true; + ws.send(":jsonGet channels"); + }; + ws.onmessage = (msg) => { console.log(msg); - if(logs.innerText.length != 0) logs.innerText += "\n"; + if (msg.data.startsWith(":json.channels>")) { + gettingChannels = false; + let channels = JSON.parse(String(msg.data).replace(":json.channels>", "")); + console.log(msg.data, "gh", channels); + channels.forEach((ch) => { + let elem = document.createElement("li"); + elem.innerText = `#${ch}`; + elem.addEventListener("click", function (ev) { + ws.send(`/join #${ch}`); + leavingAChannel = true; + }); + document.getElementById("channels").appendChild(elem); + }); + return; + } + if (leavingAChannel) { + logs.innerText = ""; + leavingAChannel = false; + return; + } + if (logs.innerText.length != 0) logs.innerText += "\n"; logs.innerText += msg.data; }; - ws.onclose = cls => { - if(logs.innerText.length != 0) logs.innerText += "\n"; - logs.innerText += `Connection closed with code ${cls.code}, reason: ${cls.reason}`; - } - msgForm.addEventListener('submit', ev => { + ws.onclose = (cls) => { + if (logs.innerText.length != 0) logs.innerText += "\n"; + logs.innerText += `Connection closed with code ${cls.code}, reason: ${cls.reason}. reload the page to reconnect`; + }; + msgForm.addEventListener("submit", (ev) => { ev.preventDefault(); const message = document.querySelector("#msg-content"); ws.send(message.value); @@ -39,31 +68,52 @@ * { box-sizing: border-box; } + body { margin: 0; width: 100%; min-height: 100vh; position: absolute; } + + .sidebar { + padding: 1em; + border-right: 2px red solid; + } + + .sidebar ul { + margin: 0; + padding: 0; + list-style: none; + } + .container { - position: relative; display: flex; - flex-direction: column; + position: relative; height: calc(100vh - 3em); border: 2px red solid; margin: 1.5em; } + + .flex-col { + flex-direction: column; + display: flex; + flex-grow: 1; + } + .logs { flex-grow: 1; padding: 1em; } + .title { border-bottom: 2px red solid; padding: 1em; - font-size: larger; - font-weight: bold; + font-size: larger; + font-weight: bold; text-align: center; } + #msg { border-top: 2px red solid; padding: 1em; @@ -72,7 +122,8 @@ height: 2em; box-sizing: content-box; } + #msg-content { flex-grow: 1; } -</style> \ No newline at end of file +</style> diff --git a/commands.js b/commands.js index 3fbd63e..456c6a7 100644 --- a/commands.js +++ b/commands.js @@ -1,108 +1,161 @@ -import fs from "fs" +import fs from "fs"; export const commands = { - 'join': { - name: 'join', + join: { + name: "join", + usage: "/join <channel>", + description: "Join a different channel", aliases: [], - command: function({user, server, args, sendInChannel}) { - if(args.length < 1) return user.socket.send("Error: You need to specify a channel (example: /join #home)."); - if(!args[0].startsWith("#")) return user.socket.send("Error: Channel not found, run /channels to see a list of channels."); - if(!server.channels.includes(args[0].replace("#", ""))) return user.socket.send("Error: Channel not found, run /channels to see a list of channels."); - sendInChannel(`${user.name()} left #${user.channel}.`, user.channel) + command({ user, server, args, sendInChannel }) { + if (args.length < 1) return user.socket.send("Error: You need to specify a channel (example: /join #home)."); + if (!args[0].startsWith("#")) return user.socket.send("Error: Channel not found, run /channels to see a list of channels."); + if (!server.channels.includes(args[0].replace("#", ""))) return user.socket.send("Error: Channel not found, run /channels to see a list of channels."); + sendInChannel(`${user.name()} left #${user.channel}.`, user.channel); user.channel = args[0].replace("#", ""); - console.info(`${user.name()} went to #${user.channel}`) - sendInChannel(`${user.name()} joined #${user.channel}!`, user.channel) - } + server.updateUsers(); + console.info(`${user.name()} went to #${user.channel}`); + sendInChannel(`${user.name()} joined #${user.channel}!`, user.channel); + }, }, - 'channels': { - name: 'channels', + channels: { + name: "channels", + usage: "/channels", + description: "Shows all channels", aliases: [], - command: function({user, server}) { - user.socket.send(`Channels:\n${server.channels.map(ch => ` * #${ch}`).join("\n")}`) - } + command({ user, server }) { + user.socket.send(`Channels:\n${server.channels.map((ch) => ` * #${ch}`).join("\n")}`); + }, }, - 'nick': { - name: 'nick', - aliases: ['nickname', 'name'], - command: function({user, server, args, sendInChannel}) { - if(args.length < 1) return user.socket.send("Error: You need to specify a nick (example: /nick WlodekM)."); - if(args[0].length < 3 ) return user.socket.send("Error: Nick too short."); - if(args[0].length > 20) return user.socket.send("Error: Nick too long."); - if(Object.values(server.users).find(usr => usr.username == args[0])) return user.socket.send("Error: Nick already used."); - sendInChannel(`${user.name()} changed their nick to ${args[0]}!`, user.channel) + nick: { + name: "nick", + usage: "/nick <nickname>", + description: "Change your nickname", + aliases: ["nickname", "name"], + command({ user, server, args, sendInChannel }) { + if (args.length < 1) return user.socket.send("Error: You need to specify a nick (example: /nick WlodekM)."); + if (args[0].length < 3) return user.socket.send("Error: Nick too short."); + if (args[0].length > 20) return user.socket.send("Error: Nick too long."); + if (Object.values(server.users).find((usr) => usr.username == args[0])) return user.socket.send("Error: Nick already used."); + sendInChannel(`${user.name()} changed their nick to ${args[0]}!`, user.channel); + server.updateUsers(); user.nickname = args[0]; - } + }, }, - 'about': { - name: 'about', + about: { + name: "about", + usage: "/about", + description: "Shows info about wsChat", aliases: [], - command: function({user, args, sendInChannel}) { - user.socket.send(`wsChat v${JSON.parse(String(fs.readFileSync("package.json"))).version}\nGithub: https://github.com/WlodekM/wsChat`) - } + command({ user, args, sendInChannel }) { + user.socket.send(`wsChat v${JSON.parse(String(fs.readFileSync("package.json"))).version}\nGithub: https://github.com/WlodekM/wsChat`); + }, }, - 'whois': { - name: 'whois', + whois: { + name: "whois", + usage: "/whois <username>", + description: "Shows info about a person", aliases: [], - command: function({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]) - user.socket.send(`${userFound.username}\nClient: ${userFound.client ?? "<Unknown>"}\nID: ${userFound.id}`) - } + 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]); + user.socket.send(`${userFound.username}\nClient: ${userFound.client ?? "<Unknown>"}\nID: ${userFound.id}`); + }, }, - 'users': { - name: 'users', + users: { + name: "users", + usage: "/users", + description: "Shows all users online", aliases: [], - command: function({user, server, args}) { - user.socket.send(`Users${args[0] != "global" ? ` in ${user.channel}` : ""}:\n${Object.values(server.users).filter(usr => (usr.channel == user.channel) || args[0] == "global").map(usr => ` * ${usr.name()}`).join("\n")}`) - } + command({ user, server, args }) { + user.socket.send( + `Users${args[0] != "global" ? ` in ${user.channel}` : ""}:\n${Object.values(server.users) + .filter((usr) => usr.channel == user.channel || args[0] == "global") + .map((usr) => ` * ${usr.name()}`) + .join("\n")}` + ); + }, }, - 'help': { - name: 'help', - aliases: ['?'], - command: function({user, args, commands}) { - user.socket.send(`Commands available:\n${Object.values(commands).map(cmd => `* /${cmd.name} (Aliases: ${(cmd.aliases.join(", ")) || "<None>"})`).join("\n")}`) - } + help: { + name: "help", + usage: "/?", + description: "Shows all commands", + aliases: ["?"], + command({ user, args, commands }) { + user.socket.send( + `Commands available:\n${Object.values(commands) + .map((cmd) => `* /${cmd.name} (Aliases: ${cmd.aliases.join(", ") || "<None>"})`) + .join("\n")}` + ); + }, }, - 'login': { - name: 'login', - aliases: ['signin'], - command: function({server, args, user, sendInChannel}) { - 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.`); + login: { + name: "login", + usage: "/login <username> <password>", + description: "Log into an account", + aliases: ["signin"], + command({ server, args, user, sendInChannel }) { + 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.`); + if (server.config.saveIP) server.accounts.logIP(args[0], user.ip); + sendInChannel(`${user.name()} logged in as ${args[0]}!`, user.channel); user.username = args[0]; user.nickname = ""; user.guest = false; - sendInChannel(`${user.name()} logged in as ${args[0]}!`, user.channel) - } + user.admin = server.accounts.getAccountData(user.username).admin ?? false; + server.updateUsers(); + }, }, - 'register': { - name: 'register', - aliases: ['signup'], - command: function({server, args, user, sendInChannel}) { - if(args.length < 2) return user.socket.send(`Usage: /register <username> <password>`); - if(args[0].length < 3) return user.socket.send(`Username too short!`); - if(args[0].length > 20) return user.socket.send(`Username too long!`); - if(args[1].length < 6) return user.socket.send(`Password too short!`); - if(server.accounts.checkAccount(args[0])) return user.socket.send(`User with username "${args[0]}" already exists!`); + register: { + name: "register", + usage: "/register <username> <password>", + description: "Make a new account", + aliases: ["signup"], + command({ server, args, user, sendInChannel }) { + if (args.length < 2) return user.socket.send(`Usage: /register <username> <password>`); + if (args[0].length < 3) return user.socket.send(`Username too short!`); + if (args[0].length > 20) return user.socket.send(`Username too long!`); + if (args[1].length < 6) return user.socket.send(`Password too short!`); + if (server.accounts.checkAccount(args[0])) return user.socket.send(`User with username "${args[0]}" already exists!`); server.accounts.createAccount(args[0], args[1]); + if (server.config.saveIP) server.accounts.logIP(args[0], user.ip); + sendInChannel(`${user.name()} logged in as ${args[0]}!`, user.channel); user.username = args[0]; user.nickname = ""; user.guest = false; - sendInChannel(`${user.name()} logged in as ${args[0]}!`, user.channel) - } + user.admin = server.accounts.getAccountData(user.username).admin ?? false; + server.updateUsers(); + }, }, -} + pm: { + name: "pm", + usage: "/pm <user> [message]", + description: "Send a private message to a user", + aliases: [], + command({ server, args, user, sendInChannel }) { + 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]); + args.shift(); + userFound.socket.send(`${user.username} -> You : ${args.join(" ")}`); + user.socket.send(`You -> ${userFound.username} : ${args.join(" ")}`); + }, + }, +}; export function register(cmd, data) { - commands[cmd] = data + commands[cmd] = data; } -let commandFiles = fs.readdirSync("commands").filter(filename => filename.endsWith(".js")).map(file => file.replace(/\.js$/gmi, '')) +let commandFiles = fs + .readdirSync("commands") + .filter((filename) => filename.endsWith(".js")) + .map((file) => file.replace(/\.js$/gim, "")); for (let i = 0; i < commandFiles.length; i++) { const cmdName = commandFiles[i]; - const cmd = (await import(`./commands/${cmdName}.js`)).default - register(cmdName, cmd) -} \ No newline at end of file + const cmd = (await import(`./commands/${cmdName}.js`)).default; + register(cmdName, cmd); +} diff --git a/commands/motd.js b/commands/motd.js index 740cb07..a4e7fde 100644 --- a/commands/motd.js +++ b/commands/motd.js @@ -1,7 +1,7 @@ export default { - name: 'motd', + name: "motd", aliases: [], - command: function({user, server}) { + command: function ({ user, server }) { user.socket.send("MOTD: " + server.format(server.config.motd)); - } -} \ No newline at end of file + }, +}; diff --git a/config.json b/config.json index 884e06d..e8b3745 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,12 @@ { "name": "wsChat server", - "motd": "Welcome to $(serverName)$\nRun /help to see a list of commands", + "motd": "Welcome to $(serverName)$\nRun /help to see a list of commands\nHUGE CLIENT UPDATE\n- Client now supports links!\n- You can now {b}style{/b} your {i}messages{/i} with \\{b} or \\{i} tags!\n- {c#ff1111}Color support{/c} with {c#22aaff}\\{c#HEXCOLOR}{/c} tags", "max": 20, + "owner": "WlodekM", + "saveIP": false, + "requireLogin": true, "profanity": false, + "profanityRemoveWords": ["butt", "arse", "balls", "dick"], + "profanityAddWords": ["kys"], "fullMessage": "Sorry, but the server is full right now, come back later" -} \ No newline at end of file +} diff --git a/index.js b/index.js index ed778b0..10c7a88 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,20 @@ import { WebSocketServer } from "ws"; -import { getRandomInt } from "./lib.js" -import { profanity } from '@2toad/profanity'; +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'; -profanity.options.grawlixChar = "*" +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"], 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, @@ -21,64 +23,106 @@ const ws = new WebSocketServer({ function sendInChannel(msg, channel) { for (const userID in server.users) { const user = server.users[userID]; - if (user.channel == channel) user.socket.send(msg) + 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 + 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<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(), + }; + }) + )}` + ); + } + }); } -server.format = format +server.updateUsers = updateUsers; +server.format = format; -ws.on('connection', (socket, request) => { +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") + 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 anonID = getRandomInt(0, 99999) + 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); server.users[userID] = { username: `Anonymous${"0".repeat(5 - anonID.toString().length) + anonID.toString()}`, nickname: `Anonymous${"0".repeat(5 - anonID.toString().length) + anonID.toString()}`, guest: true, socket: socket, joinReq: request, + ip: ip, t: { js: Number(new Date()), unix: Math.floor(new Date().getTime() / 1000), - str: String(new Date()) + str: String(new Date()), }, - channel: 'home', - name: function(){return this.nickname != "" ? this.nickname : this.username} + 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; } - const user = server.users[userID] - console.info(`${user.name()} joined the server!`) - sendInChannel(`${user.name()} joined #${server.users[userID].channel}!`, server.users[userID].channel) - socket.on('close', function (code, reason) { - sendInChannel(`${user.name()} left.`, server.users[userID].channel) - delete server.users[userID] - }) - socket.on('message', function (rawData) { + 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}`) + 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 }) + commandObj.command({ + user, + command, + args, + sendInChannel, + server, + commands, + }); } catch (error) { - console.error(error) - user.socket.send(`Unexpected error ocurred while running the command`) + console.error(error); + user.socket.send(`Unexpected error ocurred while running the command`); } - return + return; } if (rawData.toString().startsWith(":client")) { let client = String(rawData).replace(":client", ""); @@ -86,53 +130,79 @@ ws.on('connection', (socket, request) => { 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 + return; } if (rawData.toString().startsWith(":jsonGet")) { let params = String(rawData).split(" "); - params.shift() + params.shift(); switch (params[0]) { - case 'channels': - socket.send(JSON.stringify(server.channels)); + case "channels": + socket.send(":json.channels>" + JSON.stringify(server.channels)); break; - case 'users': - socket.send(JSON.stringify(Object.values(server.users).map(usr => {return { - username: usr.username, - nickname: usr.nickname, - t: usr.t, - channel: user.channel, - displayName: user.name() - }}))); + 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': + 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() - }}) - )); + .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 + 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) 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!"); - if (!server.profanity) rawData = profanity.censor(String(rawData)); - sendInChannel(`<${user.name()}${user.guest ? " (guest)" : ""}> ${rawData}`, server.users[userID].channel) - console.log(`(#${server.users[userID].channel}) <${user.name()}> ${rawData}`) - }) -}) + 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") -}) +ws.on("listening", () => { + console.info("[INFO] Server started"); +}); diff --git a/jsconfig.json b/jsconfig.json index 238655f..dfef62d 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,27 +1,27 @@ { - "compilerOptions": { - // Enable latest features - "lib": ["ESNext", "DOM"], - "target": "ESNext", - "module": "ESNext", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, - // Bundler mode - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, - // Best practices - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } } diff --git a/lib.js b/lib.js index 64b0d6c..9038650 100644 --- a/lib.js +++ b/lib.js @@ -12,4 +12,4 @@ export function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..120eefe --- /dev/null +++ b/package-lock.json @@ -0,0 +1,120 @@ +{ + "name": "wschat", + "version": "1.3.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wschat", + "version": "1.3.0", + "dependencies": { + "@2toad/profanity": "^2.2.0", + "cuid": "^3.0.0", + "ws": "^8.17.1" + }, + "devDependencies": { + "@types/bun": "latest", + "prettier": "3.3.3" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@2toad/profanity": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/bun": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.6.tgz", + "integrity": "sha512-uJgKjTdX0GkWEHZzQzFsJkWp5+43ZS7HC8sZPFnOwnSo1AsNl2q9o2bFeS23disNDqbggEgyFkKCHl/w8iZsMA==", + "dev": true, + "dependencies": { + "bun-types": "1.1.17" + } + }, + "node_modules/@types/node": { + "version": "20.12.14", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/bun-types": { + "version": "1.1.17", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "~20.12.8", + "@types/ws": "~8.5.10" + } + }, + "node_modules/cuid": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.17.1", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json index a81284c..62fed1c 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,24 @@ { - "name": "wschat", - "description": "A simple IRC-like chat made using nodejs and webSockets", - "version": "1.2.5", - "author": { - "name": "WlodekM", - "email": "[email protected]", - "url": "https://wlodekm.nekoweb.org" - }, - "module": "index.js", - "type": "module", - "devDependencies": { - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "dependencies": { - "@2toad/profanity": "^2.2.0", - "cuid": "^3.0.0", - "ws": "^8.17.1" - } -} \ No newline at end of file + "name": "wschat", + "description": "A simple IRC-like chat made using nodejs and webSockets", + "version": "1.3.0", + "author": { + "name": "WlodekM", + "email": "[email protected]", + "url": "https://wlodekm.nekoweb.org" + }, + "module": "index.js", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "prettier": "3.3.3" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@2toad/profanity": "^2.2.0", + "cuid": "^3.0.0", + "ws": "^8.17.1" + } +} |