diff --git a/README.md b/README.md
index db204c1..1d2c4a7 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ System dependencies: A stable version of node>=10, mysql3 (or a compatible imple
 
 ### Setup Instructions
 ```bash
-git clone https://gitlab.com/knotteye/satyr.git
+git clone https://pond.waldn.net/git/knotteye/satyr.git
 cd satyr
 npm install
 npm run setup
diff --git a/docs/CHAT.md b/docs/CHAT.md
index f32ecae..a3bebd7 100644
--- a/docs/CHAT.md
+++ b/docs/CHAT.md
@@ -3,7 +3,7 @@ This is not a guide to using the webchat, this a reference point for writing cli
 
 Satyr's webchat is based on [socket.io](https://socket.io/), you can find clients for [Java](https://github.com/socketio/socket.io-client-java), [C++](https://github.com/socketio/socket.io-client-cpp), [Swift](https://github.com/socketio/socket.io-client-swift), [Dart](https://github.com/rikulo/socket.io-client-dart), and probably more.
 
-Socket.IO is loosely reminiscent of IRC in that you will receive events from the server and sent events to it. The following is a list of incoming and outgoing events you will need to handle or send. If you would like to see examples, templates/chat.html is implementation used in the webclient by satyr.
+Socket.IO is loosely reminiscent of IRC in that you will receive events from the server and sent events to it. The following is a list of incoming and outgoing events you will need to handle or send. If you would like to see examples, templates/chat.html is the implementation used in the webclient by satyr.
 
 # Incoming Events
 These are events you will recieve from the server that need to be handled in some way.
@@ -53,12 +53,12 @@ This is a request to set the client's nickname. The data attached to a NICK even
 	password: "the optional password"
 }
 ```
-During the initial connect of the client, the server will check for the "Authorization" cookie. If the cookie is a valid, signed JWT, the client will be assigned the nickname of the user that cookie belongs to. If it doesn't exist or is invalid, the client will be assigned a nickname of the form Guest+some integer.
+During the initial connect of the client, the server will check for the "Authorization" cookie. If the cookie is a valid, signed JWT the client will be assigned the nickname of the user that cookie belongs to. If it doesn't exist or is invalid, the client will be assigned a nickname of the form Guest+some integer.
 
 The server will send an alert notifying the client of either the nickname change, or some error.
 
 ## MSG
-This is a chat message to send to room. It should be a JSON object in the following format:
+This is a chat message to send to a room. It should be a JSON object in the following format:
 ```
 {
 	room: "the room to send the messag to",
@@ -117,10 +117,10 @@ A request to unban an IP address. It can only be done by the owner of the room.
 
 # Final Notes
 
-Sending more than 10 messages a second will cause the server to kick your client. If kicked this way 3 times, the client will be banned for 20 minutes.
+Sending more than 10 messages per second will cause the server to kick your client. If kicked this way 3 times, the client will be banned for 20 minutes.
 
 Kicked or banned users will not be notified of this through an event.
 
-The server *will* send your own MSG events back to you, you will need to parse them out if you want to append them immediately.
+The server *will* send your own MSG events back to you, you will need to parse them out if you want to display them immediately.
 
 Clients who successfully authenticate as a registered user, through either a password or a signed JWT, can ignore nickname collision and have as many connections as they wish.
\ No newline at end of file
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 2cd6bb6..0c1853a 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -7,11 +7,23 @@ Some values you might want to change are
 satyr:
   registration: true
 # allow new users to register
+  port: 8000
+# the port to serve http on
 
 http:
   hsts: true
 # enable strict transport security
 
+rtmp:
+  port: 1935
+# change the port to serve rtmp on
+  cluster: false
+# enable clustering for the RTMP server
+# clustering is an attempt to take better advantage of multi threaded systems in spite of node.js being single-threaded
+# satyr will spawn one RTMP Worker per CPU core, and round-robin incoming connections between workers
+# If you turn this on, satyr will no longer be able to reliably serve RTMP streams to clients
+# Your users will have to use DASH instead
+
 media:
   record: true
 # allow users to record VODs
@@ -36,7 +48,7 @@ transcode:
 # https://trac.ffmpeg.org/wiki/HWAccelIntro is a good place to start
 
 # having more than 4-5 variants will start giving diminishing returns on stream quality for cpu load
-# if you can't afford to generate at least 3 variants, it's reccomended to leave adaptive streaming off
+# if you can't afford to generate at least 3 variants, it's recommended to leave adaptive streaming off
 
 crypto:
   saltRounds: 12
@@ -44,12 +56,11 @@ crypto:
 # if you don't understand the implications, don't change this
 
 chat:
-# the following settings are for chat mirroring bots
-# users will still need to choose which channel to mirror
+# the following settings are for chat bridging bots
+# users will still need to choose which channel to bridge
 # for their chat at /profile/chat
   irc:
     enabled: true
-# enable irc mirroring
     server: chat.freenode.net
     port: 6697
     tls: true
@@ -61,7 +72,6 @@ chat:
 
   discord:
     enabled: true
-# enabled discord integration
     token: abcdefghijklmnopqrstuvwxyz
 # the access token for the bot
 # note that the bot will mirror every channel matching the name the user has chosen
@@ -74,6 +84,20 @@ chat:
 # access token for the twitch chat bot
 # this is not the account password, you will need to generate a token here:
 # https://twitchapps.com/tmi/
+
+  xmpp:
+    enabled: true
+    server: 'example.com'
+    port: 5222
+    jid: 'exampleBot@example.com'
+    password: 'abcde'
+# connection settings for the bot
+    nickname: 'SatyrChat
+# the nickname the bot will join MUCs with
+
+# note that for the best experience you should set the default number of history messages to 0 for the MUC
+# The bot will attempt to request 0 history messages anyway, and will also attempt to ignore any history messages it receives
+# but both of these things are unreliable
 ```
 
 ### Web Frontend
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
index 136d9a3..cf2b82a 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -4,14 +4,14 @@ A more detailed walkthrough.
 ### System Dependencies
 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 v10.
-If the version in your distro's package manager is different, you can install 'n' through npm to manage node versions.
+If the version in your distro's package manager is different, you can install [n](https://www.npmjs.com/package/n) through npm to manage node versions.
 
 ### Installing Satyr
 Before starting, you should create a system user to run the satyr service.
 
 Clone the repository and change to the directory
 ```bash
-git clone https://gitlab.com/knotteye/satyr.git
+git clone https://pond.waldn.net/git/knotteye/satyr.git
 cd satyr
 ```
 Install nodejs dependencies
diff --git a/docs/REST.md b/docs/REST.md
index 9058efb..784b883 100644
--- a/docs/REST.md
+++ b/docs/REST.md
@@ -32,6 +32,7 @@ Configuration of the instance relating to media
 ```
 {
 	rtmp: {
+		cluster: false,
 		port: 1935,
 		ping_timeout: 60
 	},
diff --git a/docs/USAGE.md b/docs/USAGE.md
index 1469cf2..0e57a77 100644
--- a/docs/USAGE.md
+++ b/docs/USAGE.md
@@ -1,30 +1,19 @@
 ## Satyr Usage
 
 ### Administration
-Satyr needs access to port 1935 for RTMP streams, and will serve HTTP on port 8000. The ports can be changed with follow config lines.
+Satyr needs access to port 1935 for RTMP streams, and will serve HTTP on port 8000. See CONFIGURATION.md for details on changing this.
 
-For HTTPS, run a reverse proxy in front of satyr. An example nginx block is shown below.
-```
-server {
-    port 80;
-    port [::]80;
-    server_name example.tld;
-    return https://$server_name$request_uri 301;
-}
-server {
-    port 443 ssl;
-    port [::]443 ssl;
-    server_name example.tld;
+For HTTPS, run a reverse proxy in front of satyr. An example nginx config can be found at install/satyr.nginx
+An example systemd service can also be at install/satyr.service
 
-    ssl_trusted_certificate   /etc/letsencrypt/live/example.tld/chain.pem;
-    ssl_certificate               /etc/letsencrypt/live/example.tld/fullchain.pem;
-    ssl_certificate_key        /etc/letsencrypt/live/example.tld/privkey.pem;
+#### CLI
+Satyr's CLI tool can be run with `npm run cli` or `node_modules/.bin/ts-node src/cli.ts`
+
+It's not very complex. The following commands are available:
+* `npm run cli -- --adduser sally --password "hunter12"` to create user sally with the password hunter12
+* `npm run cli -- --rmuser sally` to remove user sally
+* `npm run cli -- --invite` to generate an invite code used for creating account even when registration is closed
 
-    location / {
-        proxy_pass http://127.0.0.1:8000/;
-    }
-}
-```
 
 ### Users
 
@@ -35,13 +24,14 @@ Stream keys can be changed at example.tld/changesk, and passwords at /changepwd
 #### Chat
 Chat is based on Socket.IO, and can be accessed through the webclient at /chat.
 Chatting and changing a nickname do not require authentication, but the usernames of streamers are reserved.
+
 The following commands are available:
-`/nick sally (password)` Password is only required if sally is a registered user.
-`/join sally` Join the chatroom for sally's stream and leave the previous room.
-`/kick bob` Available only in your own room if you are a streamer. Forcefully disconnect the user.
-`/ban bob (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
+* `/nick sally (password)` Password is only required if sally is a registered user.
+* `/join sally` Join the chatroom for sally's stream and leave the previous room.
+* `/kick bob` Available only in your own room if you are a streamer. Forcefully disconnect the user.
+* `/ban bob (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
 
 You can set up mirroring to and from webchat rooms and IRC channels, twitch streams, and discord server channels.
 More information is in CONFIGURATION.md
diff --git a/install/config.example.yml b/install/config.example.yml
index 8925c03..74fc8f2 100644
--- a/install/config.example.yml
+++ b/install/config.example.yml
@@ -51,10 +51,11 @@ chat:
 
   xmpp:
     enabled: false
-    server:
+    server: 
     port: 5222
-    nickname: 'SatyrChat'
-    username: 'SatyrChat'
+    jid: 
+    password: 
+    nickname: 
 
   twitch:
     enabled: false
diff --git a/install/satyr.nginx b/install/satyr.nginx
new file mode 100644
index 0000000..fb3dc2f
--- /dev/null
+++ b/install/satyr.nginx
@@ -0,0 +1,25 @@
+server {
+    port 80;
+    port [::]80;
+    server_name example.tld;
+    return https://$server_name$request_uri 301;
+}
+server {
+    port 443 ssl;
+    port [::]443 ssl;
+    server_name example.tld;
+
+    ssl_trusted_certificate	/etc/letsencrypt/live/example.tld/chain.pem;
+    ssl_certificate			/etc/letsencrypt/live/example.tld/fullchain.pem;
+    ssl_certificate_key		/etc/letsencrypt/live/example.tld/privkey.pem;
+
+    location / {
+        proxy_pass http://127.0.0.1:8000/;
+    }
+
+	location ~* \.(mpd|m4s|mp4)$ {
+		# nginx can serve static files faster than node
+		# this should improve performance
+		root /opt/satyr/site/;
+	}
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index c5e1872..e70e2b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,30 @@
       "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.67.tgz",
       "integrity": "sha512-R48tgL2izApf+9rYNH+3RBMbRpPeW3N8f0I9HMhggeq4UXwBDqumJ14SDs4ctTMhG11pIOduZ4z3QWGOiMc9Vg=="
     },
+    "@xmpp/jid": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/@xmpp/jid/-/jid-0.0.2.tgz",
+      "integrity": "sha1-DVKMqdWNr8gzZlVk/+YvMyoxZ/I="
+    },
+    "@xmpp/streamparser": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@xmpp/streamparser/-/streamparser-0.0.6.tgz",
+      "integrity": "sha1-EYAz6p23yGoctGED8mnr/3n28eo=",
+      "requires": {
+        "@xmpp/xml": "^0.1.3",
+        "inherits": "^2.0.3",
+        "ltx": "^2.5.0"
+      }
+    },
+    "@xmpp/xml": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@xmpp/xml/-/xml-0.1.3.tgz",
+      "integrity": "sha1-HxQ5nlPkGWiFWGmPbGLnHjmoam4=",
+      "requires": {
+        "inherits": "^2.0.3",
+        "ltx": "^2.6.2"
+      }
+    },
     "a-sync-waterfall": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
@@ -46,6 +70,17 @@
       "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
       "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
     },
+    "ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
     "ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@@ -108,6 +143,14 @@
       "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
       "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
     },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
     "asn1.js": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.2.0.tgz",
@@ -118,16 +161,41 @@
         "minimalistic-assert": "^1.0.0"
       }
     },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+    },
     "async-limiter": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
       "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
     },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+    },
+    "aws4": {
+      "version": "1.10.1",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz",
+      "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
+    },
     "backo2": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
       "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
     },
+    "backoff": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.3.0.tgz",
+      "integrity": "sha1-7nx+OAk/kuRyhZ22NedlJFT8Ieo="
+    },
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -157,6 +225,21 @@
         "node-pre-gyp": "0.15.0"
       }
     },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      },
+      "dependencies": {
+        "tweetnacl": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+        }
+      }
+    },
     "better-assert": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
@@ -236,6 +319,11 @@
         "fill-range": "^7.0.1"
       }
     },
+    "browser-request": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz",
+      "integrity": "sha1-ns5bWsqJopkyJC4Yv5M975h2zBc="
+    },
     "buffer-from": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@@ -251,6 +339,11 @@
       "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
       "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
     },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
     "chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -300,6 +393,14 @@
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
       "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
     "commander": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz",
@@ -390,6 +491,14 @@
         "ts-toolbelt": "^6.9.0"
       }
     },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
     "dateformat": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
@@ -431,6 +540,11 @@
       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
       "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
     },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
     "delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -506,6 +620,15 @@
         }
       }
     },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -685,6 +808,26 @@
         }
       }
     },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+    },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+    },
     "fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -728,6 +871,21 @@
       "resolved": "https://registry.npmjs.org/flags/-/flags-0.1.3.tgz",
       "integrity": "sha1-lh0vyM3zZp1jBB4w5bJK2tNvV1g="
     },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
     "forwarded": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -772,6 +930,14 @@
         "wide-align": "^1.1.0"
       }
     },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
     "glob": {
       "version": "7.1.6",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -794,6 +960,20 @@
         "is-glob": "^4.0.1"
       }
     },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+    },
+    "har-validator": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+      "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+      "requires": {
+        "ajv": "^6.12.3",
+        "har-schema": "^2.0.0"
+      }
+    },
     "has-binary2": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
@@ -824,6 +1004,33 @@
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
     },
+    "hash-base": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+      "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+      "requires": {
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.6.0",
+        "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.2.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+          "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+        }
+      }
+    },
     "http-errors": {
       "version": "1.7.2",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
@@ -843,6 +1050,16 @@
         }
       }
     },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
     "iconv": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/iconv/-/iconv-2.2.3.tgz",
@@ -955,11 +1172,21 @@
       "resolved": "https://registry.npmjs.org/is-port-available/-/is-port-available-0.1.5.tgz",
       "integrity": "sha512-/r7UZAQtfgDFdhxzM71jG0mkC4oSRA513cImMILdRe/+UOIe0Se/D/Z7XCua4AFg5k4Zt3ALMGaC1W3FzlrR2w=="
     },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
       "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
     },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
     "jose": {
       "version": "1.15.1",
       "resolved": "https://registry.npmjs.org/jose/-/jose-1.15.1.tgz",
@@ -968,11 +1195,47 @@
         "asn1.js": "^5.2.0"
       }
     },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
     "lodash": {
       "version": "4.17.20",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
       "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
     },
+    "lodash.assign": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+      "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
+    },
     "lodash.camelcase": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@@ -988,6 +1251,14 @@
       "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
       "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
     },
+    "ltx": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.10.0.tgz",
+      "integrity": "sha512-RB4zR6Mrp/0wTNS9WxMvpgfht/7u/8QAC9DpPD19opL/4OASPa28uoliFqeDkLUU8pQ4aeAfATBZmz1aSAHkMw==",
+      "requires": {
+        "inherits": "^2.0.4"
+      }
+    },
     "make-error": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
@@ -1001,6 +1272,16 @@
         "make-error": "^1.3.5"
       }
     },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
     "media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -1171,6 +1452,79 @@
         "tar": "^4.4.2"
       }
     },
+    "node-xmpp-client": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/node-xmpp-client/-/node-xmpp-client-3.2.0.tgz",
+      "integrity": "sha1-r0Un3wzFq9JpDLohOcwezcgeoYk=",
+      "requires": {
+        "browser-request": "^0.3.3",
+        "debug": "^2.2.0",
+        "md5.js": "^1.3.3",
+        "minimist": "^1.2.0",
+        "node-xmpp-core": "^5.0.9",
+        "request": "^2.65.0",
+        "ws": "^1.1.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        },
+        "ws": {
+          "version": "1.1.5",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz",
+          "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==",
+          "requires": {
+            "options": ">=0.0.5",
+            "ultron": "1.0.x"
+          }
+        }
+      }
+    },
+    "node-xmpp-core": {
+      "version": "5.0.9",
+      "resolved": "https://registry.npmjs.org/node-xmpp-core/-/node-xmpp-core-5.0.9.tgz",
+      "integrity": "sha1-XCjCjtsfs/i+uixnYHd2E/SPNCo=",
+      "requires": {
+        "@xmpp/jid": "^0.0.2",
+        "@xmpp/streamparser": "^0.0.6",
+        "@xmpp/xml": "^0.1.3",
+        "debug": "^2.2.0",
+        "inherits": "^2.0.1",
+        "lodash.assign": "^4.0.0",
+        "node-xmpp-tls-connect": "^1.0.1",
+        "reconnect-core": "https://github.com/dodo/reconnect-core/tarball/merged"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "node-xmpp-tls-connect": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/node-xmpp-tls-connect/-/node-xmpp-tls-connect-1.0.1.tgz",
+      "integrity": "sha1-kazkOsJrE4hhsr5HjfnfGdYdxcM="
+    },
     "nopt": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
@@ -1236,6 +1590,11 @@
         "commander": "^3.0.2"
       }
     },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+    },
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -1262,6 +1621,11 @@
         "wrappy": "1"
       }
     },
+    "options": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
+      "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8="
+    },
     "os-homedir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
@@ -1317,6 +1681,11 @@
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
       "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
     },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+    },
     "picomatch": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
@@ -1342,6 +1711,21 @@
         "ipaddr.js": "1.9.0"
       }
     },
+    "psl": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+      "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
+    "qbox": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/qbox/-/qbox-0.1.7.tgz",
+      "integrity": "sha1-6A8NxdCfhp2IghaMP2asjdKEDwI="
+    },
     "qs": {
       "version": "6.7.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -1418,6 +1802,13 @@
         "picomatch": "^2.2.1"
       }
     },
+    "reconnect-core": {
+      "version": "https://github.com/dodo/reconnect-core/tarball/merged",
+      "integrity": "sha512-wZK/v5ZaNaSUs2Wnwh2YSX/Jqv6bQHKNEwojdzV11tByKziR9ikOssf5tvUhx+8/oCBz6AakOFAjZuqPoiRHJQ==",
+      "requires": {
+        "backoff": "~2.3.0"
+      }
+    },
     "recursive-readdir": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
@@ -1426,6 +1817,40 @@
         "minimatch": "3.0.4"
       }
     },
+    "request": {
+      "version": "2.88.2",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.5.0",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      },
+      "dependencies": {
+        "qs": {
+          "version": "6.5.2",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+          "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+        }
+      }
+    },
     "rimraf": {
       "version": "2.7.1",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -1564,6 +1989,15 @@
         }
       }
     },
+    "simple-xmpp": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/simple-xmpp/-/simple-xmpp-1.3.1.tgz",
+      "integrity": "sha512-o0wGVlI8Q4o0qTz6Kylbo1QPOMVn+DA/vyHHZecqcQ+LK4ZWGe3wtRON9QjHAkSyxB36PoagmiUz4pHADau8Mw==",
+      "requires": {
+        "node-xmpp-client": "^3.0.0",
+        "qbox": "0.1.x"
+      }
+    },
     "snekfetch": {
       "version": "3.6.4",
       "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz",
@@ -1742,6 +2176,29 @@
       "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
       "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
     },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      },
+      "dependencies": {
+        "tweetnacl": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+        }
+      }
+    },
     "statuses": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -1829,6 +2286,15 @@
       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
       "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
     },
+    "tough-cookie": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+      "requires": {
+        "psl": "^1.1.28",
+        "punycode": "^2.1.1"
+      }
+    },
     "ts-node": {
       "version": "8.5.4",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz",
@@ -1846,6 +2312,14 @@
       "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz",
       "integrity": "sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg=="
     },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "tweetnacl": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
@@ -1865,11 +2339,24 @@
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
       "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw=="
     },
+    "ultron": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
+      "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po="
+    },
     "unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
       "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
     },
+    "uri-js": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+      "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -1880,11 +2367,26 @@
       "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
     },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+    },
     "vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
       "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
     },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
     "wide-align": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
diff --git a/package.json b/package.json
index bee4cbf..5441c89 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "satyr",
-	"version": "0.10.0",
+	"version": "0.10.1",
 	"description": "A livestreaming server.",
 	"license": "AGPL-3.0",
 	"author": "knotteye",
@@ -33,6 +33,7 @@
 		"nunjucks": "^3.2.1",
 		"parse-yaml": "^0.1.0",
 		"recursive-readdir": "^2.2.2",
+		"simple-xmpp": "^1.3.1",
 		"socket-anti-spam": "^2.0.0",
 		"socket.io": "^2.3.0",
 		"strftime": "^0.10.0",
diff --git a/site/index.html b/site/index.html
index cd6342b..d3c0047 100644
--- a/site/index.html
+++ b/site/index.html
@@ -3,12 +3,13 @@
 	<link rel="stylesheet" type="text/css" href="/styles.css">
 	<link rel="stylesheet" type="text/css" href="/local.css">
 	<link rel="icon" type="image/svg" href="/logo.svg">
+	
 	<script src="/nunjucks-slim.js"></script>
 	<script src="/templates.js"></script>
+    <script src="/dashjs/dash.all.min.js"></script>
+	
 	<script>
 		nunjucks.configure({ autoescape: true });
-	</script>
-	<script>
 		//should check for and refresh login tokens on pageload..
 		if(document.cookie.match(/^(.*;)?\s*X-Auth-As\s*=\s*[^;]+(.*)?$/) !== null) {
 			var xhr = new XMLHttpRequest();
diff --git a/site/index.js b/site/index.js
index 8a06b30..2f78da6 100644
--- a/site/index.js
+++ b/site/index.js
@@ -1,5 +1,7 @@
-async function render(path){
+async function render(path, s){
 	var context = await getContext();
+	if(!s)
+		history.pushState({}, context.sitename, location.protocol+'//'+location.host+path);
 	switch(path){
 		//nothing but context
 		case (path.match(/^\/about\/?$/) || {}).input: 
@@ -80,6 +82,7 @@ async function render(path){
 			if(!config.title){document.body.innerHTML = nunjucks.render('404.njk', context); break;}
 			document.body.innerHTML = nunjucks.render('user.njk', Object.assign({about: config.about, title: config.title, username: config.username}, context));
 			modifyLinks();
+			startVideo();
 			break;
 		case (path.match(/^\/vods\/.+\/manage\/?$/) || {}).input: // /vods/:user/manage
 			var usr = path.substring(6, (path.length - 7));
@@ -103,6 +106,9 @@ async function render(path){
 		case "":
 			render('/users/live');
 			break;	
+		case "/index.html":
+			render('/users/live');
+			break;
 		//404
 		default:
 			document.body.innerHTML = nunjucks.render('404.njk', context);
@@ -110,6 +116,10 @@ async function render(path){
 	}
 }
 
+window.addEventListener('popstate', (event) => {
+	render(document.location.pathname, true);
+});
+
 async function getContext(){
 	var info = JSON.parse(await makeRequest('GET', '/api/instance/info'));
 	info.sitename = info.name;
@@ -167,4 +177,43 @@ function modifyLinks() {
 function internalLink(path){
 	this.render(path);
 	return false;
+}
+
+//start dash.js
+async function startVideo(){
+	//var url = "/live/{{username}}/index.mpd";
+  	//var player = dashjs.MediaPlayer().create();
+  	//player.initialize(document.querySelector("#videoPlayer"), url, true);
+  	//console.log('called startvideo');
+  	while(true){
+		if(!document.querySelector('#videoPlayer'))
+			break;
+
+	    if(window.location.pathname.substring(window.location.pathname.length - 1) !== '/'){
+			var url = "/api/"+window.location.pathname.substring(7)+"/config";
+			console.log(url)
+			var xhr = JSON.parse(await makeRequest("GET", url));
+			if(xhr.live){
+				var player = dashjs.MediaPlayer().create();
+				player.initialize(document.querySelector("#videoPlayer"), url, true);
+				break;
+			}
+		}
+
+		else{
+			var url = "/api/"+window.location.pathname.substring(7, window.location.pathname.length - 1)+"/config";
+			console.log(url)
+			var xhr = JSON.parse(await makeRequest("GET", url));
+			if(xhr.live){
+				var player = dashjs.MediaPlayer().create();
+				player.initialize(document.querySelector("#videoPlayer"), url, true);
+				break;
+			}
+		}
+		await sleep(60000);
+	}
+}
+
+function sleep(ms) {
+	return new Promise(resolve => setTimeout(resolve, ms));
 }
\ No newline at end of file
diff --git a/site/local.css b/site/local.css
new file mode 100644
index 0000000..7d7d359
--- /dev/null
+++ b/site/local.css
@@ -0,0 +1,2 @@
+/* This file is for themeing Satyr's frontend without worrying about changes
+being overwritten. Feel free to make whatever local changes you want here. */
\ No newline at end of file
diff --git a/src/api.ts b/src/api.ts
index 27b0d68..f1e40a2 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -86,7 +86,7 @@ async function getConfig(username: string, all?: boolean): Promise<object>{
 	if(all) {
 		let users = await db.query('SELECT stream_key,record_flag FROM users WHERE username='+db.raw.escape(username));
 		if(users[0]) Object.assign(t, users[0]);
-		let usermeta = await db.query('SELECT title,about FROM user_meta WHERE username='+db.raw.escape(username));
+		let usermeta = await db.query('SELECT title,about,live FROM user_meta WHERE username='+db.raw.escape(username));
 		if(usermeta[0]) Object.assign(t, usermeta[0]);
 		let ci = await db.query('SELECT irc,xmpp,twitch,discord FROM chat_integration WHERE username='+db.raw.escape(username));
 		if(ci[0]) Object.assign(t, ci[0]);
@@ -94,7 +94,7 @@ async function getConfig(username: string, all?: boolean): Promise<object>{
 		if(tw[0]) t['twitch_mirror'] = Object.assign({}, tw[0]);
 	}
 	else {
-		let um = await db.query('SELECT title,about FROM user_meta WHERE username='+db.raw.escape(username));
+		let um = await db.query('SELECT title,about,live FROM user_meta WHERE username='+db.raw.escape(username));
 		if(um[0]) Object.assign(t, um[0]);
 	}
 	return t;
diff --git a/src/chat.ts b/src/chat.ts
index 647b6db..1a47ae6 100644
--- a/src/chat.ts
+++ b/src/chat.ts
@@ -4,9 +4,12 @@ import {io} from "./http";
 import * as irc from "irc";
 import * as discord from "discord.js";
 import * as twitch from "dank-twitch-irc";
+import * as xmpp from "simple-xmpp";
+const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
 
 var ircClient;
-var xmppClient;
+var xmppIgnore: Array<string> = [];
+var xmppJoined: Array<string> = [];
 var twitchClient;
 var twitchArr: Array<string> = [];
 var discordClient;
@@ -51,7 +54,24 @@ async function init() {
 		});
 	}
 	if(config['chat']['xmpp']['enabled']){
-		
+		xmpp.on('online', (data) => {
+			console.log("XMPP Client Ready");
+		});
+		xmpp.on('groupchat', function(conference, from, message, stamp) {
+			if(xmppIgnore.findIndex((e) => { return e === conference }) !== -1) return false;
+			if(from === config['chat']['xmpp']['nickname']) return false;
+			console.log(from+'\n'+conference+'\n'+message+'\n'+stamp);
+			var lu = getUsr(conference, "xmpp");
+			for(var i=0;i<lu.length;i++){
+				sendAll(lu[i], [from, message], "xmpp")
+			}
+		});
+		xmpp.connect({
+			jid: config['chat']['xmpp']['jid'],
+			password: config['chat']['xmpp']['password'],
+			host: config['chat']['xmpp']['server'],
+			port: config['chat']['xmpp']['port']
+		});
 	}
 	if(config['chat']['twitch']['enabled']){
 		twitchClient = new twitch.ChatClient({
@@ -96,12 +116,14 @@ async function updateInteg() {
 		chatIntegration = [];
 		if(config['chat']['irc']['enabled']) updateIRCChan();
 		if(config['chat']['twitch']['enabled']) updateTwitchChan();
+		if(config['chat']['xmpp']['enabled']) updateXmppChan();
 		return;
 	}
 	if(liveUsers.length === 1) {
 		chatIntegration = await db.query('SELECT * FROM chat_integration WHERE username='+db.raw.escape(liveUsers[0]['username']));
 		if(config['chat']['irc']['enabled']) updateIRCChan();
 		if(config['chat']['twitch']['enabled']) updateTwitchChan();
+		if(config['chat']['xmpp']['enabled']) updateXmppChan();
 		return;
 	}
 	var qs: string;
@@ -112,6 +134,7 @@ async function updateInteg() {
 	chatIntegration = await db.query('SELECT * FROM chat_integration WHERE username='+qs);
 	if(config['chat']['irc']['enabled']) updateIRCChan();
 	if(config['chat']['twitch']['enabled']) updateTwitchChan();
+	if(config['chat']['xmpp']['enabled']) updateXmppChan();
 }
 
 async function sendAll(user: string, msg: Array<string>, src: string) {
@@ -126,7 +149,7 @@ async function sendAll(user: string, msg: Array<string>, src: string) {
 	if(src !== "irc") sendIRC(getCh(user, "irc"), '['+src.toUpperCase()+']'+msg[0]+': '+msg[1]);
 	if(src !== "twitch") sendTwitch(getCh(user, "twitch"), '['+src.toUpperCase()+']'+msg[0]+': '+msg[1]);
 	if(src !== "discord") sendDiscord(getCh(user, "discord"), '['+src.toUpperCase()+']'+msg[0]+': '+msg[1]);
-	//if(src !== "xmpp") sendXMPP();
+	if(src !== "xmpp") sendXMPP(getCh(user, "xmpp"), '['+src.toUpperCase()+']'+msg[0]+': '+msg[1]);
 	if(src !== "web") sendWeb(user, ['['+src.toUpperCase()+']'+msg[0], msg[1]]);
 }
 
@@ -146,6 +169,7 @@ async function sendDiscord(channel: string, msg: string) {
 async function sendXMPP(channel: string, msg: string) {
 	if(!config['chat']['xmpp']['enabled']) return;
 	if(channel === null) return;
+	xmpp.send(channel, msg, true);
 }
 
 async function sendTwitch(channel: string, msg: string) {
@@ -252,4 +276,25 @@ async function normalizeDiscordMsg(msg): Promise<string>{
 	return nmsg;
 }
 
+function xmppJoin(room: string): void{
+	if(xmppJoined.findIndex((e) => { return e === room }) !== -1) return;
+	var stanza = new xmpp.Element('presence', {"to": room+'/'+config['chat']['xmpp']['nickname']}).c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', { maxstanzas: 0, seconds: 0});
+	xmpp.conn.send(stanza);
+	xmppIgnore = xmppIgnore.concat([room]);
+	xmpp.join(room+'/'+config['chat']['xmpp']['nickname']);
+	xmppJoined = xmppJoined.concat([room]);
+	sleep(4000).then(() => {
+		xmppIgnore = xmppIgnore.filter((item) => {
+			return item !== room;
+		});
+	});
+}
+
+function updateXmppChan(): void{
+	for(var i=0;i<chatIntegration.length;i++){
+		if(chatIntegration[i]['xmpp'].trim() !== "" && chatIntegration[i]['xmpp'] !== null) xmppJoin(chatIntegration[i]['xmpp']);
+	}
+	//we can't really leave channels so I'll come back to that.
+}
+
 export { init, sendAll };
\ No newline at end of file
diff --git a/src/cluster.ts b/src/cluster.ts
index 4a5181e..61f440c 100644
--- a/src/cluster.ts
+++ b/src/cluster.ts
@@ -133,14 +133,14 @@ if (cluster.isMaster) {
 				db.query('update user_meta set live=true where username=\''+results[0].username+'\' limit 1');
 				db.query('SELECT twitch_key,enabled from twitch_mirror where username='+db.raw.escape(results[0].username)+' limit 1').then(async (tm) => {
 					if(!tm[0]['enabled'] || !config['twitch_mirror']['enabled'] || !config['twitch_mirror']['ingest']) return;
-					console.log('[NodeMediaServer] Mirroring to twitch for stream:',id)
+					console.log(`[RTMP Cluster WORKER ${process.pid}] Mirroring to twitch for stream: ${id}`)
 					execFile(config['media']['ffmpeg'], ['-loglevel', 'fatal', '-i', 'rtmp://127.0.0.1:'+wPort+'/'+config['media']['privateEndpoint']+'/'+key, '-vcodec', 'copy', '-acodec', 'copy', '-f', 'flv', config['twitch_mirror']['ingest']+tm[0]['twitch_key']], {
 						detached: true,
 						stdio : 'inherit',
 						maxBuffer: Infinity
 					}).unref();
 				});
-				console.log('[NodeMediaServer] Stream key ok for stream:',id);
+				console.log(`[RTMP Cluster WORKER ${process.pid}] Stream key ok for stream: ${id}`);
 				console.log(`[RTMP Cluster WORKER ${process.pid}] Stream key ok for stream: ${id}`);
 				//notify master process that we're handling the stream for this user
 				process.send({type: 'handle-publish', name:results[0].username});
@@ -171,14 +171,14 @@ if (cluster.isMaster) {
 		let key: string = StreamPath.split("/")[2];
 		//correctly formatted urls again
 		if (StreamPath.split("/").length !== 3){
-			console.log("[NodeMediaServer] Malformed URL, closing connection for stream:",id);
+			console.log(`[RTMP Cluster WORKER ${process.pid}] Malformed URL, closing connection for stream: ${id}`);
 			session.reject();
 			return false;
 		}
 		//localhost can play from whatever endpoint
 		//other clients must use private endpoint
 		if(app !== config['media']['publicEndpoint'] && !session.isLocal) {
-			console.log("[NodeMediaServer] Non-local Play from private endpoint, rejecting client:",id);
+			console.log(`[RTMP Cluster WORKER ${process.pid}] Non-local Play from private endpoint, rejecting client: ${id}`);
 			session.reject();
 			return false;
 		}
diff --git a/src/config.ts b/src/config.ts
index d9edb2e..a68abaa 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -30,6 +30,7 @@ const config: Object = {
 	   insecureAuth: false,
 	   debug: false }, localconfig['database']),
 	rtmp: Object.assign({
+	  cluster: false,
 	  port: 1935,
 	  chunk_size: 6000,
 	  gop_cache: true,
@@ -75,8 +76,9 @@ const config: Object = {
 			enabled: false,
 			server: null,
 			port: 5222,
-			nickname: 'SatyrChat',
-			username: 'SatyrChat'
+			jid: null,
+			password: null,
+			nickname: 'SatyrChat'
 		}, localconfig['chat']['xmpp']),
 
 		twitch: Object.assign({
diff --git a/src/http.ts b/src/http.ts
index f83b61b..b15b04f 100644
--- a/src/http.ts
+++ b/src/http.ts
@@ -168,6 +168,7 @@ async function initAPI() {
 	app.get('/api/instance/config', (req, res) => {
 		res.json({
 			rtmp: {
+				cluster: config['rtmp']['cluster'],
 				port: config['rtmp']['port'],
 				ping_timeout: config['rtmp']['ping_timeout']
 			},
diff --git a/src/index.ts b/src/index.ts
index d2c3f15..49f9ae2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,7 +10,7 @@ async function run() {
 	await initDB();
 	await clean();
 	await initHTTP();
-	config['rtmp']['cluster'] ? execFile(process.cwd()+'/node_modules/.bin/ts-node' [process.cwd()+'src/cluster.ts']) : await initRTMP();
+	config['rtmp']['cluster'] ? execFile(process.cwd()+'/node_modules/.bin/ts-node', [process.cwd()+'/src/cluster.ts']) : await initRTMP();
 	await initChat();
 	console.log(`Satyr v${config['satyr']['version']} ready`);
 }
diff --git a/templates/base.njk b/templates/base.njk
index f832bc3..aa150ed 100644
--- a/templates/base.njk
+++ b/templates/base.njk
@@ -12,6 +12,29 @@
 			xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 			xhr.send("");
 		}
+		function makeRequest(method, url, payload) {
+  		  return new Promise(function (resolve, reject) {
+        	let xhr = new XMLHttpRequest();
+        	xhr.open(method, url);
+        	xhr.onload = function () {
+            if (this.status >= 200 && this.status < 300) {
+                resolve(xhr.response);
+            } else {
+                reject({
+                    status: this.status,
+                    statusText: xhr.statusText
+                });
+            }
+        	};
+       	 	xhr.onerror = function () {
+            reject({
+                status: this.status,
+                statusText: xhr.statusText
+            });
+        	};
+        	!payload ? xhr.send() : xhr.send(payload);
+    		});
+		}
 	</script>
 	{% block head %}
 	{% endblock %}
diff --git a/templates/user.njk b/templates/user.njk
index d00c82e..2a637ff 100644
--- a/templates/user.njk
+++ b/templates/user.njk
@@ -4,12 +4,43 @@
   <link rel="stylesheet" type="text/css" href="/videojs/video-js.min.css">-->
   <script src="/dashjs/dash.all.min.js"></script>
   <script>
-	  function startVideo(){
-	  	var url = "/live/{{username}}/index.mpd";
-		var player = dashjs.MediaPlayer().create();
-		player.initialize(document.querySelector("#videoPlayer"), url, true);
-		console.log('called startvideo');
-	  }
+	async function startVideo(){
+	//var url = "/live/{{username}}/index.mpd";
+  	//var player = dashjs.MediaPlayer().create();
+  	//player.initialize(document.querySelector("#videoPlayer"), url, true);
+  	//console.log('called startvideo');
+  	while(true){
+		if(!document.querySelector('#videoPlayer'))
+			break;
+
+	    if(window.location.pathname.substring(window.location.pathname.length - 1) !== '/'){
+			var url = "/api/"+window.location.pathname.substring(7)+"/config";
+			console.log(url)
+			var xhr = JSON.parse(await makeRequest("GET", url));
+			if(xhr.live){
+				var player = dashjs.MediaPlayer().create();
+				player.initialize(document.querySelector("#videoPlayer"), url, true);
+				break;
+			}
+		}
+
+		else{
+			var url = "/api/"+window.location.pathname.substring(7, window.location.pathname.length - 1)+"/config";
+			console.log(url)
+			var xhr = JSON.parse(await makeRequest("GET", url));
+			if(xhr.live){
+				var player = dashjs.MediaPlayer().create();
+				player.initialize(document.querySelector("#videoPlayer"), url, true);
+				break;
+			}
+		}
+		await sleep(60000);
+	}
+}
+
+function sleep(ms) {
+	return new Promise(resolve => setTimeout(resolve, ms));
+}
   </script>
 {% endblock %}
 {% block content %}
@@ -28,7 +59,7 @@ function newPopup(url) {
 
 			<!--this spits errors fucking constantly after it tries to reload a video that's already running.. I dunno if it's bad or causing problems so let's just push it to develop and wait for issues!-->
 			<!--it plays the stream without reloading the page tho lol-->
-			<script>window.setInterval(startVideo, 60000)</script>
+			<script>startVideo()</script>
 		</div>
 		<div id="jschild" class="webchat" style="width: 30%;height: 100%;position: relative;">
 			<iframe src="/chat?room={{ username }}" frameborder="0" style="width: 100%;height: 100%; min-height: 534px;" allowfullscreen></iframe>