Merge pull request 'master -> develop' (#2) from develop into master

Reviewed-on: http://localhost:3000/git/knotteye/satyr/pulls/2
pull/24/head^2
knotteye 3 years ago
commit 97b6f50b7c
  1. 2
      README.md
  2. 10
      docs/CHAT.md
  3. 34
      docs/CONFIGURATION.md
  4. 4
      docs/INSTALLATION.md
  5. 1
      docs/REST.md
  6. 50
      docs/USAGE.md
  7. 7
      install/config.example.yml
  8. 25
      install/satyr.nginx
  9. 502
      package-lock.json
  10. 3
      package.json
  11. 5
      site/index.html
  12. 51
      site/index.js
  13. 2
      site/local.css
  14. 4
      src/api.ts
  15. 51
      src/chat.ts
  16. 8
      src/cluster.ts
  17. 6
      src/config.ts
  18. 1
      src/http.ts
  19. 2
      src/index.ts
  20. 23
      templates/base.njk
  21. 45
      templates/user.njk

@ -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

@ -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.

@ -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

@ -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

@ -32,6 +32,7 @@ Configuration of the instance relating to media
```
{
rtmp: {
cluster: false,
port: 1935,
ping_timeout: 60
},

@ -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.
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;
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/;
}
}
```
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 config can be found at install/satyr.nginx
An example systemd service can also be at install/satyr.service
#### 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
### 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

@ -51,10 +51,11 @@ chat:
xmpp:
enabled: false
server:
server:
port: 5222
nickname: 'SatyrChat'
username: 'SatyrChat'
jid:
password:
nickname:
twitch:
enabled: false

@ -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/;
}
}

502
package-lock.json generated

@ -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",

@ -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",

@ -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();

@ -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));
}

@ -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. */

@ -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;

@ -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 };

@ -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;
}

@ -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({

@ -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']
},

@ -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`);
}

@ -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 %}

@ -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>