From bac2526c99506ecc9a2e04905e3d0307233e47f2 Mon Sep 17 00:00:00 2001 From: WlodekM Date: Tue, 2 Jul 2024 15:10:24 +0300 Subject: Initial commit --- .gitignore | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 26 +++++++++ bun.lockb | Bin 0 -> 3873 bytes client.html | 68 +++++++++++++++++++++++ index.js | 87 +++++++++++++++++++++++++++++ jsconfig.json | 27 +++++++++ lib.js | 15 +++++ package.json | 21 +++++++ 8 files changed, 419 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bun.lockb create mode 100644 client.html create mode 100644 index.js create mode 100644 jsconfig.json create mode 100644 lib.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..00d38a1 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# wsChat + +A simple IRC-like chat made using nodejs and webSockets + +## Running the server + +To install dependencies: + +```bash +npm install +``` + +To run: + +```bash +node index.js +``` + +## TODO + +- Make commands be in an object instead of a switch statement +- /help command +- /about command +- Config (JSON) +- MOTD +- Custom commands diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..3cef471 Binary files /dev/null and b/bun.lockb differ diff --git a/client.html b/client.html new file mode 100644 index 0000000..87d95c3 --- /dev/null +++ b/client.html @@ -0,0 +1,68 @@ +
+
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..2043219 --- /dev/null +++ b/index.js @@ -0,0 +1,87 @@ +import { WebSocketServer } from "ws"; +import cuid from 'cuid'; +import { getRandomInt } from "./lib.js" + +const channels = ["home", "off-topic"] + +const ws = new WebSocketServer({ + port: 9933, +}); + +const users = {} + +function sendInChannel(msg, channel) { + for (const userID in users) { + const user = users[userID]; + if(user.channel == channel) user.socket.send(msg) + } +} + +ws.on('connection', (socket, request) => { + // console.log(request, socket); + let userID = cuid() + socket.send("Welcome to WlodekM's wsChat server!") + let anonID = getRandomInt(0, 99999) + users[userID] = { + username: `Anonymous${"0".repeat(5 - anonID.length) + anonID.toString()}`, + socket: socket, + joinReq: request, + t: { + js: Number(new Date()), + unix: Math.floor(new Date().getTime() / 1000), + str: String(new Date()) + }, + channel: 'home' + } + sendInChannel(`${users[userID].username} joined #${users[userID].channel}!`, users[userID].channel) + socket.on('close', function(code, reason) { + sendInChannel(`${users[userID].username} left.`, users[userID].channel) + delete users[userID] + }) + socket.on('message', function (rawData) { + if(rawData.toString().startsWith("/")) { + let args = String(rawData).replace("/", "").split(" "); + let command = args.shift(); + console.log(`${users[userID].username} used /${command}`) + switch (command) { + case 'join': + if(args.length < 1) return socket.send("Error: You need to specify a channel (example: /join #home)."); + if(!args[0].startsWith("#")) return socket.send("Error: Channel not found, run /channels to see a list of channels."); + if(!channels.includes(args[0].replace("#", ""))) return socket.send("Error: Channel not found, run /channels to see a list of channels."); + sendInChannel(`${users[userID].username} left #${users[userID].channel}.`, users[userID].channel) + users[userID].channel = args[0].replace("#", ""); + sendInChannel(`${users[userID].username} joined #${users[userID].channel}!`, users[userID].channel) + break; + case 'channels': + socket.send(`Channels:\n${channels.map(ch => ` * #${ch}`).join("\n")}`) + break; + case 'name': + case 'nickname': + case 'nick': + if(args.length < 1) return socket.send("Error: You need to specify a nick (example: /nick WlodekM)."); + if(args[0].length < 3 ) return socket.send("Error: Nick too long."); + if(args[0].length > 20) return socket.send("Error: Nick too short."); + if(Object.values(users).find(usr => usr.username == args[0])) return socket.send("Error: Nick already used."); + sendInChannel(`${users[userID].username} changed their nick to ${args[0]}!`, users[userID].channel) + users[userID].username = args[0] + break; + case 'users': + socket.send(`Users${args[0] != "global" ? ` in ${users[userID].channel}` : ""}:\n${Object.values(users).filter(usr => (usr.channel == users[userID].channel) || args[0] == "global").map(ch => ` * ${ch}`).join("\n")}`) + break; + + default: + socket.send(`Error: Command "${command}" not found!`); + break; + } + return + } + if(rawData.length < 1) return socket.send("Error: message too short!") + if(rawData.length >= 2000) return socket.send("Error: message too long!") + sendInChannel(`<${users[userID].username}> ${rawData}`, users[userID].channel) + console.log(`(#${users[userID].channel}) <${users[userID].username}> ${rawData}`) + }) +}) + +ws.on('listening', () => { + console.info("[INFO] Server started") +}) diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,27 @@ +{ + "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, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/lib.js b/lib.js new file mode 100644 index 0000000..64b0d6c --- /dev/null +++ b/lib.js @@ -0,0 +1,15 @@ +/** + * Returns a random integer between min (inclusive) and max (inclusive). + * The value is no lower than min (or the next integer greater than min + * if min isn't an integer) and no greater than max (or the next integer + * lower than max if max isn't an integer). + * Using Math.round() will give you a non-uniform distribution! + * @param {Number} min Min number + * @param {Number} max Max number + * @returns {Number} + */ +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.json b/package.json new file mode 100644 index 0000000..28078ba --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "wschat", + "description": "A simple IRC-like chat made using nodejs and webSockets", + "author": { + "name": "WlodekM", + "email": "wlodekm.work@gmail.com", + "url": "https://wlodekm.nekoweb.org" + }, + "module": "index.js", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "cuid": "^3.0.0", + "ws": "^8.17.1" + } +} \ No newline at end of file -- cgit 1.4.1-2-gfad0