From 4b1fc5c1fc093f04345c6c8c2ba66662f961f6e9 Mon Sep 17 00:00:00 2001 From: knotteye Date: Thu, 5 Dec 2019 18:27:29 -0600 Subject: [PATCH 1/9] Make adapative streaming full configurable --- src/controller.ts | 4 ++-- src/server.ts | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/controller.ts b/src/controller.ts index c87f0c9..6170a7d 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -53,8 +53,8 @@ async function run() { api: config.server.api, api_user: config.server.api_user, api_pass: config.server.api_pass - } - + }, + transcode: config.transcode }; db.init(dbcfg, bcryptcfg); await cleanup.init(); diff --git a/src/server.ts b/src/server.ts index 62d8a8d..0f0b0cf 100644 --- a/src/server.ts +++ b/src/server.ts @@ -34,14 +34,14 @@ function init (mediaconfig: any, satyrconfig: any) { //otherwise kill the session db.query('select username,record_flag from users where stream_key='+db.raw.escape(key)+' limit 1').then(async (results) => { if(results[0]){ - //push to rtmp - //execFile(satyrconfig.ffmpeg, ['-loglevel', 'fatal', '-analyzeduration', '0', '-i', 'rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key, '-vcodec', 'copy', '-acodec', 'copy', '-crf', '18', '-f', 'flv', 'rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.publicEndpoint+'/'+results[0].username], {maxBuffer: Infinity}); - //push to mpd after making sure directory exists + //transcode to mpd after making sure directory exists keystore[results[0].username] = key; mkdir(satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username, { recursive : true }, ()=>{;}); while(true){ if(session.audioCodec !== 0 && session.videoCodec !== 0){ - execFile(satyrconfig.ffmpeg, ['-loglevel', 'fatal', '-y', '-i', 'rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key, '-map', '0:2', '-map', '0:2', '-map', '0:2', '-map', '0:1', '-c:a', 'copy', '-c:v:0', 'copy', '-c:v:1', 'libx264', '-c:v:2', 'libx264', '-crf:1', '33', '-crf:2', '40', '-b:v:1', '3000K', '-b:v:2', '1500K', '-remove_at_exit', '1', '-seg_duration', '1', '-window_size', '30', '-f', 'dash', satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username+'/index.mpd'], {maxBuffer: Infinity}); + transCommand(mediaconfig, satyrconfig, results[0].username, key).then((r) => { + execFile(satyrconfig.ffmpeg, r, {maxBuffer: Infinity}); + }); break; } await sleep(300); @@ -109,4 +109,33 @@ function init (mediaconfig: any, satyrconfig: any) { } }); } + +async function transCommand(config: object, satyrconfig: object, user: string, key: string): Promise{ + let args: string[] = ['-loglevel', 'fatal', '-y', '-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+satyrconfig['privateEndpoint']+'/'+key]; + if(config['transcode']['adaptive']===true && config['transcode']['variants'] > 1) { + for(let i=0;i 51 ? 51 : Math.floor(18 + (i * 7)); + args = args.concat(['-crf:'+i, ''+crf]); + } + for(let i=1;i Date: Thu, 5 Dec 2019 18:37:26 -0600 Subject: [PATCH 2/9] Add adaptive livestreaming config docs. --- docs/CONFIGURATION.md | 31 ++++++++++++++++++++++++++----- docs/INSTALLATION.md | 11 +++++------ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index f9bf420..54e848b 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -6,22 +6,43 @@ Some values you might want to change are ``` [satyr] registration = true -#allow new users to register +# allow new users to register rootRedirect = '/users/live' -#the page users are directed to when they visit your site root +# the page users are directed to when they visit your site root + +[server.http] +hsts = true +# enable strict transport security + [media] record = true -#allow users to record VODs +# allow users to record VODs + +[transcode] +adapative = true +# enable adaptive livestreaming +# will help users with poor connections, but EXTREMELY cpu intensive +# even 3 variants will max out most budget VPSs with a single stream +variants = 3 +# the number of adaptive streaming variants to generate +# satyr will always copy the source stream +# and the remaining variants will lower the quality incrementally + +# So the default setting of 3 will copy the source stream once +# And generate two lower quality & bitrate variants + [bcrypt] saltRounds = 12 #change the number of rounds of bcrypt to fit your hardware #if you don't understand the implications, don't change this + [ircd] enable = true #enable IRC peering -#unused for now +#extremely beta ``` ### Web Frontend If you want to customize the front-end css, place a file with any changes you wish to make at site/local.css -You can change the logo by replacing site/logo.svg. \ No newline at end of file +You can change the logo by replacing site/logo.svg. +You should also consider editing templates/about.html and templates/tos.html \ No newline at end of file diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index abcf2eb..0f9dbd2 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -2,8 +2,8 @@ A more detailed walkthrough. ### System Dependencies -Install ffmpeg and mysql through your distribution's package manager. -See [this page](https://nodejs.org/en/download/package-manager/) for instructions on install node. Compatible versions are >=10. Nightly builds may fail to compile some of the native addons. +Install ffmpeg(>= 4.2.1) and mysql through your distribution's package manager. +See [this page](https://nodejs.org/en/download/package-manager/) for instructions on installing node. Compatible versions are >=10. Nightly builds may fail to compile some of the native addons. ### Installing Satyr Clone the repository and change to the directory @@ -25,9 +25,8 @@ Run the setup script for the database. sudo mysql source install/db_setup.sql; ``` -Compile the code and start the server. +Then start the server. ```bash -npm run build npm start ``` @@ -40,10 +39,10 @@ Updating should be as simple as pulling the latest code and dependencies, then b ```bash git pull npm i -npm run build +npm update ``` Then restart the server. ## Migrating Satyr -To backup and restore, you will need to export the mysqlDB. Restore the new database from the backup, then copy the config/local.toml file and the site directory to the new install. \ No newline at end of file +To backup and restore, you will need to export the mysqlDB. Restore the new database from the backup, then copy config/local.toml, config/jwt.pem, and the site directory to the new location. \ No newline at end of file From a0be256a64875f462ac0b74a8a49f6014b9e7ed4 Mon Sep 17 00:00:00 2001 From: knotteye Date: Sat, 7 Dec 2019 10:51:26 -0600 Subject: [PATCH 3/9] Hopefully resolves issues with videojs occasionally not initializing the player --- templates/base.njk | 2 ++ templates/user.njk | 15 +++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/templates/base.njk b/templates/base.njk index 7f2a642..bc1f99a 100644 --- a/templates/base.njk +++ b/templates/base.njk @@ -4,6 +4,8 @@ {{ sitename }} + {% block head %} + {% endblock %}
diff --git a/templates/user.njk b/templates/user.njk index 7e86d4e..b25ebbe 100644 --- a/templates/user.njk +++ b/templates/user.njk @@ -1,4 +1,8 @@ {% extends "base.njk" %} +{% block head %} + + +{% endblock %} {% block content %} - - +
+ + {{ about | escape }}
- - {{ about | escape }} + {% endblock %} \ No newline at end of file From 61deb1afa72328d8d50c7aacd45ce463c04594a9 Mon Sep 17 00:00:00 2001 From: knotteye Date: Sat, 7 Dec 2019 21:23:50 -0600 Subject: [PATCH 4/9] Minor improvements to socket.io chat, including banning and unbanning per room, and spam detection and server bans --- .gitignore | 1 + package-lock.json | 15 ++++++- package.json | 1 + src/http.ts | 99 +++++++++++++++++++++++++++++++++++++++------ templates/chat.html | 18 +++++++++ 5 files changed, 120 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index fad7bdd..116395e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ site/live config/local.toml config/jwt.pem config/generated.toml +config/bans.db install/db_setup.sql build/** diff --git a/package-lock.json b/package-lock.json index 9c53b90..379f6ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "satyr", - "version": "0.4.4", + "version": "0.5.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2043,6 +2043,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2712,6 +2717,14 @@ } } }, + "socket-anti-spam": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/socket-anti-spam/-/socket-anti-spam-2.0.0.tgz", + "integrity": "sha512-glCDT8LrqwSY+tQJtvaz3YwTw1HL6bgWVvaQFumkClOcF+Jbg0NlAImqQabowNJcrCxr1dibKRoAvIfN98FKVw==", + "requires": { + "moment": "^2.21.0" + } + }, "socket.io": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", diff --git a/package.json b/package.json index f98013f..82dcedf 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "node-media-server": ">=2.1.3 <3.0.0", "nunjucks": "^3.2.0", "recursive-readdir": "^2.2.2", + "socket-anti-spam": "^2.0.0", "socket.io": "^2.3.0", "strftime": "^0.10.0", "toml": "^3.0.0", diff --git a/src/http.ts b/src/http.ts index 47b00e6..7f05cd7 100644 --- a/src/http.ts +++ b/src/http.ts @@ -5,6 +5,7 @@ import * as socketio from "socket.io"; import * as http from "http"; import * as cookies from "cookie-parser"; import * as dirty from "dirty"; +import * as socketSpam from "socket-anti-spam"; import * as api from "./api"; import * as db from "./database"; import * as irc from "./irc"; @@ -18,6 +19,8 @@ const app = express(); const server = http.createServer(app); const io = socketio(server); const store = dirty(); +var banlist; +var ircconf; var jwkey; try{ jwkey = JWK.asKey(readFileSync('./config/jwt.pem')); @@ -28,7 +31,8 @@ try{ } var njkconf; -async function init(satyr: any, http: object, ircconf: any){ +async function init(satyr: any, http: object, irc: any){ + ircconf = irc; njk.configure('templates', { autoescape : true, express : app, @@ -65,19 +69,22 @@ async function init(satyr: any, http: object, ircconf: any){ else res.status(404).render('404.njk', njkconf); //res.status(404).render('404.njk', njkconf); }); - await initChat(ircconf); + banlist = new dirty('./config/bans.db').on('load', () => {initChat()}); server.listen(http['port']); } -async function newNick(socket, skip?: boolean) { +async function newNick(socket, skip?: boolean, i?: number) { if(socket.handshake.headers['cookie'] && !skip){ let c = await parseCookie(socket.handshake.headers['cookie']); let t = await validToken(c['Authorization']); - if(t) return t['username']; + if(t) { + store.set(t, socket.id); + return t['username']; + } } - //i just realized how shitty of an idea this is - let n: string = 'Guest'+Math.floor(Math.random() * Math.floor(1000)); - if(store.get(n)) return newNick(socket, true); + if(!i) i = 10; + let n: string = 'Guest'+Math.floor(Math.random() * Math.floor(i)); + if(store.get(n)) return newNick(socket, true, Math.floor(i * 10)); else { store.set(n, socket.id); return n; @@ -90,7 +97,7 @@ async function chgNick(socket, nick, f?: boolean) { io.to(rooms[i]).emit('ALERT', socket.nick+' is now known as '+nick); } if(store.get(socket.nick)) store.rm(socket.nick); - if (!f) store.set(nick, socket.id); + store.set(nick, socket.id); socket.nick = nick; } @@ -328,7 +335,7 @@ async function initSite(openReg) { }); } -async function initChat(ircconf: any) { +async function initChat() { //irc peering if(ircconf.enable){ await irc.connect({ @@ -349,6 +356,15 @@ async function initChat(ircconf: any) { socket.on('JOINROOM', async (data) => { let t: any = await db.query('select username from users where username='+db.raw.escape(data)); if(t[0]){ + if(banlist.get(data) && banlist.get(data)[socket.ip]){ + if(Math.floor(banlist.get(data)[socket.ip]['time'] + (banlist.get(data)[socket.ip]['length'] * 60)) < Math.floor(Date.now() / 1000)){ + banlist.set('data', Object.assign(banlist['data'], {[socket.ip]: null})); + } + else { + socket.emit('ALERT', 'You are banned from that room'); + return; + } + } socket.join(data); io.to(data).emit('JOINED', {nick: socket.nick}); if(ircconf.enable) irc.join(socket.nick, data); @@ -386,10 +402,6 @@ async function initChat(ircconf: any) { }); socket.on('NICK', async (data) => { data.nick = data.nick.replace(' ',''); - if(store.get(data.nick)){ - socket.emit('ALERT', 'Nickname is already in use'); - return false; - } let user = await db.query('select username from users where username='+db.raw.escape(data.nick)); if(user[0]){ if(!data.password){ @@ -402,6 +414,10 @@ async function initChat(ircconf: any) { else socket.emit('ALERT','Incorrect username or password'); } else { + if(store.get(data.nick)){ + socket.emit('ALERT', 'Nickname is already in use'); + return false; + } chgNick(socket, data.nick); } }); @@ -423,6 +439,63 @@ async function initChat(ircconf: any) { } else socket.emit('ALERT', 'Not authorized to do that.'); }); + socket.on('BAN', (data: Object) => { + if(socket.nick === data['room']){ + let id: string = store.get(data['nick']); + if(id){ + let target = io.sockets.connected[id]; + if(typeof(data['time']) === 'number' && (data['time'] !== 0 || data['time'] !== NaN)) banlist.set(data['room'], Object.assign({[target.ip]: {time: Math.floor(Date.now() / 1000), length: data['time']}}, banlist.get(data['room']))); + else banlist.set(data['room'], Object.assign({[target.ip]: {time: Math.floor(Date.now() / 1000), length: 30}}, banlist.get(data['room']))); + target.disconnect(true); + io.to(data['room']).emit('ALERT', target.nick+' was banned.'); + } + else socket.emit('ALERT', 'No such user found.'); + } + else socket.emit('ALERT', 'Not authorized to do that.'); + }); + socket.on('UNBAN', (data: Object) => { + if(socket.nick === data['room']){ + if(banlist.get(data['room']) && banlist.get(data['room'])[data['ip']]){ + banlist.set(data['room'], Object.assign(banlist.get(data['room']), {[data['ip']]: null})); + socket.emit('ALERT', data['ip']+' was unbanned.'); + } + else + socket.emit('ALERT', 'That IP is not banned.'); + } + else socket.emit('ALERT', 'Not authorized to do that.'); + }); + socket.on('LISTBAN', (data: Object) => { + if(socket.nick === data['room']){ + if(banlist.get(data['room'])) { + let bans = Object.keys(banlist.get(data['room'])); + let str = ''; + for(let i=0;i { + let rooms = Object.keys(socket.rooms); + for(let i=1;i Date: Sun, 8 Dec 2019 17:18:31 -0600 Subject: [PATCH 5/9] Bugfix for room bans --- src/http.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/http.ts b/src/http.ts index 7f05cd7..cf0be22 100644 --- a/src/http.ts +++ b/src/http.ts @@ -78,7 +78,7 @@ async function newNick(socket, skip?: boolean, i?: number) { let c = await parseCookie(socket.handshake.headers['cookie']); let t = await validToken(c['Authorization']); if(t) { - store.set(t, socket.id); + store.set(t['username'], socket.id); return t['username']; } } @@ -356,9 +356,9 @@ async function initChat() { socket.on('JOINROOM', async (data) => { let t: any = await db.query('select username from users where username='+db.raw.escape(data)); if(t[0]){ - if(banlist.get(data) && banlist.get(data)[socket.ip]){ - if(Math.floor(banlist.get(data)[socket.ip]['time'] + (banlist.get(data)[socket.ip]['length'] * 60)) < Math.floor(Date.now() / 1000)){ - banlist.set('data', Object.assign(banlist['data'], {[socket.ip]: null})); + if(banlist.get(data) && banlist.get(data)[socket['handshake']['address']]){ + if(Math.floor(banlist.get(data)[socket['handshake']['address']]['time'] + (banlist.get(data)[socket['handshake']['address']]['length'] * 60)) < Math.floor(Date.now() / 1000)){ + banlist.set(data, Object.assign({}, banlist.get(data), {[socket['handshake']['address']]: null})); } else { socket.emit('ALERT', 'You are banned from that room'); @@ -444,8 +444,8 @@ async function initChat() { let id: string = store.get(data['nick']); if(id){ let target = io.sockets.connected[id]; - if(typeof(data['time']) === 'number' && (data['time'] !== 0 || data['time'] !== NaN)) banlist.set(data['room'], Object.assign({[target.ip]: {time: Math.floor(Date.now() / 1000), length: data['time']}}, banlist.get(data['room']))); - else banlist.set(data['room'], Object.assign({[target.ip]: {time: Math.floor(Date.now() / 1000), length: 30}}, banlist.get(data['room']))); + if(typeof(data['time']) === 'number' && (data['time'] !== 0 || data['time'] !== NaN)) banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[target.ip]: {time: Math.floor(Date.now() / 1000), length: data['time']}})); + else banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[target.ip]: {time: Math.floor(Date.now() / 1000), length: 30}})); target.disconnect(true); io.to(data['room']).emit('ALERT', target.nick+' was banned.'); } @@ -456,7 +456,7 @@ async function initChat() { socket.on('UNBAN', (data: Object) => { if(socket.nick === data['room']){ if(banlist.get(data['room']) && banlist.get(data['room'])[data['ip']]){ - banlist.set(data['room'], Object.assign(banlist.get(data['room']), {[data['ip']]: null})); + banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[data['ip']]: null})); socket.emit('ALERT', data['ip']+' was unbanned.'); } else From f7733b9507aef0fed112d483b44656b905992a93 Mon Sep 17 00:00:00 2001 From: knotteye Date: Sat, 21 Dec 2019 08:59:35 -0600 Subject: [PATCH 6/9] Big Refactor Stop using config and toml as dependencies Stop passing around config variables through function calls Add config.ts and pull the values you need directly in the files Remove irc.js for incoming new IRC solution Rename controller to index because that was stupid Minor git bullshit with the config folder Change to yaml as a config format --- .gitignore | 6 +- config/.gitkeep | 0 config/default.toml | 58 ---------- install/config.example.yml | 24 ++++ install/setup.sh | 6 +- install/template.local.toml | 19 ---- package-lock.json | 58 ++++++++++ package.json | 8 +- src/api.ts | 15 +-- src/cli.ts | 3 +- src/config.ts | 49 +++++++++ src/controller.ts | 67 ------------ src/database.ts | 15 +-- src/http.ts | 56 ++++------ src/index.ts | 15 +++ src/irc.js | 212 ------------------------------------ src/server.ts | 35 +++--- 17 files changed, 208 insertions(+), 438 deletions(-) create mode 100644 config/.gitkeep delete mode 100644 config/default.toml create mode 100644 install/config.example.yml delete mode 100644 install/template.local.toml create mode 100644 src/config.ts delete mode 100644 src/controller.ts create mode 100644 src/index.ts delete mode 100644 src/irc.js diff --git a/.gitignore b/.gitignore index 116395e..8c8153b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ node_modules site/live -config/local.toml -config/jwt.pem -config/generated.toml -config/bans.db +config/**/* +!config/.gitkeep install/db_setup.sql build/** diff --git a/config/.gitkeep b/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/config/default.toml b/config/default.toml deleted file mode 100644 index 21b7055..0000000 --- a/config/default.toml +++ /dev/null @@ -1,58 +0,0 @@ -#DO NOT EDIT THIS FILE -#ALL CHANGES SHOULD GO IN LOCAL.TOML -[bcrypt] -saltRounds = 12 - -[satyr] -name = '' -domain = '' -registration = false -restrictedNames = ['live'] -rootredirect = '/users/live' - -[ircd] -enable = false -port = 6667 -sid = '' -server = '' -pass = '' -vhost = 'web.satyr.net' - -[database] -host = 'localhost' -user = 'satyr' -password = '' -database = 'satyr_db' -connectionLimit = '50' -connectionTimeout = '1000' -insecureAuth = false -debug = false - -[server] -logs = 0 -api = false -api_user = false -api_pass = false - -[server.rtmp] -port = 1935 -chunk_size = 6000 -gop_cache = true -ping = 30 -ping_timeout = 60 - -[server.http] -hsts = false -directory = './site' -port = 8000 - -[media] -record = false -publicEndpoint = 'live' -privateEndpoint = 'stream' -ffmpeg = '' - -[transcode] -adapative = false -variants = 3 -format = 'dash' \ No newline at end of file diff --git a/install/config.example.yml b/install/config.example.yml new file mode 100644 index 0000000..4f4ee13 --- /dev/null +++ b/install/config.example.yml @@ -0,0 +1,24 @@ +satyr: + name: '' + domain: '' + email: '' + registration: false + +media: + record: false + ffmpeg: '' + +http: + # uncomment to set HSTS when SSL is ready + #hsts: true + +database: + user: '' + password: '' + database: '' + host: '' + +transcode: + adaptive: false + format: dash + variants: 3 \ No newline at end of file diff --git a/install/setup.sh b/install/setup.sh index 6741d48..e4a153a 100644 --- a/install/setup.sh +++ b/install/setup.sh @@ -38,8 +38,8 @@ dbclient="${dbclient:='*'}" else dbclient="localhost" fi -sed -e "s##$name#g" -e "s##$domain#g" -e "s##$ffmpeg#g" -e "s##$dbuser#g" -e "s##$dbname#g" -e "s##$dbpass#g" -e "s##$dbhost#g" -e "s##$email#g" install/template.local.toml > config/generated.toml +sed -e "s##$name#g" -e "s##$domain#g" -e "s##$ffmpeg#g" -e "s##$dbuser#g" -e "s##$dbname#g" -e "s##$dbpass#g" -e "s##$dbhost#g" -e "s##$email#g" install/config.example.yml > config/generated.yml sed -e "s##$dbuser#g" -e "s##$dbname#g" -e "s##$dbpass#g" -e "s##$dbhost#g" -e "s##$dbclient#g" install/db_template.sql > install/db_setup.sql echo "A setup script for the database has been generated at install/db_setup.sql. Please run it by connecting to your database software and executing 'source install/db_setup.sql;''" -echo "A default configuration file has been generated at config/generated.toml" -echo "If everything looks fine, move it to config/local.toml and start your instance." \ No newline at end of file +echo "A default configuration file has been generated at config/generated.yml" +echo "If everything looks fine, move it to config/config.yml and start your instance." \ No newline at end of file diff --git a/install/template.local.toml b/install/template.local.toml deleted file mode 100644 index bb32b64..0000000 --- a/install/template.local.toml +++ /dev/null @@ -1,19 +0,0 @@ -[satyr] -name = '' -domain = '' -email = '' -registration = false - -[media] -record = false -ffmpeg = '' - -[server.http] -# uncomment to set HSTS when SSL is enabled -# hsts = true - -[database] -user = '' -password = '' -database = '' -host = '' diff --git a/package-lock.json b/package-lock.json index 379f6ce..b0e10eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,6 +82,11 @@ "readable-stream": "^2.0.6" } }, + "arg": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.2.tgz", + "integrity": "sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==" + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -342,6 +347,11 @@ } } }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -644,6 +654,11 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==" + }, "dirty": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/dirty/-/dirty-1.1.0.tgz", @@ -1910,6 +1925,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -2292,6 +2312,11 @@ "os-tmpdir": "^1.0.0" } }, + "parse-yaml": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/parse-yaml/-/parse-yaml-0.1.0.tgz", + "integrity": "sha512-tLfs2QiziUPFTA4nNrv2rrC0CnHDIF2o2m5TCgNss/E0asI0ltVjBcNKhcd/8vteZa8xKV5RGfD0ZFFlECMCqQ==" + }, "parseqs": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", @@ -2868,6 +2893,22 @@ "urix": "^0.1.0" } }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", @@ -3029,6 +3070,18 @@ "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" }, + "ts-node": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz", + "integrity": "sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw==", + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -3201,6 +3254,11 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } } diff --git a/package.json b/package.json index 82dcedf..a5012b2 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "license": "AGPL-3.0", "author": "knotteye", "scripts": { - "start": "tsc && node build/controller.js", - "user": "node build/cli.js", + "start": "ts-node src/index.ts", + "user": "ts-node src/cli.ts", "setup": "sh install/setup.sh" }, "repository": { @@ -16,7 +16,6 @@ "dependencies": { "bcrypt": "^3.0.6", "body-parser": "^1.19.0", - "config": "^3.2.2", "cookie-parser": "^1.4.4", "dirty": "^1.1.0", "express": "^4.17.1", @@ -25,11 +24,12 @@ "mysql": "^2.17.1", "node-media-server": ">=2.1.3 <3.0.0", "nunjucks": "^3.2.0", + "parse-yaml": "^0.1.0", "recursive-readdir": "^2.2.2", "socket-anti-spam": "^2.0.0", "socket.io": "^2.3.0", "strftime": "^0.10.0", - "toml": "^3.0.0", + "ts-node": "^8.5.4", "typescript": "^3.6.3" }, "devDependencies": { diff --git a/src/api.ts b/src/api.ts index 7ff9944..bd9a853 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,17 +1,12 @@ import * as db from "./database" -import { unregisterUser } from "./irc"; - -var config: any; -function init(conf: object){ - config = conf; -} +import { config } from "./config"; async function register(name: string, password: string, confirm: string) { - if(!config.registration) return {"error":"registration disabled"}; + if(!config['satyr']['registration']) return {"error":"registration disabled"}; if(name.includes(';') || name.includes(' ') || name.includes('\'')) return {"error":"illegal characters"}; if(password !== confirm) return {"error":"mismatched passwords"}; - for(let i=0;i { res.append('Strict-Transport-Security', 'max-age=5184000'); next(); @@ -56,11 +55,11 @@ async function init(satyr: any, http: object, irc: any){ } app.disable('x-powered-by'); //site handlers - await initSite(satyr.registration); + await initSite(config['satyr']['registration']); //api handlers await initAPI(); //static files if nothing else matches first - app.use(express.static(satyr.directory)); + app.use(express.static(config['http']['directory'])); //404 Handler app.use(function (req, res, next) { if(tryDecode(req.cookies.Authorization)) { @@ -70,7 +69,7 @@ async function init(satyr: any, http: object, irc: any){ //res.status(404).render('404.njk', njkconf); }); banlist = new dirty('./config/bans.db').on('load', () => {initChat()}); - server.listen(http['port']); + server.listen(config['http']['port']); } async function newNick(socket, skip?: boolean, i?: number) { @@ -336,23 +335,17 @@ async function initSite(openReg) { } async function initChat() { - //irc peering - if(ircconf.enable){ - await irc.connect({ - port: ircconf.port, - sid: ircconf.sid, - pass: ircconf.pass, - server: ircconf.server, - vhost: ircconf.vhost - }); - irc.events.on('message', (nick, channel, msg) => { - io.to(channel).emit('MSG', {nick: nick, msg: msg}); - }); - } + //set a cookie to request same nick + //apply same nick if the request comes from the same ip + //structure of entry in store should be: + // knotteye: { + // ip: "127.0.0.1", + // id: ["aklsjdnaksj", "asjdnaksjnd", "aksjdnkajs"] + //} + //socket.io chat logic io.on('connection', async (socket) => { socket.nick = await newNick(socket); - if(ircconf.enable) irc.registerUser(socket.nick); socket.on('JOINROOM', async (data) => { let t: any = await db.query('select username from users where username='+db.raw.escape(data)); if(t[0]){ @@ -367,7 +360,6 @@ async function initChat() { } socket.join(data); io.to(data).emit('JOINED', {nick: socket.nick}); - if(ircconf.enable) irc.join(socket.nick, data); } else socket.emit('ALERT', 'Room does not exist'); }); @@ -388,16 +380,13 @@ async function initChat() { }); socket.on('LEAVEROOM', (data) => { socket.leave(data); - if(ircconf.enable) irc.part(socket.nick, data); io.to(data).emit('LEFT', {nick: socket.nick}); }); socket.on('disconnecting', (reason) => { let rooms = Object.keys(socket.rooms); for(let i=1;i { @@ -424,7 +413,6 @@ async function initChat() { socket.on('MSG', (data) => { if(data.msg === "" || !data.msg.replace(/\s/g, '').length) return; io.to(data.room).emit('MSG', {nick: socket.nick, msg: data.msg}); - if(ircconf.enable) irc.send(socket.nick, data.room, data.msg); }); socket.on('KICK', (data) => { if(socket.nick === data.room){ @@ -491,10 +479,8 @@ async function initChat() { socketAS.event.on('ban', (socket) => { let rooms = Object.keys(socket.rooms); for(let i=1;i -// thanks nikki - -const net = require('net') -const EventEmitter = require('events') - -const socket = new net.Socket() -const emitter = new EventEmitter() - -socket.setEncoding('utf8') - -socket.on('error', console.error) - -function m (text) { - console.log('> ' + text) - socket.write(text + '\r\n') -} - -var config - -socket.once('connect', async () => { - console.log('Connected') - m(`PASS ${config.pass} TS 6 :${config.sid}`) - m('CAPAB QS ENCAP EX IE SAVE EUID') - m(`SERVER ${config.server} 1 satyr`) -}) - -function parseLine (l) { - const colIndex = l.lastIndexOf(':') - if (colIndex > -1) { - return { - params: l.substring(0, colIndex - 1).split(' '), - query: l.substring(colIndex + 1) - } - } else return { params: l.split(' ') } -} - -const servers = [] -const users = {} -const channels = {} - -const globalCommands = { - // PING :42X - // params: SID - PING: l => { - const { query } = parseLine(l) - m(`PONG :${query}`) - emitter.emit('ping') - }, - // PASS hunter2 TS 6 :42X - // params: password, 'TS', TS version, SID - PASS: l => { - const { query } = parseLine(l) - // adds a server - servers.push(query) - } -} - -const serverCommands = { - // EUID nik 1 1569146316 +i ~nik localhost6.attlocal.net 0::1 42XAAAAAB * * :nik - // params: nickname, hopcount, nickTS, umodes, username, visible hostname, IP address, UID, real hostname, account name, gecos - EUID: l => { - const { params } = parseLine(l) - const user = { - nick: params[0], - nickTS: params[2], - modes: params[3], - username: params[4], - vhost: params[5], - ip: params[6], - uid: params[7] - } - users[user.uid] = user - }, - // SJOIN 1569142987 #test +nt :42XAAAAAB - // params: channelTS, channel, simple modes, opt. mode parameters..., nicklist - SJOIN: l => { - const { params, query } = parseLine(l) - const channel = { - timestamp: params[0], - name: params[1], - modes: params.slice(2).join(' '), - nicklist: query.split(' ').map(uid => { - if (/[^0-9a-zA-Z]/.test(uid[0])) return { uid: uid.slice(1), mode: uid[0] } - else return { uid: uid, mode: '' } - }) - } - channels[channel.name] = channel - } -} - -const userCommands = { - // :42XAAAAAC PRIVMSG #test :asd - // params: target, msg - PRIVMSG: (l, source) => { - const { params, query } = parseLine(l) - emitter.emit('message', users[source].nick, params[0], query) - }, - // :42XAAAAAC JOIN 1569149395 #test + - JOIN: (l, source) => { - const { params } = parseLine(l) - channels[params[1]].nicklist.push({ - uid: source - }) - }, - // :42XAAAAAC PART #test :WeeChat 2.6 - PART: (l, source) => { - const { params } = parseLine(l) - for (let i = 0; i < channels[params[0]].nicklist.length; i++) { - if (channels[params[0]].nicklist[i].uid === source) { - channels[params[0]].nicklist.splice(i, 1) - return - } - } - }, - QUIT: (_l, source) => { - delete users[source] - } -} - -function parser (l) { - const split = l.split(' ') - const cmd = split[0] - const args = split.slice(1).join(' ') - if (globalCommands[cmd]) return globalCommands[cmd](args) - if (cmd[0] === ':') { - const source = cmd.slice(1) - const subcmd = split[1] - const subargs = split.slice(2).join(' ') - if (servers.indexOf(source) > -1 && serverCommands[subcmd]) serverCommands[subcmd](subargs) - if (users[source] && userCommands[subcmd]) userCommands[subcmd](subargs, source) - } -} - -socket.on('data', data => { - data.split('\r\n') - .filter(l => l !== '') - .forEach(l => { - console.log('< ' + l) - parser(l) - }) -}) - -module.exports.connect = conf => new Promise((resolve, reject) => { - emitter.once('ping', resolve) - config = conf - socket.connect(config.port) - process.on('SIGINT', () => { - socket.write('QUIT\r\n') - process.exit() - }) -}) -module.exports.events = emitter - -const genTS = () => Math.trunc((new Date()).getTime() / 1000) -const genUID = () => { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - var uid = '' - for (let i = 0; i < 6; i++) uid += chars.charAt(Math.floor(Math.random() * chars.length)) - if (users[uid]) return genUID() - return config.sid + uid -} -const getUID = nick => { - for (const key in users) if (users[key].nick === nick) return key -} - -module.exports.registerUser = nick => { - const user = { - nick: nick, - nickTS: genTS(), - modes: '+i', - username: '~' + nick, - vhost: config.vhost, - ip: '0::1', - uid: genUID() - } - users[user.uid] = user - m(`EUID ${user.nick} 1 ${user.nickTS} ${user.modes} ~${user.nick} ${user.vhost} 0::1 ${user.uid} * * :${user.nick}`) -} -module.exports.unregisterUser = nick => { - const uid = getUID(nick) - m(`:${uid} QUIT :Quit: satyr`) - delete users[uid] -} -module.exports.join = (nick, channelName) => { - const uid = getUID(nick) - if (!channels[channelName]) { - const channel = { - timestamp: genTS(), - name: channelName, - modes: '+nt', - nicklist: [{ uid: uid, mode: '' }] - } - channels[channel.name] = channel - } - m(`:${uid} JOIN ${channels[channelName].timestamp} ${channelName} +`) -} -module.exports.part = (nick, channelName) => { - const uid = getUID(nick) - m(`:${uid} PART ${channelName} :satyr`) - for (let i = 0; i < channels[channelName].nicklist.length; i++) { - if (channels[channelName].nicklist[i].uid === uid) { - channels[channelName].nicklist.splice(i, 1) - return - } - } -} -module.exports.send = (nick, channelName, message) => { - const uid = getUID(nick) - m(`:${uid} PRIVMSG ${channelName} :${message}`) - emitter.emit('message', nick, channelName, message) -} diff --git a/src/server.ts b/src/server.ts index 0f0b0cf..054ec06 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,13 +3,14 @@ import * as dirty from "dirty"; import { mkdir, fstat, access } from "fs"; import * as strf from "strftime"; import * as db from "./database"; +import {config} from "./config"; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const { exec, execFile } = require('child_process'); const keystore = dirty(); -function init (mediaconfig: any, satyrconfig: any) { - const nms = new NodeMediaServer(mediaconfig); +function init () { + const nms = new NodeMediaServer({logType: 0,rtmp: config['rtmp']}); nms.run(); nms.on('postPublish', (id, StreamPath, args) => { @@ -23,7 +24,7 @@ function init (mediaconfig: any, satyrconfig: any) { session.reject(); return false; } - if(app !== satyrconfig.privateEndpoint){ + if(app !== config['media']['privateEndpoint']){ //app isn't at public endpoint if we've reached this point console.log("[NodeMediaServer] Wrong endpoint, rejecting stream:",id); session.reject(); @@ -36,21 +37,21 @@ function init (mediaconfig: any, satyrconfig: any) { if(results[0]){ //transcode to mpd after making sure directory exists keystore[results[0].username] = key; - mkdir(satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username, { recursive : true }, ()=>{;}); + mkdir(config['http']['directory']+'/'+config['media']['publicEndpoint']+'/'+results[0].username, { recursive : true }, ()=>{;}); while(true){ if(session.audioCodec !== 0 && session.videoCodec !== 0){ - transCommand(mediaconfig, satyrconfig, results[0].username, key).then((r) => { - execFile(satyrconfig.ffmpeg, r, {maxBuffer: Infinity}); + transCommand(results[0].username, key).then((r) => { + execFile(config['media']['ffmpeg'], r, {maxBuffer: Infinity}); }); break; } await sleep(300); } - if(results[0].record_flag && satyrconfig.record){ + if(results[0].record_flag && config['media']['record']){ console.log('[NodeMediaServer] Initiating recording for stream:',id); - mkdir(satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username, { recursive : true }, (err) => { + mkdir(config['http']['directory']+'/'+config['media']['publicEndpoint']+'/'+results[0].username, { recursive : true }, (err) => { if (err) throw err; - execFile(satyrconfig.ffmpeg, ['-loglevel', 'fatal', '-i', 'rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.prviateEndpoint+'/'+key, '-vcodec', 'copy', '-acodec', 'copy', satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username+'/'+strf('%d%b%Y-%H%M')+'.mp4'], { + execFile(config['media']['ffmpeg'], ['-loglevel', 'fatal', '-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+config['media']['privateEndpoint']+'/'+key, '-vcodec', 'copy', '-acodec', 'copy', config['http']['directory']+'/'+config['media']['publicEndpoint']+'/'+results[0].username+'/'+strf('%d%b%Y-%H%M')+'.mp4'], { detached : true, stdio : 'inherit', maxBuffer: Infinity @@ -74,7 +75,7 @@ function init (mediaconfig: any, satyrconfig: any) { nms.on('donePublish', (id, StreamPath, args) => { let app: string = StreamPath.split("/")[1]; let key: string = StreamPath.split("/")[2]; - if(app === satyrconfig.privateEndpoint) { + if(app === config['media']['privateEndpoint']) { db.query('update user_meta,users set user_meta.live=false where users.stream_key='+db.raw.escape(key)); db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then(async (results) => { if(results[0]) keystore.rm(results[0].username); @@ -94,24 +95,24 @@ function init (mediaconfig: any, satyrconfig: any) { } //localhost can play from whatever endpoint //other clients must use private endpoint - if(app !== satyrconfig.publicEndpoint && !session.isLocal) { + if(app !== config['media']['publicEndpoint'] && !session.isLocal) { console.log("[NodeMediaServer] Non-local Play from private endpoint, rejecting client:",id); session.reject(); return false; } //rewrite playpath to private endpoint serverside //(hopefully) - if(app === satyrconfig.publicEndpoint) { + if(app === config['media']['publicEndpoint']) { if(keystore[key]){ - session.playStreamPath = '/'+satyrconfig.privateEndpoint+'/'+keystore[key]; + session.playStreamPath = '/'+config['media']['privateEndpoint']+'/'+keystore[key]; return true; } } }); } -async function transCommand(config: object, satyrconfig: object, user: string, key: string): Promise{ - let args: string[] = ['-loglevel', 'fatal', '-y', '-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+satyrconfig['privateEndpoint']+'/'+key]; +async function transCommand(user: string, key: string): Promise{ + let args: string[] = ['-loglevel', 'fatal', '-y', '-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+config['media']['privateEndpoint']+'/'+key]; if(config['transcode']['adaptive']===true && config['transcode']['variants'] > 1) { for(let i=0;i Date: Sat, 21 Dec 2019 16:16:50 -0600 Subject: [PATCH 7/9] Add proper kicking and banning for users with multiple accounts. --- src/http.ts | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/http.ts b/src/http.ts index 695f40a..a3f774e 100644 --- a/src/http.ts +++ b/src/http.ts @@ -95,7 +95,11 @@ async function chgNick(socket, nick, f?: boolean) { for(let i=1;i item !== socket.id)); + else store.rm(socket.nick); + } + if(f) store.set(nick, [].concat(store.get(nick), socket.id).filter(item => item !== undefined)); store.set(nick, socket.id); socket.nick = nick; } @@ -336,12 +340,6 @@ async function initSite(openReg) { async function initChat() { //set a cookie to request same nick - //apply same nick if the request comes from the same ip - //structure of entry in store should be: - // knotteye: { - // ip: "127.0.0.1", - // id: ["aklsjdnaksj", "asjdnaksjnd", "aksjdnkajs"] - //} //socket.io chat logic io.on('connection', async (socket) => { @@ -387,6 +385,11 @@ async function initChat() { for(let i=1;i item !== socket.id)) + if(store.get(socket.nick) !== []) + return; + } store.rm(socket.nick); }); socket.on('NICK', async (data) => { @@ -419,6 +422,11 @@ async function initChat() { //find client with data.nick let id: string = store.get(data.nick); if(id){ + if(Array.isArray(id)) { + for(let i=0;i Date: Sat, 21 Dec 2019 16:58:40 -0600 Subject: [PATCH 8/9] Fix a bug with socket ids not being recorded or looped through properly --- src/http.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/http.ts b/src/http.ts index a3f774e..c45fffa 100644 --- a/src/http.ts +++ b/src/http.ts @@ -77,7 +77,7 @@ async function newNick(socket, skip?: boolean, i?: number) { let c = await parseCookie(socket.handshake.headers['cookie']); let t = await validToken(c['Authorization']); if(t) { - store.set(t['username'], socket.id); + store.set(t['username'], [].concat(store.get(t['username']), socket.id).filter(item => item !== undefined)); return t['username']; } } @@ -99,8 +99,8 @@ async function chgNick(socket, nick, f?: boolean) { if(Array.isArray(store.get(socket.nick))) store.set(socket.nick, store.get(socket.nick).filter(item => item !== socket.id)); else store.rm(socket.nick); } - if(f) store.set(nick, [].concat(store.get(nick), socket.id).filter(item => item !== undefined)); - store.set(nick, socket.id); + if(f) store.set(nick, [].concat(store.get(nick), [socket.id]).filter(item => item !== undefined)); + else store.set(nick, socket.id); socket.nick = nick; } @@ -415,7 +415,7 @@ async function initChat() { }); socket.on('MSG', (data) => { if(data.msg === "" || !data.msg.replace(/\s/g, '').length) return; - io.to(data.room).emit('MSG', {nick: socket.nick, msg: data.msg}); + if(socket.rooms[data['room']]) io.to(data.room).emit('MSG', {nick: socket.nick, msg: data.msg}); }); socket.on('KICK', (data) => { if(socket.nick === data.room){ @@ -423,13 +423,15 @@ async function initChat() { let id: string = store.get(data.nick); if(id){ if(Array.isArray(id)) { - for(let i=0;i Date: Sat, 21 Dec 2019 17:23:00 -0600 Subject: [PATCH 9/9] Update documentation to match recent changes with config and chat --- docs/CONFIGURATION.md | 34 +++++++++++++++++----------------- docs/INSTALLATION.md | 4 ++-- docs/USAGE.md | 3 +++ templates/help.njk | 9 ++++++--- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 54e848b..43f4cfe 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1,29 +1,29 @@ ## Configuring Satyr ### Config file -All changes to satyr's config will go in the config/local.toml file +All changes to satyr's config will go in the config/config.yml file Some values you might want to change are ``` -[satyr] -registration = true +satyr: + registration: true # allow new users to register -rootRedirect = '/users/live' + rootRedirect: '/users/live' # the page users are directed to when they visit your site root -[server.http] -hsts = true +http: + hsts: true # enable strict transport security -[media] -record = true +media: + record: true # allow users to record VODs -[transcode] -adapative = true +transcode: + adapative: true # enable adaptive livestreaming # will help users with poor connections, but EXTREMELY cpu intensive # even 3 variants will max out most budget VPSs with a single stream -variants = 3 + variants: 3 # the number of adaptive streaming variants to generate # satyr will always copy the source stream # and the remaining variants will lower the quality incrementally @@ -31,15 +31,15 @@ variants = 3 # So the default setting of 3 will copy the source stream once # And generate two lower quality & bitrate variants -[bcrypt] -saltRounds = 12 +crypto: + saltRounds: 12 #change the number of rounds of bcrypt to fit your hardware #if you don't understand the implications, don't change this -[ircd] -enable = true -#enable IRC peering -#extremely beta +irc: + port: 6667 +#irc settings +#currently unused ``` ### Web Frontend diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 0f9dbd2..0058c81 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -27,7 +27,7 @@ source install/db_setup.sql; ``` Then start the server. ```bash -npm start +npm run start ``` It is reccomended that you run Satyr behind a TLS terminating reverse proxy, like nginx. @@ -45,4 +45,4 @@ npm update Then restart the server. ## Migrating Satyr -To backup and restore, you will need to export the mysqlDB. Restore the new database from the backup, then copy config/local.toml, config/jwt.pem, and the site directory to the new location. \ No newline at end of file +To backup and restore, you will need to export the mysqlDB. Restore the new database from the backup, then copy config and site directories to the new location. \ No newline at end of file diff --git a/docs/USAGE.md b/docs/USAGE.md index 38014d9..d8a35a1 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -52,6 +52,9 @@ The following commands are available: `/nick kawen (password)` Password is only required if kawen is a registered user. `/join kawen` Join the chatroom for kawen's stream and leave the previous room. `/kick lain` Available only in your own room if you are a streamer. Forcefully disconnect the user. +`/ban lain (time)` Ban a user from your room. Bans are based on IP address. The optional time is in minutes. The default is 30. +`/banlist` List the IPs currently banned from your room. +`/unban (ip)` self explanatory #### Streaming Users should stream to rtmp://example.tld/stream/examplestreamkey diff --git a/templates/help.njk b/templates/help.njk index 432eb09..fdfc607 100644 --- a/templates/help.njk +++ b/templates/help.njk @@ -4,9 +4,12 @@

Chatting

The webclient for chat can be accessed on the streamer's page, or at https://{{ domain }}/chat

The following commands are available:
-`/nick kawen (password)` Password is only required if kawen is a registered user.
-`/join kawen` Join the chatroom for kawen's stream and leave the previous room.
-`/kick cvcvcv` Available only in your own room if you are a streamer. Forcefully disconnect the user.
+/nick kawen (password) Password is only required if kawen is a registered user.
+/join kawen Join the chatroom for kawen's stream and leave the previous room.
+/kick cvcvcv Available only in your own room if you are a streamer. Forcefully disconnect the user.
+/ban cvcvcv (time) Ban a user from your room. Bans are based on IP address. The optional time is in minutes. The default is 30.
+/banlist List the IPs currently banned from your room.
+/unban (ip) self explanatory

Streaming

Users should stream to rtmp://{{ domain }}/stream/Stream-Key