diff --git a/.gitignore b/.gitignore
index fad7bdd..8c8153b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
 node_modules
 site/live
-config/local.toml
-config/jwt.pem
-config/generated.toml
+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/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index f9bf420..43f4cfe 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -1,27 +1,48 @@
 ## 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
-#allow new users to register
-rootRedirect = '/users/live'
-#the page users are directed to when they visit your site root
-[media]
-record = true
-#allow users to record VODs
-[bcrypt]
-saltRounds = 12
+satyr:
+  registration: true
+# allow new users to register
+  rootRedirect: '/users/live'
+# the page users are directed to when they visit your site root
+
+http:
+  hsts: true
+# enable strict transport security
+
+media:
+  record: true
+# 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
+
+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
-#unused for now
+
+irc:
+  port: 6667
+#irc settings
+#currently unused
 ```
 
 ### 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..0058c81 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,10 +25,9 @@ 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
+npm run start
 ```
 
 It is reccomended that you run Satyr behind a TLS terminating reverse proxy, like nginx.
@@ -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 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/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: '<iname>'
+  domain: '<domain>'
+  email: '<email>'
+  registration: false
+
+media:
+  record: false
+  ffmpeg: '<ffmpeg>'
+
+http:
+  # uncomment to set HSTS when SSL is ready
+  #hsts: true
+
+database:
+  user: '<dbuser>'
+  password: '<dbpass>'
+  database: '<dbname>'
+  host: '<dbhost>'
+
+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#<iname>#$name#g" -e "s#<domain>#$domain#g" -e "s#<ffmpeg>#$ffmpeg#g" -e "s#<dbuser>#$dbuser#g" -e "s#<dbname>#$dbname#g" -e "s#<dbpass>#$dbpass#g" -e "s#<dbhost>#$dbhost#g" -e "s#<email>#$email#g" install/template.local.toml > config/generated.toml
+sed -e "s#<iname>#$name#g" -e "s#<domain>#$domain#g" -e "s#<ffmpeg>#$ffmpeg#g" -e "s#<dbuser>#$dbuser#g" -e "s#<dbname>#$dbname#g" -e "s#<dbpass>#$dbpass#g" -e "s#<dbhost>#$dbhost#g" -e "s#<email>#$email#g" install/config.example.yml > config/generated.yml
 sed -e "s#<dbuser>#$dbuser#g" -e "s#<dbname>#$dbname#g" -e "s#<dbpass>#$dbpass#g" -e "s#<dbhost>#$dbhost#g" -e "s#<dbclient>#$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 = '<iname>'
-domain = '<domain>'
-email = '<email>'
-registration = false
-
-[media]
-record = false
-ffmpeg = '<ffmpeg>'
-
-[server.http]
-# uncomment to set HSTS when SSL is enabled
-# hsts = true
-
-[database]
-user = '<dbuser>'
-password = '<dbpass>'
-database = '<dbname>'
-host = '<dbhost>'
diff --git a/package-lock.json b/package-lock.json
index 9c53b90..b0e10eb 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": {
@@ -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",
@@ -2043,6 +2063,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",
@@ -2287,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",
@@ -2712,6 +2742,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",
@@ -2855,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",
@@ -3016,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",
@@ -3188,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 f98013f..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,10 +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<config.restrictedNames.length;i++){
-		if (name === config.restrictedNames[i]) return {"error":"restricted name"};
+	for(let i=0;i<config['satyr']['restrictedNames'].length;i++){
+		if (name === config['satyr']['restrictedNames'][i]) return {"error":"restricted name"};
 	}
 	let r: boolean = await db.addUser(name, password);
 	if(r) {
@@ -61,4 +56,4 @@ async function login(name: string, password: string){
 	return false;
 }
 
-export { init, register, update, changepwd, changesk, login };
\ No newline at end of file
+export { register, update, changepwd, changesk, login };
\ No newline at end of file
diff --git a/src/cli.ts b/src/cli.ts
index 3ec41ac..110ecc6 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -1,8 +1,7 @@
 import * as db from "./database"
 import * as flags from "flags";
-import * as config from "config"
 
-db.init(config.database, config.bcrypt);
+db.init();
 
 flags.defineString('adduser', '', 'User to add');
 flags.defineString('rmuser', '', 'User to remove');
diff --git a/src/config.ts b/src/config.ts
new file mode 100644
index 0000000..f15fbc9
--- /dev/null
+++ b/src/config.ts
@@ -0,0 +1,49 @@
+import {parseAsYaml as parse} from "parse-yaml";
+import {readFileSync as read} from "fs";
+var localconfig: Object = parse(read('config/config.yml'));
+const config: Object = {
+	crypto: Object.assign({ 
+		saltRounds: 12 
+	}, localconfig['crypto']),
+	satyr: Object.assign({
+	   name: '',
+	   domain: '',
+	   registration: false,
+	   restrictedNames: [ 'live' ],
+	   rootredirect: '/users/live',
+	   version: process.env.npm_package_version,
+	 }, localconfig['satyr']),
+	ircd: Object.assign({
+	   port: 6667,
+	}, localconfig['ircd']),
+	database: Object.assign({
+	   host: 'localhost',
+	   user: 'satyr',
+	   password: '',
+	   database: 'satyr_db',
+	   connectionLimit: '50',
+	   connectionTimeout: '1000',
+	   insecureAuth: false,
+	   debug: false }, localconfig['database']),
+	rtmp: Object.assign({
+	  port: 1935,
+	  chunk_size: 6000,
+	  gop_cache: true,
+	  ping: 30,
+	  ping_timeout: 60 }, localconfig['rtmp']),
+	http: Object.assign({ 
+		hsts: false, directory: './site', port: 8000 
+	}, localconfig['http']),
+	media: Object.assign({
+	   record: false,
+	   publicEndpoint: 'live',
+	   privateEndpoint: 'stream',
+	   ffmpeg: ''
+	}, localconfig['media']),
+	transcode: Object.assign({ 
+		adapative: false, 
+		variants: 3, 
+		format: 'dash' 
+	}, localconfig['transcode'])
+};
+export { config };
\ No newline at end of file
diff --git a/src/controller.ts b/src/controller.ts
deleted file mode 100644
index c87f0c9..0000000
--- a/src/controller.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import * as mediaserver from "./server";
-import * as db from "./database";
-import * as api from "./api";
-import * as http from "./http";
-import * as cleanup from "./cleanup";
-import * as config from "config";
-
-async function run() {
-	const dbcfg: object = config.database;
-	const bcryptcfg: object = config.bcrypt;
-	const satyr: object = {
-		privateEndpoint: config.media.privateEndpoint,
-		publicEndpoint: config.media.publicEndpoint,
-		record: config.media.record,
-		registration: config.satyr.registration,
-		webFormat: config.satyr.webFormat,
-		restrictedNames: config.satyr.restrictedNames,
-		name: config.satyr.name,
-		domain: config.satyr.domain,
-		email: config.satyr.email,
-		rootredirect: config.satyr.rootredirect,
-		version: process.env.npm_package_version,
-		directory: config.server.http.directory,
-		ffmpeg: config.media.ffmpeg
-	};
-	const nms: object = {
-		logType: config.server.logs,
-		rtmp: {
-			port: config.server.rtmp.port,
-			chunk_size: config.server.rtmp.chunk_size,
-			gop_cache: config.server.rtmp.gop_cache,
-			ping: config.server.rtmp.ping,
-			ping_timeout: config.server.rtmp.ping_timeout,
-		},
-		/*http: {
-			port: config.server.http.port + 1,
-			mediaroot: config.server.http.directory,
-			allow_origin: config.server.http.allow_origin
-		},
-		trans: {
-			ffmpeg: config.media.ffmpeg,
-			tasks: [
-				{
-					app: config.media.publicEndpoint,
-					hls: config.transcode.hls,
-					hlsFlags: config.transcode.hlsFlags,
-					dash: config.transcode.dash,
-					dashFlags: config.transcode.dashFlags
-				}
-			]
-		},*/
-		auth: {
-			api: config.server.api,
-			api_user: config.server.api_user,
-			api_pass: config.server.api_pass
-		}
-		
-	};
-	db.init(dbcfg, bcryptcfg);
-	await cleanup.init();
-	api.init(satyr);
-	http.init(satyr, config.server.http, config.ircd);
-	mediaserver.init(nms, satyr);
-	console.log(`Satyr v${process.env.npm_package_version} ready`);
-}
-run();
-export { run };
\ No newline at end of file
diff --git a/src/database.ts b/src/database.ts
index df0f3d2..6e1032f 100644
--- a/src/database.ts
+++ b/src/database.ts
@@ -1,20 +1,21 @@
 import * as mysql from "mysql";
 import * as bcrypt from "bcrypt";
 import * as crypto from "crypto";
+import { config } from "./config";
 import { resolve } from "url";
-var raw: any;
-var cryptoconfig: any;
+var raw;
+var cryptoconfig: Object;
 
-function init (db: object, bcrypt: object){
-	raw = mysql.createPool(db);
-	cryptoconfig = bcrypt;
+function init (){
+	raw = mysql.createPool(config['database']);
+	cryptoconfig = config['crypto'];
 }
 
 async function addUser(name: string, password: string){
 	//does not respect registration setting in config
 	if(password === '') return false;
 	let key: string = await genKey();
-	let hash: string = await bcrypt.hash(password, cryptoconfig.saltRounds);
+	let hash: string = await bcrypt.hash(password, cryptoconfig['saltRounds']);
 	let dupe = await query('select * from users where username='+raw.escape(name));
 	if(dupe[0]) return false;
 	await query('INSERT INTO users (username, password_hash, stream_key, record_flag) VALUES ('+raw.escape(name)+', '+raw.escape(hash)+', '+raw.escape(key)+', 0)');
@@ -54,7 +55,7 @@ async function validatePassword(username: string, password: string){
 }
 
 async function hash(pwd){
-	return await bcrypt.hash(pwd, cryptoconfig.saltRounds);
+	return await bcrypt.hash(pwd, cryptoconfig['saltRounds']);
 }
 
 export { query, raw, init, addUser, rmUser, validatePassword, hash, genKey };
\ No newline at end of file
diff --git a/src/http.ts b/src/http.ts
index 47b00e6..c45fffa 100644
--- a/src/http.ts
+++ b/src/http.ts
@@ -5,9 +5,10 @@ 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";
+import { config } from "./config";
 import { readdir, readFileSync, writeFileSync } from "fs";
 import { JWT, JWK } from "jose";
 import { strict } from "assert";
@@ -18,9 +19,11 @@ const app = express();
 const server = http.createServer(app);
 const io = socketio(server);
 const store = dirty();
+var banlist;
 var jwkey;
 try{
 	jwkey = JWK.asKey(readFileSync('./config/jwt.pem'));
+	console.log('Found key for JWT signing.');
 } catch (e) {
 	console.log("No key found for JWT signing, generating one now.");
 	jwkey = JWK.generateSync('RSA', 2048, { use: 'sig' });
@@ -28,23 +31,23 @@ try{
 }
 var njkconf;
 
-async function init(satyr: any, http: object, ircconf: any){
+async function init(){
 	njk.configure('templates', {
 		autoescape	: true,
 		express   	: app,
 		watch		: false
 	});
-	njkconf ={
-		sitename: satyr.name,
-		domain: satyr.domain,
-		email: satyr.email,
-		rootredirect: satyr.rootredirect,
-		version: satyr.version
+	njkconf = {
+		sitename: config['satyr']['name'],
+		domain: config['satyr']['domain'],
+		email: config['satyr']['email'],
+		rootredirect: config['satyr']['rootredirect'],
+		version: config['satyr']['version']
 	};
 	app.use(cookies());
 	app.use(bodyparser.json());
 	app.use(bodyparser.urlencoded({ extended: true }));
-	if(http['hsts']){
+	if(config['http']['hsts']){
 		app.use((req, res, next) => {
 			res.append('Strict-Transport-Security', 'max-age=5184000');
 			next();
@@ -52,11 +55,11 @@ async function init(satyr: any, http: object, ircconf: 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)) {
@@ -65,19 +68,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);
-	server.listen(http['port']);
+	banlist = new dirty('./config/bans.db').on('load', () => {initChat()});
+	server.listen(config['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['username'], [].concat(store.get(t['username']), socket.id).filter(item => item !== undefined));
+			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;
@@ -89,8 +95,12 @@ async function chgNick(socket, nick, f?: boolean) {
 	for(let i=1;i<rooms.length;i++){
 		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);
+	if(store.get(socket.nick)) {
+		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));
+	else store.set(nick, socket.id);
 	socket.nick = nick;
 }
 
@@ -328,30 +338,26 @@ async function initSite(openReg) {
 	});
 }
 
-async function initChat(ircconf: any) {
-	//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});
-		});
-	}
+async function initChat() {
+	//set a cookie to request same nick
+	
 	//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]){
+				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');
+						return;
+					}
+				}
 				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');
 		});
@@ -372,24 +378,22 @@ async function initChat(ircconf: any) {
 		});
 		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<rooms.length;i++){
-				if(ircconf.enable) irc.part(socket.nick, rooms[i]);
 				io.to(rooms[i]).emit('ALERT', socket.nick+' disconnected');
 			}
-			if(ircconf.enable) irc.unregisterUser(socket.nick);
+			if(Array.isArray(store.get(socket.nick))) {
+				store.set(socket.nick, store.get(socket.nick).filter(item => item !== socket.id))
+				if(store.get(socket.nick) !== [])
+				return;
+			}
 			store.rm(socket.nick);
 		});
 		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,27 +406,102 @@ 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);
 			}
 		});
 		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);
+			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){
 			//find client with data.nick
 				let id: string = store.get(data.nick);
 				if(id){
+					if(Array.isArray(id)) {
+						for(let i=0;i<id.length;i++){
+							io.sockets.connected[id[i]].leave(data.room);
+						}
+						io.in(data.room).emit('ALERT', data.nick+' has been kicked.');
+						return;
+					}
 					let target = io.sockets.connected[id];
 					io.in(data.room).emit('ALERT', data.nick+' has been kicked.');
-					target.disconnect(true);
+					target.leave(data.room);
 				}
 				else socket.emit('ALERT', 'No such user found.');
 			}
 			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){
+					if(Array.isArray(id)) {
+						for(let i=0;i<id.length;i++){
+							let target = io.sockets.connected[id[i]];
+							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.leave(data['room']);
+						}
+						io.to(data['room']).emit('ALERT', data['nick']+' was banned.');
+						return;
+					}
+					let target = io.sockets.connected[id];
+					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.leave(data['room']);
+					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<bans.length;i++){
+						str += bans[i]+', ';
+					}
+					socket.emit('ALERT', 'Banned IP adresses: '+str.substring(0, str.length - 2));
+					return;
+				}
+				socket.emit('ALERT', 'No one is banned from this room');
+			}
+			else socket.emit('ALERT', 'Not authorized to do that.');
+		});
+	});
+	//socketio spam
+	const socketAS = new socketSpam({
+		banTime:            20,
+		kickThreshold:      10,
+		kickTimesBeforeBan: 3,
+		banning:            true,
+		io:                 io
+	});
+	socketAS.event.on('ban', (socket) => {
+		let rooms = Object.keys(socket.rooms);
+		for(let i=1;i<rooms.length;i++){
+			io.to(rooms[i]).emit('ALERT', socket.nick+' was banned.');
+		}
+		store.rm(socket.nick);
 	});
 }
 
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..c384b81
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,15 @@
+import * as mediaserver from "./server";
+import * as db from "./database";
+import * as http from "./http";
+import * as cleanup from "./cleanup";
+import { config } from "./config";
+
+async function run() {
+	await db.init();
+	await cleanup.init();
+	await http.init();
+	await mediaserver.init();
+	console.log(`Satyr v${config['satyr']['version']} ready`);
+}
+run();
+export { run };
\ No newline at end of file
diff --git a/src/irc.js b/src/irc.js
deleted file mode 100644
index 86359e7..0000000
--- a/src/irc.js
+++ /dev/null
@@ -1,212 +0,0 @@
-// written by crushv <nik@telekem.net>
-// 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 62d8a8d..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();
@@ -34,23 +35,23 @@ 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 }, ()=>{;});
+				mkdir(config['http']['directory']+'/'+config['media']['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(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,19 +95,48 @@ 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(user: string, key: string): Promise<string[]>{
+	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<config['transcode']['variants'];i++){
+			args = args.concat(['-map', '0:2']);
+		}
+		args = args.concat(['-map', '0:1', '-c:a', 'copy', '-c:v:0', 'copy']);
+		for(let i=1;i<config['transcode']['variants'];i++){
+			args = args.concat(['-c:v:'+i, 'libx264',]);
+		}
+		for(let i=1;i<config['transcode']['variants'];i++){
+			let crf: number = Math.floor(18 + (i * 8)) > 51 ? 51 : Math.floor(18 + (i * 7));
+			args = args.concat(['-crf:'+i, ''+crf]);
+		}
+		for(let i=1;i<config['transcode']['variants'];i++){
+			let bv: number = Math.floor((5000 / config['transcode']['variants']) * (config['transcode']['variants'] - i));
+			args = args.concat(['-b:v:'+i, ''+bv]);
+		}
+	}
+	else {
+		args = args.concat(['-c:a', 'copy', '-c:v', 'copy']);
+	}
+	if(config['transcode']['format'] === 'dash')
+	args = args.concat(['-remove_at_exit', '1', '-seg_duration', '1', '-window_size', '30', '-f', 'dash', config['http']['directory']+'/'+config['media']['publicEndpoint']+'/'+user+'/index.mpd']);
+	else if(config['transcode']['format'] === 'hls')
+	args = args.concat(['-remove_at_exit', '1', '-hls_time', '1', '-hls_list_size', '30', '-f', 'hls', config['http']['directory']+'/'+config['media']['publicEndpoint']+'/'+user+'/index.m3u8']);
+	return args;
+}
 export { init };
\ No newline at end of file
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 @@
 	<link rel="stylesheet" type="text/css" href="/local.css">
 	<link rel="icon" type="image/svg" href="/logo.svg">
 	<title>{{ sitename }}</title>
+	{% block head %}
+	{% endblock %}
 </head>
 <body>
 	<div id="wrapper">
diff --git a/templates/chat.html b/templates/chat.html
index a8b30ef..5f9af78 100644
--- a/templates/chat.html
+++ b/templates/chat.html
@@ -36,6 +36,24 @@
         else if(m.startsWith('/list')){
           socket.emit('LIST', {room: room});
         }
+        else if(m.startsWith('/banlist')){
+          socket.emit('LISTBAN', {
+            room: room,
+          });
+        }
+        else if(m.startsWith('/ban')){
+          socket.emit('BAN', {
+            room: room,
+            nick: m.split(' ')[1],
+            time: (1 * m.split(' ')[2])
+          });
+        }
+        else if(m.startsWith('/unban')){
+          socket.emit('UNBAN', {
+            room: room,
+            ip: m.split(' ')[1]
+          });
+        }
         else socket.emit('MSG', {room: room, msg: m});
         document.getElementById('m').value = '';
       }
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 @@
 <h4>Chatting</h4>
 The webclient for chat can be accessed on the streamer's page, or at <a href="https://{{ domain }}/chat">https://{{ domain }}/chat</a></br></br>
 The following commands are available:</br>
-`/nick kawen (password)` Password is only required if kawen is a registered user.</br>
-`/join kawen` Join the chatroom for kawen's stream and leave the previous room.</br>
-`/kick cvcvcv` Available only in your own room if you are a streamer. Forcefully disconnect the user.</br>
+<code><a>/nick kawen (password)</a></code> Password is only required if kawen is a registered user.</br>
+<code><a>/join kawen</a></code> Join the chatroom for kawen's stream and leave the previous room.</br>
+<code><a>/kick cvcvcv</a></code> Available only in your own room if you are a streamer. Forcefully disconnect the user.</br>
+<code><a>/ban cvcvcv (time)</a></code> Ban a user from your room. Bans are based on IP address. The optional time is in minutes. The default is 30.</br>
+<code><a>/banlist</a></code> List the IPs currently banned from your room.</br>
+<code><a>/unban (ip)</a></code> self explanatory</br>
 
 <h4>Streaming</h4>
 Users should stream to <a>rtmp://{{ domain }}/stream/Stream-Key</a></br></br>
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 %}
+  <script src="/videojs/video.min.js"></script>
+  <link rel="stylesheet" type="text/css" href="/videojs/video-js.min.css">
+{% endblock %}
 {% block content %}
 <script>
 function newPopup(url) {
@@ -16,10 +20,11 @@ function newPopup(url) {
 			<iframe src="/chat?room={{ username }}" frameborder="0" style="width: 100%;height: 100%; min-height: 500px;" allowfullscreen></iframe>
 		</div>
 	</div>
-  <script>window.HELP_IMPROVE_VIDEOJS = false;</script>
-  <script src="/videojs/video.min.js"></script>
-  <link rel="stylesheet" type="text/css" href="/videojs/video-js.min.css">
+  </br>
+    <noscript>The webclients for the stream and the chat require javascript, but feel free to use the direct links above!</br></br></noscript>
+    {{ about | escape }}
   <script>
+    window.HELP_IMPROVE_VIDEOJS = false;
     var player = videojs('live-video', {
       html: {
         nativeCaptions: false,
@@ -34,7 +39,5 @@ function newPopup(url) {
         type: 'application/dash+xml'
       });
     })
-    </script></br>
-    <noscript>The webclients for the stream and the chat require javascript, but feel free to use the direct links above!</br></br></noscript>
-    {{ about | escape }}
+  </script>
 {% endblock %}
\ No newline at end of file