Kaynağa Gözat

Avatare, Farben und History hinzugefügt

Alexander Vornam 3 yıl önce
ebeveyn
işleme
ff315af0c4

+ 49 - 8
index.js

@@ -1,5 +1,6 @@
 const fs = require('fs');
 const https = require('https');
+const seedrandom = require('seedrandom');
 
 const express = require('express');
 const app = express();
@@ -7,7 +8,7 @@ const app = express();
 const options = {
     key: fs.readFileSync("ssl/private.key"),
     cert: fs.readFileSync("ssl/certificate.crt"),
-    ca: [ fs.readFileSync('ssl/ca_bundle.crt') ]
+    ca: [fs.readFileSync('ssl/ca_bundle.crt')]
 };
 
 const server = https.createServer(options, app);
@@ -29,8 +30,36 @@ fs.readFile("config/ban.json", "utf8", (err, data) => {
     }
 })
 
+const randomColors = [
+    '#c42020',
+    '#de801e',
+    '#f6c60b',
+    '#19ce19',
+    '#129696',
+    '#2c53e3',
+    '#6f23e3',
+    '#883f88',
+];
+
+const HISTORY_SIZE = 50;
+var history = [];
+
 app.use(express.static(__dirname + '/www'));
 app.use('/node_modules', express.static(__dirname + '/node_modules'));
+app.get('/img/ava/:username', (req, res) => {
+    var file = '';
+    if (fs.existsSync(__dirname + '/www/img/ava/' + req.params.username + '.png')) {
+        file = __dirname + '/www/img/ava/' + req.params.username + '.png';
+    } else {
+        const files = fs.readdirSync(__dirname + '/www/img/ava/random/');
+
+        const seededRandom = seedrandom(req.params.username)();
+        const randomFile = files[Math.floor(seededRandom * files.length)];
+        file = __dirname + '/www/img/ava/random/' + randomFile;
+    }
+
+    res.sendFile(file);
+});
 
 io.on('connection', (socket) => {
     const ip = socket.handshake.address;
@@ -48,7 +77,7 @@ io.on('connection', (socket) => {
 
     socket.emit('serverHandshake');
 
-    const user = { ip: ip, socket: socket };
+    const user = {ip: ip, socket: socket};
     users.push(user);
 
     socket.on('login', (username, password) => {
@@ -93,8 +122,9 @@ io.on('connection', (socket) => {
         log('- New user logged in:', username, '(' + ip + ')');
 
         user.name = username;
+        user.color = getRandomColor(username);
 
-        socket.emit('serverLogin', getCleanUser(user));
+        socket.emit('serverLogin', getCleanUser(user), history);
         io.emit('userJoined', username);
         updateUsers();
         updateBanned();
@@ -110,6 +140,10 @@ io.on('connection', (socket) => {
         socket.on('message', (msg) => {
             if (msg) {
                 log(user.name + ':', msg, '(' + user.ip + ')');
+                if (history.length >= HISTORY_SIZE) {
+                    history = history.slice(1);
+                }
+                history.push({msg: msg, user: getCleanUser(user), timestamp: Date.now()});
                 io.emit('message', msg, getCleanUser(user), Date.now());
             }
         });
@@ -123,7 +157,8 @@ function updateUsers() {
 function getCleanUser(user) {
     return {
         name: user.name,
-        admin: user.admin
+        admin: user.admin,
+        color: user.color
     }
 }
 
@@ -137,7 +172,7 @@ function kickUser(user) {
         }
     }
 
-    bannedIps.push({ ip: ip, user: user });
+    bannedIps.push({ip: ip, user: user});
     updateBanned();
 
     for (const exUser of users) {
@@ -154,10 +189,15 @@ function liftBan(ip) {
 }
 
 function updateBanned() {
-    fs.writeFile('config/ban.json', JSON.stringify(bannedIps), (e) => {});
+    fs.writeFile('config/ban.json', JSON.stringify(bannedIps), (e) => {
+    });
     io.emit('bannedUpdated', bannedIps);
 }
 
+function getRandomColor(seed) {
+    return randomColors[Math.floor(seedrandom(seed)() * randomColors.length)];
+}
+
 function log(...inputs) {
     var output = '[' + new Date().toISOString().substr(0, 19).replace('T', ', ') + '] ';
 
@@ -167,9 +207,10 @@ function log(...inputs) {
 
     console.log(output);
 
-    fs.appendFile('log.txt', '' + output + '\n', (e) => {});
+    fs.appendFile('log.txt', '' + output + '\n', (e) => {
+    });
 }
 
 server.listen(PORT, () => {
-    log('- Server up and running at port ' +  PORT);
+    log('- Server up and running at port ' + PORT);
 });

+ 713 - 2
package-lock.json

@@ -1,8 +1,713 @@
 {
 	"name": "Chat",
 	"version": "0.0.1",
-	"lockfileVersion": 1,
+	"lockfileVersion": 2,
 	"requires": true,
+	"packages": {
+		"": {
+			"name": "Chat",
+			"version": "0.0.1",
+			"dependencies": {
+				"express": "^4.15.2",
+				"seedrandom": "^3.0.5",
+				"socket.io": "^4.1.3"
+			}
+		},
+		"node_modules/@types/component-emitter": {
+			"version": "1.2.10",
+			"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
+			"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
+		},
+		"node_modules/@types/cookie": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+			"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
+		},
+		"node_modules/@types/cors": {
+			"version": "2.8.12",
+			"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
+			"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
+		},
+		"node_modules/@types/node": {
+			"version": "16.7.1",
+			"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz",
+			"integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A=="
+		},
+		"node_modules/accepts": {
+			"version": "1.3.7",
+			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+			"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+			"dependencies": {
+				"mime-types": "~2.1.24",
+				"negotiator": "0.6.2"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/array-flatten": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+			"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+		},
+		"node_modules/base64-arraybuffer": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
+			"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=",
+			"engines": {
+				"node": ">= 0.6.0"
+			}
+		},
+		"node_modules/base64id": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+			"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+			"engines": {
+				"node": "^4.5.0 || >= 5.9"
+			}
+		},
+		"node_modules/body-parser": {
+			"version": "1.19.0",
+			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+			"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+			"dependencies": {
+				"bytes": "3.1.0",
+				"content-type": "~1.0.4",
+				"debug": "2.6.9",
+				"depd": "~1.1.2",
+				"http-errors": "1.7.2",
+				"iconv-lite": "0.4.24",
+				"on-finished": "~2.3.0",
+				"qs": "6.7.0",
+				"raw-body": "2.4.0",
+				"type-is": "~1.6.17"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/body-parser/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/body-parser/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+		},
+		"node_modules/bytes": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+			"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/component-emitter": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+			"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
+		},
+		"node_modules/content-disposition": {
+			"version": "0.5.3",
+			"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+			"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+			"dependencies": {
+				"safe-buffer": "5.1.2"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/content-type": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+			"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/cookie": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+			"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/cookie-signature": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+			"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+		},
+		"node_modules/cors": {
+			"version": "2.8.5",
+			"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+			"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+			"dependencies": {
+				"object-assign": "^4",
+				"vary": "^1"
+			},
+			"engines": {
+				"node": ">= 0.10"
+			}
+		},
+		"node_modules/debug": {
+			"version": "4.3.2",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+			"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+			"dependencies": {
+				"ms": "2.1.2"
+			},
+			"engines": {
+				"node": ">=6.0"
+			},
+			"peerDependenciesMeta": {
+				"supports-color": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/depd": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+			"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/destroy": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+			"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+		},
+		"node_modules/ee-first": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+			"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+		},
+		"node_modules/encodeurl": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+			"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/engine.io": {
+			"version": "5.1.1",
+			"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.1.1.tgz",
+			"integrity": "sha512-aMWot7H5aC8L4/T8qMYbLdvKlZOdJTH54FxfdFunTGvhMx1BHkJOntWArsVfgAZVwAO9LC2sryPWRcEeUzCe5w==",
+			"dependencies": {
+				"accepts": "~1.3.4",
+				"base64id": "2.0.0",
+				"cookie": "~0.4.1",
+				"cors": "~2.8.5",
+				"debug": "~4.3.1",
+				"engine.io-parser": "~4.0.0",
+				"ws": "~7.4.2"
+			},
+			"engines": {
+				"node": ">=10.0.0"
+			}
+		},
+		"node_modules/engine.io-parser": {
+			"version": "4.0.2",
+			"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
+			"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
+			"dependencies": {
+				"base64-arraybuffer": "0.1.4"
+			},
+			"engines": {
+				"node": ">=8.0.0"
+			}
+		},
+		"node_modules/escape-html": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+			"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+		},
+		"node_modules/etag": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+			"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/express": {
+			"version": "4.17.1",
+			"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+			"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+			"dependencies": {
+				"accepts": "~1.3.7",
+				"array-flatten": "1.1.1",
+				"body-parser": "1.19.0",
+				"content-disposition": "0.5.3",
+				"content-type": "~1.0.4",
+				"cookie": "0.4.0",
+				"cookie-signature": "1.0.6",
+				"debug": "2.6.9",
+				"depd": "~1.1.2",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"finalhandler": "~1.1.2",
+				"fresh": "0.5.2",
+				"merge-descriptors": "1.0.1",
+				"methods": "~1.1.2",
+				"on-finished": "~2.3.0",
+				"parseurl": "~1.3.3",
+				"path-to-regexp": "0.1.7",
+				"proxy-addr": "~2.0.5",
+				"qs": "6.7.0",
+				"range-parser": "~1.2.1",
+				"safe-buffer": "5.1.2",
+				"send": "0.17.1",
+				"serve-static": "1.14.1",
+				"setprototypeof": "1.1.1",
+				"statuses": "~1.5.0",
+				"type-is": "~1.6.18",
+				"utils-merge": "1.0.1",
+				"vary": "~1.1.2"
+			},
+			"engines": {
+				"node": ">= 0.10.0"
+			}
+		},
+		"node_modules/express/node_modules/cookie": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+			"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/express/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/express/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+		},
+		"node_modules/finalhandler": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+			"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+			"dependencies": {
+				"debug": "2.6.9",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"on-finished": "~2.3.0",
+				"parseurl": "~1.3.3",
+				"statuses": "~1.5.0",
+				"unpipe": "~1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/finalhandler/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/finalhandler/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+		},
+		"node_modules/forwarded": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+			"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/fresh": {
+			"version": "0.5.2",
+			"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+			"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/http-errors": {
+			"version": "1.7.2",
+			"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+			"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+			"dependencies": {
+				"depd": "~1.1.2",
+				"inherits": "2.0.3",
+				"setprototypeof": "1.1.1",
+				"statuses": ">= 1.5.0 < 2",
+				"toidentifier": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"dependencies": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/inherits": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+			"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+		},
+		"node_modules/ipaddr.js": {
+			"version": "1.9.1",
+			"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+			"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+			"engines": {
+				"node": ">= 0.10"
+			}
+		},
+		"node_modules/media-typer": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+			"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/merge-descriptors": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+			"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+		},
+		"node_modules/methods": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+			"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/mime": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+			"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+			"bin": {
+				"mime": "cli.js"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/mime-db": {
+			"version": "1.49.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
+			"integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/mime-types": {
+			"version": "2.1.32",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz",
+			"integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==",
+			"dependencies": {
+				"mime-db": "1.49.0"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/ms": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+		},
+		"node_modules/negotiator": {
+			"version": "0.6.2",
+			"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+			"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/object-assign": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+			"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/on-finished": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+			"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+			"dependencies": {
+				"ee-first": "1.1.1"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/parseurl": {
+			"version": "1.3.3",
+			"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+			"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/path-to-regexp": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+			"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+		},
+		"node_modules/proxy-addr": {
+			"version": "2.0.7",
+			"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+			"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+			"dependencies": {
+				"forwarded": "0.2.0",
+				"ipaddr.js": "1.9.1"
+			},
+			"engines": {
+				"node": ">= 0.10"
+			}
+		},
+		"node_modules/qs": {
+			"version": "6.7.0",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+			"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+			"engines": {
+				"node": ">=0.6"
+			}
+		},
+		"node_modules/range-parser": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+			"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/raw-body": {
+			"version": "2.4.0",
+			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+			"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+			"dependencies": {
+				"bytes": "3.1.0",
+				"http-errors": "1.7.2",
+				"iconv-lite": "0.4.24",
+				"unpipe": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/safe-buffer": {
+			"version": "5.1.2",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+		},
+		"node_modules/safer-buffer": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+		},
+		"node_modules/seedrandom": {
+			"version": "3.0.5",
+			"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+			"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
+		},
+		"node_modules/send": {
+			"version": "0.17.1",
+			"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+			"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+			"dependencies": {
+				"debug": "2.6.9",
+				"depd": "~1.1.2",
+				"destroy": "~1.0.4",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"fresh": "0.5.2",
+				"http-errors": "~1.7.2",
+				"mime": "1.6.0",
+				"ms": "2.1.1",
+				"on-finished": "~2.3.0",
+				"range-parser": "~1.2.1",
+				"statuses": "~1.5.0"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/send/node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/send/node_modules/debug/node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+		},
+		"node_modules/send/node_modules/ms": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+			"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+		},
+		"node_modules/serve-static": {
+			"version": "1.14.1",
+			"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+			"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+			"dependencies": {
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"parseurl": "~1.3.3",
+				"send": "0.17.1"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/setprototypeof": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+			"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+		},
+		"node_modules/socket.io": {
+			"version": "4.1.3",
+			"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz",
+			"integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==",
+			"dependencies": {
+				"@types/cookie": "^0.4.0",
+				"@types/cors": "^2.8.10",
+				"@types/node": ">=10.0.0",
+				"accepts": "~1.3.4",
+				"base64id": "~2.0.0",
+				"debug": "~4.3.1",
+				"engine.io": "~5.1.1",
+				"socket.io-adapter": "~2.3.1",
+				"socket.io-parser": "~4.0.4"
+			},
+			"engines": {
+				"node": ">=10.0.0"
+			}
+		},
+		"node_modules/socket.io-adapter": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.1.tgz",
+			"integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw=="
+		},
+		"node_modules/socket.io-parser": {
+			"version": "4.0.4",
+			"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
+			"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
+			"dependencies": {
+				"@types/component-emitter": "^1.2.10",
+				"component-emitter": "~1.3.0",
+				"debug": "~4.3.1"
+			},
+			"engines": {
+				"node": ">=10.0.0"
+			}
+		},
+		"node_modules/statuses": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+			"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/toidentifier": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+			"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+			"engines": {
+				"node": ">=0.6"
+			}
+		},
+		"node_modules/type-is": {
+			"version": "1.6.18",
+			"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+			"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+			"dependencies": {
+				"media-typer": "0.3.0",
+				"mime-types": "~2.1.24"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/unpipe": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+			"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/utils-merge": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+			"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+			"engines": {
+				"node": ">= 0.4.0"
+			}
+		},
+		"node_modules/vary": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+			"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/ws": {
+			"version": "7.4.6",
+			"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
+			"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+			"engines": {
+				"node": ">=8.3.0"
+			},
+			"peerDependencies": {
+				"bufferutil": "^4.0.1",
+				"utf-8-validate": "^5.0.2"
+			},
+			"peerDependenciesMeta": {
+				"bufferutil": {
+					"optional": true
+				},
+				"utf-8-validate": {
+					"optional": true
+				}
+			}
+		}
+	},
 	"dependencies": {
 		"@types/component-emitter": {
 			"version": "1.2.10",
@@ -414,6 +1119,11 @@
 			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
 			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
 		},
+		"seedrandom": {
+			"version": "3.0.5",
+			"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+			"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
+		},
 		"send": {
 			"version": "0.17.1",
 			"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
@@ -540,7 +1250,8 @@
 		"ws": {
 			"version": "7.4.6",
 			"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
-			"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
+			"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+			"requires": {}
 		}
 	}
 }

+ 1 - 0
package.json

@@ -4,6 +4,7 @@
 	"description": "",
 	"dependencies": {
 		"express": "^4.15.2",
+		"seedrandom": "^3.0.5",
 		"socket.io": "^4.1.3"
 	},
 	"scripts": {

+ 24 - 2
www/css/app.css

@@ -88,6 +88,9 @@
 #chat-host .messages {
     flex-basis: 80%;
     overflow: auto;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
 }
 
 #chat-host .messages .systemMsg {
@@ -97,11 +100,25 @@
 }
 
 #chat-host .messages .userMsg {
-    display: flex;
+    display: inline-flex;
     align-items: baseline;
+    flex-wrap: wrap;
+    
+    min-width: 25%;
+    max-width: 85%;
+
+    padding: 0.5em;
+    margin: 1em 0.5em;
+}
+
+#chat-host .messages .userMsg.self {
+    align-self: flex-end;
+    justify-content: flex-end;
+    text-align: right;
 }
 
 #chat-host .messages .userMsg .user {
+    order: -1;
     margin-right: 0.3em;
     font-weight: bold;
 }
@@ -114,6 +131,7 @@
 
 #chat-host .messages .userMsg .message {
     white-space: pre-wrap;
+    width: 100%;
 }
 
 #chat-host .userlistWrapper {
@@ -122,6 +140,10 @@
     min-width: 8em;
 }
 
+#chat-host .userlistWrapper span {
+    font-weight: bold;
+}
+
 #chat-host .userlistWrapper button {
     width: 2em;
     height: 2em;
@@ -139,4 +161,4 @@
     flex-grow: 1;
     margin-right: 1em;
     resize: none;
-}
+}

+ 49 - 2
www/css/theme.css

@@ -12,8 +12,55 @@
     background-color: #f8f8f8;
 }
 
+#chat-host .main .messages li.systemMsg {
+    margin: 0.5em;
+}
+
+#chat-host .main .messages li.userMsg {
+    background-color: #ededed;
+    border-radius: 1em;
+    padding: 0.5em 0.8em 0.5em 4em;
+    margin: 0.5em 1em;
+    position: relative;
+    border-width: 1px;
+    border-style: solid;
+    border-left-width: 1em;
+    box-shadow: 0.1em 0.1em 0.5em rgba(0, 0, 0, 0.1);
+}
+
+#chat-host .main .messages li.userMsg.self {
+    padding: 0.5em 4em 0.5em 0.8em;
+    border-left-width: 1px;
+    border-right-width: 1em;
+}
+
+#chat-host .main .messages li.userMsg .user,
+#chat-host .main .userlistWrapper .userlist li span {
+    filter: brightness(0.7) saturate(1.2);
+}
+
+#chat-host .main .messages li.userMsg .avatar {
+    position: absolute;
+    border-radius: 50%;
+    width: 2.75em;
+    height: 2.75em;
+    left: 0.75em;
+    background-color: silver;
+    object-fit: cover;
+}
+
+#chat-host .main .messages li.userMsg.self .avatar {
+    left: unset;
+    right: 0.75em;
+}
+
+#chat-host .main .messages li.userMsg .message {
+    margin-top: 0.2em;
+}
+
+
 #chat-host .main .userlistWrapper {
-    border-left: 1px solid #d0d0d0;
+    border-left: 1px solid #e2e2e2;
     padding-left: 1em;
 }
 
@@ -27,4 +74,4 @@
     font-size: inherit;
     text-transform: uppercase;
     color: #4d4d4d;
-}
+}

BIN
www/img/ava/André.png


BIN
www/img/ava/Jacky.png


BIN
www/img/ava/random/768px-Dragonball.png


BIN
www/img/ava/random/EmHsVb5X0AAJ6SD.png


BIN
www/img/ava/random/KlBecoa.png


BIN
www/img/ava/random/card_1017490_thumb.png


BIN
www/img/ava/random/dbz_son_goku___head__by_krizart_da_d5oimcd-fullview.png


BIN
www/img/ava/random/ddomo6i-7ee0e831-494c-4028-b80d-08fbdf9ed79d.png


BIN
www/img/ava/random/krillin.png


BIN
www/img/ava/random/pngaaa.com-1305546.png


BIN
www/img/ava/random/roshi_dbz.png


BIN
www/img/ava/random/shenron_render__dbz_kakarot__by_maxiuchiha22_ddom6t2-fullview.png


+ 2 - 5
www/index.html

@@ -44,11 +44,6 @@
                 <ul class="userlist"></ul>
             </div>
         </div>
-
-        <form class="messageForm" action="">
-            <textarea class="messageField" />
-            <button>Abschicken</button>
-        </form>
     </script>
 
     <script id="chat-admin-template" type="text/x-custom-template">
@@ -61,7 +56,9 @@
                 <ul class="bannedlist"></ul>
             </div>
         </div>
+    </script>
 
+    <script id="chat-message-form-template" type="text/x-custom-template">
         <form class="messageForm" action="">
             <textarea class="messageField" />
             <button>Abschicken</button>

+ 52 - 27
www/js/main.js

@@ -17,8 +17,6 @@ function onServerHandshake() {
     $('#chat-login-form').submit((e) => {
         e.preventDefault();
 
-        console.debug('submit login form', e);
-
         socket.emit('login', $('#chat-login-form .username').val(), $('#chat-login-form .password').val());
 
         return false;
@@ -47,7 +45,7 @@ function onServerHandshake() {
     });
 }
 
-function onServerLogin(user) {
+function onServerLogin(user, history) {
     resetChatListeners();
     resetLoginListeners();
 
@@ -61,8 +59,14 @@ function onServerLogin(user) {
     $('#chat-host').empty();
     $('#chat-host').append(template);
 
+    $('#chat-host').append($('#chat-message-form-template').html());
+
     $('#chat-host .messageField').focus();
 
+    for (const historyMsg of history) {
+        appendUserMessage(historyMsg.msg, historyMsg.user, historyMsg.timestamp, user);
+    }
+
     socket.on('usersUpdated', (userList) => {
         $('#chat-host .main .userlist').empty();
 
@@ -84,12 +88,18 @@ function onServerLogin(user) {
             }
 
             const userListEl = $('<li>');
-            userListEl.text(userString);
+
+            const usernameEl = $('<span>');
+            usernameEl.text(userString);
+            usernameEl.css('color', listUser.color);
+            userListEl.append(usernameEl);
+
             if (user.admin) {
                 const kickBtn = $('<button class="kickBtn">🥾</button>');
                 kickBtn.click(() => onKickUser(listUser));
                 userListEl.append(kickBtn);
             }
+
             $('#chat-host .main .userlist').append(userListEl);
         }
     });
@@ -112,28 +122,7 @@ function onServerLogin(user) {
         });
     }
 
-    socket.on('message', (msg, user, timestamp) => {
-        const messageWrapper = $('<li class="userMsg">');
-
-        const timeEl = $('<span class="time">');
-        timeEl.text(new Date(timestamp).toLocaleTimeString());
-        timeEl.prop('title', new Date(timestamp).toLocaleString());
-        messageWrapper.append(timeEl);
-
-        const userEl = $('<span class="user">');
-        let userString = user.name + ':';
-        if (user.admin) {
-            userString = '👑 ' + userString;
-        }
-        userEl.text(userString);
-        messageWrapper.append(userEl);
-
-        const messageEl = $('<span class="message">');
-        messageEl.text(msg)
-        messageWrapper.append(messageEl);
-
-        appendMessage(messageWrapper);
-    });
+    socket.on('message', (msg, msgUser, timestamp) => appendUserMessage(msg, msgUser, timestamp, user));
 
     socket.on('userJoined', (msg) => {
         appendMessage($('<li class="systemMsg user joined">').text(msg + ' hat den Chat betreten.'));
@@ -190,9 +179,45 @@ function appendMessage(element) {
 
     $('#chat-host .main .messages').append(element);
 
-    if (!scrolled) {
+    // TODO: Scrolling
+    //if (!scrolled) {
         messagesListEl.scrollTop = messagesListEl.scrollHeight - messagesListEl.clientHeight;
+    //}
+}
+
+function appendUserMessage(msg, msgUser, timestamp, user) {
+    console.log(msg, msgUser, timestamp);
+
+    const messageWrapper = $('<li class="userMsg">');
+    if(user.name ==  msgUser.name) {
+        messageWrapper.addClass('self');
+    }
+    messageWrapper.css('borderColor', msgUser.color);
+
+    const avaEl = $('<img class="avatar" />');
+    avaEl.prop('src', '/img/ava/' + msgUser.name);
+    avaEl.css('backgroundColor', msgUser.color);
+    messageWrapper.append(avaEl);
+
+    const timeEl = $('<span class="time">');
+    timeEl.text(new Date(timestamp).toLocaleTimeString());
+    timeEl.prop('title', new Date(timestamp).toLocaleString());
+    messageWrapper.append(timeEl);
+
+    const userEl = $('<span class="user">');
+    let userString = msgUser.name;
+    if (msgUser.admin) {
+        userString = '👑 ' + userString;
     }
+    userEl.text(userString);
+    userEl.css('color', msgUser.color);
+    messageWrapper.append(userEl);
+
+    const messageEl = $('<span class="message">');
+    messageEl.text(msg)
+    messageWrapper.append(messageEl);
+
+    appendMessage(messageWrapper);
 }
 
 function onKickUser(user) {