diff options
-rw-r--r-- | .gitignore | 175 | ||||
-rw-r--r-- | README.md | 26 | ||||
-rw-r--r-- | bun.lockb | bin | 0 -> 3873 bytes | |||
-rw-r--r-- | client.html | 68 | ||||
-rw-r--r-- | index.js | 87 | ||||
-rw-r--r-- | jsconfig.json | 27 | ||||
-rw-r--r-- | lib.js | 15 | ||||
-rw-r--r-- | package.json | 21 |
8 files changed, 419 insertions, 0 deletions
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 --- /dev/null +++ b/bun.lockb Binary files differdiff --git a/client.html b/client.html new file mode 100644 index 0000000..87d95c3 --- /dev/null +++ b/client.html @@ -0,0 +1,68 @@ +<div class="container"> + <div class="logs"> + </div> + <div class="msg"> + <form id="msg"> + <input type="text" id="msg-content" autocomplete="off"> + <input type="submit"> + </form> + </div> +</div> +<script> + const logs = document.querySelector(".logs"); + const msgForm = document.querySelector("#msg"); + const ws = new WebSocket("ws://127.0.0.1:9933"); // CHANGE ME + ws.onopen = openEv => { + if(logs.innerText.length != 0) logs.innerText += "\n"; + logs.innerText += `Connected to wsChat server at ${ws.url}`; + } + ws.onmessage = msg => {; + console.log(msg); + 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 => { + ev.preventDefault(); + const message = document.querySelector("#msg-content"); + ws.send(message.value); + message.value = ""; + }); +</script> +<style> + * { + box-sizing: border-box; + } + body { + margin: 0; + width: 100%; + min-height: 100vh; + position: absolute; + } + .container { + position: relative; + display: flex; + flex-direction: column; + height: calc(100vh - 3em); + border: 2px red solid; + margin: 1.5em; + } + .logs { + flex-grow: 1; + padding: 1em; + } + #msg { + border-top: 2px red solid; + padding: 1em; + display: flex; + margin: 0; + height: 2em; + box-sizing: content-box; + } + #msg-content { + flex-grow: 1; + } +</style> \ 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": "[email protected]", + "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 |