Compare commits

..

63 Commits

Author SHA1 Message Date
knotteye
a8a05a992e Merge pull request 'Update package version for release' (#25) from develop into master
Reviewed-on: https://git.waldn.net/git/knotteye/satyr/pulls/25
2021-01-10 15:52:08 -06:00
knotteye
ef52cbe629 Update package version for release 2021-01-10 15:49:40 -06:00
knotteye
67694cffd8 Merge pull request 'develop -> master' (#24) from develop into master
Reviewed-on: https://git.waldn.net/git/knotteye/satyr/pulls/24
2021-01-10 15:24:24 -06:00
knotteye
3701b0c8fa Merge branch 'master' into develop 2021-01-10 15:22:40 -06:00
knotteye
362381e7db Update documentation with hwaccleration issues 2021-01-10 15:20:45 -06:00
knotteye
a3341d8b7d Disable CRF when using hwaccel 2021-01-10 15:09:42 -06:00
knotteye
8a33b80593 Map streams automatically instead of manually 2021-01-10 14:29:54 -06:00
knotteye
c5921e4e08 Merge pull request 'experimental hardware acceleration' (#21) from hwaccel into develop
Reviewed-on: https://git.waldn.net/git/knotteye/satyr/pulls/21
2021-01-10 13:46:02 -06:00
knotteye
943c71d1e0 Add what I think is working hwaccel support 2021-01-10 13:42:52 -06:00
knotteye
1afd855e73 Clarify some documentation 2021-01-10 13:42:23 -06:00
knotteye
9df4b545ec Update config.ts to include hwaccel options 2021-01-10 13:42:12 -06:00
knotteye
364994decc Start work on hardware acceleration.
Documentation first, code later.
2021-01-10 12:46:45 -06:00
knotteye
cccdc8838d Merge pull request 'Switch to using the actual database instead of bans.db' (#20) from bansdb into develop
Reviewed-on: https://git.waldn.net/git/knotteye/satyr/pulls/20
2021-01-10 12:34:19 -06:00
knotteye
69d81ec836 Switch to using MySQL instead of bans.db 2021-01-10 12:31:11 -06:00
knotteye
7156accbee Fix a bug where we weren't setting X-Auth-As on /api/register
Also fix new users appearing to not exist until they update their bio/profile
2021-01-10 11:26:52 -06:00
knotteye
814d826ec9 Modify the migration script to import existing data. 2021-01-10 11:01:28 -06:00
knotteye
a882285bac Fix database functions regarding ch_bans since it's a special case 2021-01-10 10:13:29 -06:00
knotteye
57410dc969 Update database functions to create and destroy rows in new table. 2021-01-09 15:25:06 -06:00
knotteye
5c22c1a738 Add migration script for channel bans table in the database. 2021-01-09 15:22:23 -06:00
knotteye
a1a101c0f1 Check if the video object still exists before restarting the timeout 2021-01-08 17:25:44 -06:00
knotteye
7f40690820 Update repository link 2021-01-08 17:14:16 -06:00
knotteye
4c1298cc5c Merge pull request 'develop -> master' (#19) from develop into master
Reviewed-on: https://git.waldn.net/git/knotteye/satyr/pulls/19
2021-01-08 16:30:46 -06:00
knotteye
1abb35f9ac Update version for release 2021-01-08 16:29:00 -06:00
knotteye
2ef1c80813 Merge pull request 'merge frontend-improvments' (#18) from frontend-improvments into develop
Reviewed-on: https://git.waldn.net/git/knotteye/satyr/pulls/18
2021-01-08 16:18:05 -06:00
knotteye
8cb78a7dd6 Add code for re-checking if a user has gone live since loading the page
Also exempt static files from link rewriting in the SPA
There may be more in here.. It's been a long break halfway through this commit.
2021-01-08 16:14:19 -06:00
knotteye
f703d5af7f Stop accidentally capturing static file links 2021-01-08 15:39:03 -06:00
knotteye
2a121d27ee Merge pull request 'config-bugfix -> develop' (#13) from config-bugfix into develop
Reviewed-on: https://pond.waldn.net/git/knotteye/satyr/pulls/13
2020-10-30 07:40:20 -05:00
knotteye
cc8c4915f9 Distinguish between errors when loading the config file. 2020-10-30 07:38:01 -05:00
knotteye
d0e3507cc0 Merge pull request 'better-migration -> develop' (#12) from better-migration into develop
Reviewed-on: https://pond.waldn.net/git/knotteye/satyr/pulls/12
2020-10-30 07:27:55 -05:00
knotteye
33accfb8b7 Select scripts to run based on comparing version strings and script names.
This means scripts need to insert a version value the same as their name to db_meta. This is nothing new, it just works better now.
2020-10-30 07:20:23 -05:00
knotteye
3e073e7f66 Skip compiling templates when running migrations alone 2020-10-30 06:25:47 -05:00
knotteye
47e036cde6 Merge pull request 'web-player -> develop' (#8) from web-player into develop
Reviewed-on: https://pond.waldn.net/git/knotteye/satyr/pulls/8
2020-10-25 10:27:51 -05:00
knotteye
a75a625cd3 Merge branch 'develop' into web-player 2020-10-25 10:27:29 -05:00
knotteye
7b5a498241 Fix manifest uri in user.njk 2020-10-25 10:26:07 -05:00
knotteye
2a5e8d6ec2 Add play button functionality for web player 2020-10-25 10:17:45 -05:00
knotteye
12e868456a Add play button for shaka player. Needs functionality. 2020-10-25 10:17:45 -05:00
knotteye
281d9c7922 Merge pull request 'master <- develop' (#7) from develop into master
Reviewed-on: https://pond.waldn.net/git/knotteye/satyr/pulls/7
2020-10-24 22:54:29 -05:00
knotteye
fe3ec1bd75 Fix CORS issues in reccomended nginx config 2020-10-24 22:52:59 -05:00
knotteye
07cba66096 Fix a typo in the generated config file 2020-10-24 22:47:03 -05:00
knotteye
2de486da46 Switch to shaka-player and initialize it on pageload.
Maybe todo: more testing and re-check if the stream has started after the page has already loaded
2020-10-18 09:23:28 -05:00
knotteye
2c073a7cdb Update URLs with new repository 2020-10-18 09:25:54 +00:00
knotteye
8a71680971 Merge pull request 'Fix a bug with migrate.ts calling a function incorrectly' (#4) from migrate-bugfix into develop 2020-10-18 04:16:15 -05:00
knotteye
0900496d70 Fix a bug with migrate.ts calling a function incorrectly 2020-10-18 09:13:09 +00:00
knotteye
34d6fa91df Fix some typos in documentation 2020-10-18 02:16:54 -05:00
knotteye
97b6f50b7c Merge pull request 'master -> develop' (#2) from develop into master
Reviewed-on: http://localhost:3000/git/knotteye/satyr/pulls/2
2020-10-17 23:49:01 -05:00
knotteye
94f240d4d6 Merge branch 'master' into develop 2020-10-17 23:48:18 -05:00
knotteye
d8b327752e Bump minor version 2020-10-17 23:41:37 -05:00
knotteye
7107cb4c8f Update documentation for new repository host 2020-10-17 23:37:34 -05:00
knotteye
90cce68581 Fix bug 2020-10-17 22:40:30 -05:00
knotteye
1fa6bf7e81 Update documentation 2020-10-17 22:23:05 -05:00
knotteye
ab9a9b4585 Expose whether cluster mode is enabled over /api/instance/config 2020-10-17 22:08:56 -05:00
knotteye
01744df3cd Fix a bug where XMPP bridge would repeatedly ignore channels 2020-10-17 22:07:30 -05:00
knotteye
ab082e5f95 Fix logging in cluster.ts
Fix a bug calling the cluster process in index.ts
Set a default value for rtmp.cluster in config.ts
Update documentation
2020-10-17 21:39:56 -05:00
knotteye
de17128cd2 Merge branch 'xmpp-mirror' into 'develop'
Xmpp mirror

See merge request knotteye/satyr!30
2020-10-18 01:19:20 +00:00
knotteye
cfa7c5ab13 Implement XMPP mirror 2020-10-17 20:17:24 -05:00
knotteye
81afb7493b Update config options for xmpp bridge 2020-10-17 18:30:38 -05:00
knotteye
9a6e5c8798 Add documentation for configuring XMPP bridge 2020-10-17 18:29:17 -05:00
knotteye
06dc05eb8b Add a dependency for xmpp mirroring and add configurability 2020-10-17 16:55:55 -05:00
knotteye
dfd0bc4881 Commit a local.css file to get rid of some error messages 2020-10-17 16:15:49 -05:00
knotteye
30a62e6378 Handle back button when the user navigates to one of our own history events 2020-10-17 16:02:50 -05:00
knotteye
cc3876ff4a Update URL when navigating to new pages and push state to history in client-side SPA 2020-10-17 15:39:19 -05:00
knotteye
56c4b94a80 Fix the start video function to avoid redudant calls 2020-10-17 05:26:31 -05:00
knotteye
a5c1adfffc Return live flag as well when getting user info 2020-10-17 05:13:32 -05:00
31 changed files with 1414 additions and 190 deletions

View File

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

View File

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

View File

@ -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
@ -26,17 +38,16 @@ transcode:
# satyr will generate one source quality variant, and the remaining
# variants will be of incrementally lower quality and bitrate
# 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 recommended to leave adaptive streaming off
inputflags: ""
# additional flags to apply to the input during transcoding
outputflags: ""
# additional flags to apply to the output during transcoding
# hardware acceleration is a bit difficult to configure programmatically
# this is a good place to do so for your system
# 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
hwaccel:
# See HWACCEL.md for information on configuring hardware acceleration.
crypto:
saltRounds: 12
@ -44,12 +55,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 +71,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 +83,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

53
docs/HWACCEL.md Normal file
View File

@ -0,0 +1,53 @@
## Configuration Hardware Acceleration
Satyr supports the NVENC and VA-API hardware acceleration APIs. If you've configured your system correctly (the hard part) it should be enough to set the type and use the default device setting if you only have one hardware acceleration device.
### System
Configuring the system for any hardware acceleration API involves three main steps: selecting the right drivers, installing the API libraries, and configuring ffmpeg.
#### NVENC
NVENC in ffmpeg can work with either open-source drivers (nouvea) or nvidia's proprietary drivers. The documentation for your distribution should have instructions for installing these.
The only system library you should need is the CUDA toolkit, general named cudatoolkit, nvidia-cuda-toolkit, or some variation in your system repositories.
You can also try installing manually from [here](https://developer.nvidia.com/cuda-downloads).
Most binary distributions provide a version of ffmpeg with NVENC already enabled. If not you can try compiling ffmpeg from source with the `--enable-nvenc` flag. If you use a source based distribution you should be familiar with enabling optional compile flags.
You can verify that ffmpeg has been set up correctly by checking the output of `ffmpeg -hide_banner -hwaccels | grep cuvid` and `ffmpeg -hide_banner -encoders | grep nvenc`. If you don't see anything, something is wrong.
#### VA-API
VA-API is an extremely generic API. Although the package names might be different in your distribution, the arch wiki page for hardware acceleration has good information on [driver selection](https://wiki.archlinux.org/index.php/Hardware_video_acceleration#Installation) and [verifying](https://wiki.archlinux.org/index.php/Hardware_video_acceleration#Verifying_VA-API) a VA-API install for a wide range of devices.
Regardless of driver selection, you will also need libva or the equivalent from your distrubtion, and libva-utils can be helpful as well.
Most binary distributions provide a version of ffmpeg with VA-API already enabled. If not you can try compiling ffmpeg from source with the `--enable-vaapi` flag. If you use a source based distribution you should be familiar with enabling optional compile flags.
You can verify that ffmpeg has been set up correctly by checking the output of `ffmpeg -hide_banner -hwaccels | grep vaapi` and `ffmpeg -hide_banner -encoders | grep vaapi`. If you don't see anything, something is wrong.
### Satyr
```
# Decoding
hwaccel:
# Enable hardware acceleration for decoding as well as encoding.
# Probably not worth it, hardware decoding won't be any faster compared to software on a vaguely modern CPU
# Hardware decoding also may not support the input format, in which case transcoding will fail
decode: true
# Only supported for VA-API
# Fall back to software decoding if hardware decoding fails
hwaccel:
decode: 'fallback'
# NVENC
hwaccel:
type: 'nvenc'
# device is optional for nvenc
device: 0
# nvenc wants a device number instead of a path, set to null to use the default
# VA-API
hwaccel:
type: 'vaapi'
# device is mandatory for va-api
device: '/dev/dri/renderD128'
```

View File

@ -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
@ -33,8 +33,7 @@ Then start the server.
npm run start
```
It is reccomended that you run Satyr behind a TLS terminating reverse proxy like nginx. An example configuration can be found in CONFIGURATION.md
An example systemd service is provided at install/satyr.service. It assumes you've installed satyr into /opt/satyr, and created a satyr user with the home directory /var/lib/satyr for the purpose of running the service.
It is reccomended that you run Satyr behind a TLS terminating reverse proxy like nginx. An example configuration can be found at install/satyr.nginx. An example systemd service is provided at install/satyr.service. It assumes you've installed satyr into /opt/satyr, and created a satyr user with the home directory /var/lib/satyr for the purpose of running the service.
## Updating Satyr
Updating should be as simple as pulling the latest code and dependencies, then restarting the server.

View File

@ -26,12 +26,13 @@ Configuration of the instance relating to media
**Parameters**: none
**Response**: JSON object containing the port and ping_timeout for RTMP, public and private play endpoints, and whether adaptive livestreaming and VOD recording are enabled.
**Response**: JSON object containing the port and ping_timeout for RTMP, whether clustering is enabled, public and private play endpoints, and whether adaptive livestreaming and VOD recording are enabled.
**Example**:
```
{
rtmp: {
cluster: false,
port: 1935,
ping_timeout: 60
},
@ -68,7 +69,7 @@ sort is the optional way to sort results. current options are "alphabet" and "al
page is the page number (i.e. skip the first num * page results)
**Response**: Returns an array of JSON objects containing the username and title of each stream. Returns an empty array if no one is streaming. Returns `{"error":"error code"}` in the event of an error. Will attempt to correct malformed requests to default values.
**Response**: Returns an array of JSON objects containing the username and title of each stream. Returns an empty array if no one is streaming. Returns `{"error":"error reason"}` in the event of an error. Will attempt to correct malformed requests to default values.
The array will be wrapped in a JSON object under the key 'users'.
@ -77,7 +78,7 @@ The array will be wrapped in a JSON object under the key 'users'.
## /api/users/all
Same as above, but returns all users regardless of whether they are streaming and if they're streaming or not.
Same as above, but returns all users regardless of whether they are streaming and if they're streaming or not. Also returns a value 'live' indicating whether a user is currently streaming.
**Example**: `{users: [{username:"foo", title:"bar", live:1}] }`
@ -130,7 +131,7 @@ Update the current user's information
Rec is a boolean (whether to record VODs), twitch is a boolean (whether to mirror video streams to twitch) others are strings. Twitch_key is the stream key to use for twitch. Parameters that are not included in the request will not be updated.
**Response**: Returns `{error: "error code"}` or `{success: ""}`
**Response**: Returns `{error: "error reason"}` or `{success: ""}`
@ -144,9 +145,9 @@ Update the chatrooms on other platforms to integrate with the user's stream chat
**Parameters**: discord, xmpp, twitch irc
Strings corresponding to a channel name to mirror to. XMPP is currently unused. Parameters not included in the request will not be updated.
Strings corresponding to a channel name to mirror to. Parameters not included in the request will not be updated.
**Response**: Returns `{error: "error code"}` or `{success: ""}`
**Response**: Returns `{error: "error reason"}` or `{success: ""}`
@ -158,9 +159,9 @@ Delete the specified vods of the current user
**Authentication**: yes
**Paramters**: A string array of the names of vods to be deleted. (Do not include the file extension);
**Paramters**: A string array of the names of vods to be deleted. (Do not include the file extension)
**Response**: Returns `{error: "error code"}` or `{success: ""}`
**Response**: Returns `{error: "error reason"}` or `{success: ""}`
## /api/user/password
@ -172,7 +173,7 @@ Change the current user's password
**Parameters**: The user's current password, the new password, AND a valid JWT cookie.
**Response**: Returns `{error: "error code"}` or `{success: ""}`
**Response**: Returns `{error: "error reason"}` or `{success: ""}`
@ -186,7 +187,7 @@ Change the current user's stream key. This will not affect the stream if the use
**Parameters**: A valid JWT cookie. No other parameters.
**Response**: Returns `{error: "error code"}` or `{success: "new_stream_key"}`
**Response**: Returns `{error: "error reason"}` or `{success: "new_stream_key"}`
@ -202,7 +203,7 @@ Get a list of the named users VODs
**Response**: Returns an array of VODs inside a JSON object with the format `{"vods": [{"name":"yote.mp4"},{"name":"yeet.mp4"}] }`
**Notes**: VODs are always available at http://domain.com/publicEndpoint/username/filename.mp4
**Notes**: VODs are always available at http://domain/publicEndpoint/username/filename
@ -216,9 +217,9 @@ Get information about the specified user.
**Parameters**: user
**Response**: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title and bio. In the case of searching for a user that does not exist, the returned object will contain only the username searched for.
**Response**: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title, bio, and whether the stream is live. In the case of searching for a user that does not exist, the returned object will contain only the username searched for.
**Example**: `{username: "foo", title: "bar", about: "This is an example bio"}`
**Example**: `{username: "foo", title: "bar", about: "This is an example bio", live: 0}`
@ -234,4 +235,4 @@ Returns the current stream key for the authenticated user.
**Response**: returns a JSON object with the stream key
**Example**: `{"stream_key": "abcdefghijklmno12345"}` or `{"error":"error reason"}`
**Example**: `{stream_key: "abcdefghijklmno12345"}` or `{error:"error reason"}`

View File

@ -1,47 +1,36 @@
## 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
#### Accounts
If registration is open, users can register at example.tld/registration, and set some options such as whether to record VODs and a stream title on /profile
Stream keys can be changed at example.tld/changesk, and passwords at /changepwd
If registration is open, users can register at your.site.com/registration, and set some options such as whether to record VODs and a stream title on /profile.
#### 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

View File

@ -28,10 +28,15 @@ database:
transcode:
#may result in higher latency if your cpu can't keep up
adaptive: false
#more than 3 might cause problems when using hwacceleration
variants: 3
#unused right now, will always transcode to dash
format: dash
hwaccel:
# see docs/HWACCEL.md for instructions on configuring hardware acceleration
type: null
chat:
irc:
@ -53,8 +58,9 @@ chat:
enabled: false
server:
port: 5222
nickname: 'SatyrChat'
username: 'SatyrChat'
jid:
password:
nickname:
twitch:
enabled: false
@ -68,4 +74,4 @@ twitch_mirror:
enabled: false
# https://stream.twitch.tv/ingests/
# do not include {stream_key}
ingest: 'rtmp://live-ord02.twitch.tv/app/
ingest: 'rtmp://live-ord02.twitch.tv/app/'

29
install/satyr.nginx Normal file
View File

@ -0,0 +1,29 @@
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)$ {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# nginx can serve static files faster than node
# this should improve performance
root /opt/satyr/site/;
}
}

523
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "satyr",
"version": "0.10.0",
"version": "0.10.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -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,11 +620,25 @@
}
}
},
"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",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"eme-encryption-scheme-polyfill": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.0.1.tgz",
"integrity": "sha512-Wz+Ro1c0/2Wsx2RLFvTOO0m4LvYn+7cSnq3XOvRvLLBq8jbvUACH/zpU9s0/5+mQa5oaelkU69x+q0z/iWYrFA=="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -685,6 +813,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 +876,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 +935,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 +965,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 +1009,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 +1055,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",
@ -888,9 +1110,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"ipaddr.js": {
"version": "1.9.0",
@ -955,11 +1177,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 +1200,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 +1256,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 +1277,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 +1457,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 +1595,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 +1626,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 +1686,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 +1716,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 +1807,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 +1822,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",
@ -1522,6 +1952,14 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"shaka-player": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-3.0.5.tgz",
"integrity": "sha512-LYq56q9DA7yTLBD1yQwZrMlJZOovb2yRmo0C3AsddL1J0ee+U4BXr1QZd5amtpBvl8fOiLgkW/12UuChMR764A==",
"requires": {
"eme-encryption-scheme-polyfill": "^2.0.1"
}
},
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
@ -1564,6 +2002,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 +2189,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 +2299,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 +2325,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 +2352,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 +2380,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",

View File

@ -1,6 +1,6 @@
{
"name": "satyr",
"version": "0.10.0",
"version": "1.0.0",
"description": "A livestreaming server.",
"license": "AGPL-3.0",
"author": "knotteye",
@ -10,11 +10,12 @@
"setup": "sh install/setup.sh",
"migrate": "ts-node src/migrate.ts",
"invite": "ts-node src/cli.ts --invite",
"v3-manual": "ts-node src/v3manual.ts",
"make-templates": "nunjucks-precompile -i [\"\\.html$\",\"\\.njk$\"] templates > site/templates.js"
},
"repository": {
"type": "git",
"url": "https://gitlab.com/knotteye/satyr.git"
"url": "https://git.waldn.net/git/knotteye/satyr.git"
},
"dependencies": {
"bcrypt": "^5.0.0",
@ -33,11 +34,13 @@
"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",
"ts-node": "^8.5.4",
"typescript": "^3.6.3"
"typescript": "^3.6.3",
"shaka-player": "^3.0.5"
},
"devDependencies": {
"@types/node": "^12.12.67"

View File

@ -1,14 +0,0 @@
# dash.js BSD License Agreement
The copyright in this software is being made available under the BSD License, included below. This software may be subject to other third party and contributor rights, including patent rights, and no such rights are granted under this license.
**Copyright (c) 2015, Dash Industry Forum.
**All rights reserved.**
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the Dash Industry Forum nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
**THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.**

File diff suppressed because one or more lines are too long

View File

@ -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="/shaka-player.compiled.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();

View File

@ -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();
initPlayer(usr);
break;
case (path.match(/^\/vods\/.+\/manage\/?$/) || {}).input: // /vods/:user/manage
var usr = path.substring(6, (path.length - 7));
@ -99,9 +102,15 @@ async function render(path){
//root
case "/":
render('/users/live');
modifyLinks();
break;
case "":
render('/users/live');
modifyLinks();
break;
case "/index.html":
render('/users/live');
modifyLinks();
break;
//404
default:
@ -110,6 +119,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;
@ -157,7 +170,7 @@ function handleLoad() {
function modifyLinks() {
for (var ls = document.links, numLinks = ls.length, i=0; i<numLinks; i++){
if(ls[i].href.indexOf(location.protocol+'//'+location.host) !== -1) {
if(ls[i].href.indexOf(location.protocol+'//'+location.host) !== -1 && ls[i].href.match(new RegExp(/\/\w+\.\w+$/)) === null) {
//should be a regular link
ls[i].setAttribute('onclick', 'return internalLink(\"'+ls[i].href.substring((location.protocol+'//'+location.host).length)+'\")');
}
@ -168,3 +181,54 @@ function internalLink(path){
this.render(path);
return false;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var shakaPolyFilled = false;
async function initPlayer(usr) {
var manifestUri = document.location.protocol+'//'+document.location.host+'/live/'+usr+'/index.mpd';
if(!shakaPolyFilled){
shaka.polyfill.installAll();
shakaPolyFilled = true;
}
var live = JSON.parse(await makeRequest("GET", "/api/"+usr+"/config")).live;
if(live){
// Create a Player instance.
const video = document.getElementById('video');
const player = new shaka.Player(video);
// Listen for error events.
player.addEventListener('error', onErrorEvent);
video.addEventListener('play', () => {
document.getElementById('playbtn').style.visibility = 'hidden';
});
video.addEventListener('pause', () => {
document.getElementById('playbtn').style.visibility = 'visible';
});
// Try to load a manifest.
// This is an asynchronous process.
try {
await player.load(manifestUri);
// This runs if the asynchronous load is successful.
console.log('The video has now been loaded!');
} catch (e) {
// onError is executed if the asynchronous load fails.
onError(e);
}
} else {
if(document.getElementById('video') !== null)
setTimeout(initPlayer, 5000, usr);
}
}
function onErrorEvent(event) {
// Extract the shaka.util.Error object from the event.
onError(event.detail);
}
function onError(error) {
// Log the error.
console.error('Error code', error.code, 'object', error);
}

2
site/local.css Normal file
View File

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

385
site/play.svg Normal file
View File

@ -0,0 +1,385 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:ns1="http://sozi.baierouge.fr"
id="svg1307"
sodipodi:docname="media-playback-start.svg"
inkscape:export-filename="/home/lapo/Desktop/Grafica/Icone/media-actions-outlines.png"
viewBox="0 0 48 48"
sodipodi:version="0.32"
inkscape:export-xdpi="90"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-ydpi="90"
inkscape:version="0.46"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions"
>
<defs
id="defs1309"
>
<linearGradient
id="linearGradient2306"
y2="95"
gradientUnits="userSpaceOnUse"
x2="70.827"
gradientTransform="translate(-45 -71.094)"
y1="124.12"
x1="71.289"
inkscape:collect="always"
>
<stop
id="stop5077"
style="stop-color:#adb0a8"
offset="0"
/>
<stop
id="stop5079"
style="stop-color:#464744"
offset="1"
/>
</linearGradient
>
<radialGradient
id="radialGradient2314"
gradientUnits="userSpaceOnUse"
cy="83.991"
cx="107.59"
gradientTransform="matrix(.053243 -.83624 2.0195 .12857 -151.92 108.08)"
r="12.552"
inkscape:collect="always"
>
<stop
id="stop2693"
style="stop-color:#ffffff"
offset="0"
/>
<stop
id="stop2695"
style="stop-color:#d3d7cf"
offset="1"
/>
</radialGradient
>
<linearGradient
id="linearGradient2690"
y2="88.924"
gradientUnits="userSpaceOnUse"
x2="70.952"
gradientTransform="matrix(1.1282 0 0 1.1282 -53.993 -83.36)"
y1="101.74"
x1="70.914"
inkscape:collect="always"
>
<stop
id="stop2686"
style="stop-color:#ffffff"
offset="0"
/>
<stop
id="stop2688"
style="stop-color:#000000"
offset="1"
/>
</linearGradient
>
</defs
>
<sodipodi:namedview
id="base"
inkscape:showpageshadow="false"
inkscape:zoom="1"
borderopacity="0.19607843"
inkscape:current-layer="layer1"
stroke="#555753"
guidetolerance="1px"
fill="#555753"
inkscape:grid-points="true"
inkscape:grid-bbox="true"
showgrid="false"
showguides="false"
bordercolor="#666666"
inkscape:window-x="326"
inkscape:guide-bbox="true"
inkscape:window-y="160"
inkscape:window-width="872"
inkscape:pageopacity="0.0000000"
inkscape:pageshadow="2"
pagecolor="#ffffff"
inkscape:cx="-117.42449"
inkscape:cy="12.980288"
inkscape:document-units="px"
inkscape:window-height="688"
showborder="true"
>
<sodipodi:guide
id="guide2194"
position="38.996647"
orientation="horizontal"
/>
<sodipodi:guide
id="guide2196"
position="9.0140845"
orientation="horizontal"
/>
<sodipodi:guide
id="guide2198"
position="9.0140845"
orientation="vertical"
/>
<sodipodi:guide
id="guide2200"
position="38.975184"
orientation="vertical"
/>
<sodipodi:guide
id="guide2202"
position="22.988281"
orientation="horizontal"
/>
<sodipodi:guide
id="guide2204"
position="23.908786"
orientation="vertical"
/>
<sodipodi:guide
id="guide4332"
position="157.99417"
orientation="vertical"
/>
<sodipodi:guide
id="guide4334"
position="-36.062446"
orientation="horizontal"
/>
<sodipodi:guide
id="guide4336"
position="-58.02695"
orientation="horizontal"
/>
<sodipodi:guide
id="guide4338"
position="180.00287"
orientation="vertical"
/>
<sodipodi:guide
id="guide4417"
position="107.92217"
orientation="vertical"
/>
<sodipodi:guide
id="guide4419"
position="129.93087"
orientation="vertical"
/>
<sodipodi:guide
id="guide5106"
position="19.996875"
orientation="horizontal"
/>
<sodipodi:guide
id="guide5119"
position="63.039674"
orientation="horizontal"
/>
<sodipodi:guide
id="guide5121"
position="49.066305"
orientation="horizontal"
/>
<sodipodi:guide
id="guide5307"
position="-86.007168"
orientation="horizontal"
/>
<sodipodi:guide
id="guide5309"
position="-108.09009"
orientation="horizontal"
/>
<sodipodi:guide
id="guide3111"
position="-100.15429"
orientation="horizontal"
/>
<inkscape:grid
id="GridFromPre046Settings"
opacity=".2"
color="#0000ff"
originy="0px"
originx="0px"
empspacing="2"
spacingy="0.5px"
spacingx="0.5px"
empopacity="0.4"
type="xygrid"
empcolor="#0000ff"
/>
</sodipodi:namedview
>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
>
<path
id="path2682"
style="stroke-linejoin:round;opacity:.15;color:#000000;stroke:url(#linearGradient2690);stroke-linecap:square;stroke-width:2;fill:none"
inkscape:r_cy="true"
inkscape:r_cx="true"
sodipodi:nodetypes="cccc"
d="m12 39.5v-30.5l26.07 14.817-26.07 15.683z"
/>
<path
id="path3375"
style="fill-rule:evenodd;color:#000000;fill:url(#radialGradient2314)"
inkscape:r_cy="true"
inkscape:r_cx="true"
sodipodi:nodetypes="cccc"
d="m12.499 37.811v-27.811l24.104 13.906-24.104 13.905z"
/>
<path
id="path2479"
style="stroke-linejoin:round;color:#000000;stroke:url(#linearGradient2306);stroke-linecap:square;fill:none"
inkscape:r_cy="true"
inkscape:r_cx="true"
sodipodi:nodetypes="cccc"
d="m12.499 37.811v-27.811l24.104 13.906-24.104 13.905z"
/>
<path
id="path2481"
style="fill-rule:evenodd;color:#000000;fill:#ffffff"
inkscape:r_cy="true"
inkscape:r_cx="true"
sodipodi:nodetypes="cccccccc"
d="m12.999 10.874v26.063l22.594-13.031-22.594-13.032zm1 1.75l19.563 11.282-19.563 11.281v-22.563z"
/>
<path
id="path2339"
style="opacity:.5;color:#000000;display:block;fill:#ffffff"
inkscape:r_cy="true"
inkscape:r_cx="true"
sodipodi:nodetypes="cccc"
d="m13.938 12.562v11.688c4.269-0.045 9.164-0.346 17.062-1.875l-17.062-9.813z"
/>
</g
>
<metadata
>
<rdf:RDF
>
<cc:Work
>
<dc:format
>image/svg+xml</dc:format
>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
/>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/"
/>
<dc:publisher
>
<cc:Agent
rdf:about="http://openclipart.org/"
>
<dc:title
>Openclipart</dc:title
>
</cc:Agent
>
</dc:publisher
>
<dc:title
>tango media start</dc:title
>
<dc:date
>2010-03-11T09:12:20</dc:date
>
<dc:description
>"Play" or "start" icon from &lt;A href="http://tango.freedesktop.org/Tango_Desktop_Project"&gt; Tango Project &lt;/A&gt; &#13;\n&lt;BR&gt;&lt;BR&gt;&#13;\nSince version 0.8.90 Tango Project icons are Public Domain: &lt;A href="http://tango.freedesktop.org/Frequently_Asked_Questions#Terms_of_Use.3F"&gt; Tango Project FAQ &lt;/A&gt;</dc:description
>
<dc:source
>https://openclipart.org/detail/31003/tango-media-start-by-warszawianka</dc:source
>
<dc:creator
>
<cc:Agent
>
<dc:title
>warszawianka</dc:title
>
</cc:Agent
>
</dc:creator
>
<dc:subject
>
<rdf:Bag
>
<rdf:li
>audio</rdf:li
>
<rdf:li
>button</rdf:li
>
<rdf:li
>externalsource</rdf:li
>
<rdf:li
>icon</rdf:li
>
<rdf:li
>play</rdf:li
>
<rdf:li
>playback</rdf:li
>
<rdf:li
>sign</rdf:li
>
<rdf:li
>start</rdf:li
>
<rdf:li
>symbol</rdf:li
>
<rdf:li
>tango</rdf:li
>
<rdf:li
>triangle</rdf:li
>
</rdf:Bag
>
</dc:subject
>
</cc:Work
>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/"
>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
/>
</cc:License
>
</rdf:RDF
>
</metadata
>
</svg
>

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

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

View File

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

View File

@ -9,12 +9,9 @@ async function init() {
if(tmp.length === 0){
console.log('No database version info, running initial migration.');
await require('./db/0').run();
await bringUpToDate();
}
else {
await bringUpToDate();
}
}
else {
console.log('Skipping database version check.');
}
@ -34,16 +31,19 @@ async function init() {
}
async function bringUpToDate(): Promise<void>{
var versions: Object[] = await db.query('select * from db_meta');
var scripts: Buffer[] | string[] = readdirSync('./src/db/', {withFileTypes: false});
var diff: number = scripts.length - versions.length
if(diff === 0){
var versions: Object[] = JSON.parse(JSON.stringify(await db.query('select * from db_meta'))); //ugh, don't ask
var scripts: any[] = readdirSync('./src/db/', {withFileTypes: false});
if(scripts.length - versions.length === 0){
console.log('No migration needed.');
} else {
console.log('Versions differ, migrating now.');
for(let i=0;i<diff;i++){
console.log('Migration to version '+Math.floor(scripts.length-(diff-i)));
await require('./db/'+scripts[Math.floor(scripts.length-(diff-i))]).run();
var diff: string[] = scripts.filter(n => {
//we have to use versions.some because {version: 0} === {version: 0} returns false lmao
return !versions.some(o => o['version']+''=== n.substring(0, n.length - 3))
});
for(let i=0;i<diff.length;i++){
console.log('Running migration '+diff[i]);
await require('./db/'+diff[i]).run();
}
console.log('Done migrating database.');
}

View File

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

View File

@ -4,7 +4,17 @@ try {
var localconfig: Object = parse(read('config/config.yml'));
console.log('Config file found.');
} catch (e) {
console.log('No config file found. Exiting.');
if(e['reason']) {
console.log('Error parsing config on line '+e['mark']['line']+', with reason: '+e['reason']);
}
else {
console.log('Config Error: '+e['code']);
switch(e['code']){
case 'ENOENT':
console.log('Does the file exist?');
break;
}
}
process.exit();
}
const config: Object = {
@ -29,7 +39,13 @@ const config: Object = {
connectionTimeout: '1000',
insecureAuth: false,
debug: false }, localconfig['database']),
hwaccel: Object.assign({
type: null,
device: null,
decode: false
}, localconfig['hwaccel']),
rtmp: Object.assign({
cluster: false,
port: 1935,
chunk_size: 6000,
gop_cache: true,
@ -75,8 +91,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({

View File

@ -40,6 +40,7 @@ async function rmUser(name: string){
await query('delete from user_meta where username='+raw.escape(name)+' limit 1');
await query('delete from chat_integration where username='+raw.escape(name)+' limit 1');
await query('delete from twitch_mirror where username='+raw.escape(name)+' limit 1');
await query('delete from ch_bans where channel='+raw.escape(name));
return true;
}

21
src/db/3.ts Normal file
View File

@ -0,0 +1,21 @@
import * as db from "../database";
import * as dirty from "dirty";
async function run () {
await db.query('CREATE TABLE IF NOT EXISTS ch_bans(channel VARCHAR(25), target VARCHAR(45), time BIGINT, length INT DEFAULT 30)');
console.log('!!! This migration has a race condition when run from the `npm run migrate` command. If thats how this was called, please re-run this migration manually.\n!!! Run `npm run v3-manual` to do so');
var bansdb = new dirty('./config/bans.db')
bansdb.on('load', async () => {
bansdb.forEach(async (key, val) => {
let ips = Object.keys(val);
for(var i=0;i<ips.length;i++){
await db.query('INSERT INTO ch_bans (channel, target, time, length) VALUES ('+db.raw.escape(key)+', '+db.raw.escape(ips[i])+', '+val[ips[i]].time+', '+val[ips[i]].length+')');
}
});
await db.query('INSERT INTO db_meta (version) VALUES (3)');
console.log('Done migrating bans.db');
console.log('If this was a manual migration, you can kill the process now.');
});
}
export { run }

View File

@ -17,7 +17,6 @@ const app = express();
const server = http.createServer(app);
const io = socketio(server);
const store = dirty();
var banlist;
var jwkey;
try{
jwkey = JWK.asKey(readFileSync('./config/jwt.pem'));
@ -59,6 +58,13 @@ async function init(){
await initAPI();
//static files if nothing else matches
app.use(express.static(config['http']['directory']));
//Fake static files
app.get('/shaka-player.compiled.js', (req, res) => {
res.sendFile(process.cwd()+'/node_modules/shaka-player/dist/shaka-player.compiled.js');
});
app.get('/shaka-player.compiled.map', (req, res) => {
res.sendFile(process.cwd()+'/node_modules/shaka-player/dist/shaka-player.compiled.map');
});
//client-side site routes
if(!config['http']['server_side_render'])
await initFE();
@ -70,7 +76,7 @@ async function init(){
else res.status(404).render('404.njk', njkconf);
//res.status(404).render('404.njk', njkconf);
});
banlist = new dirty('./config/bans.db').on('load', () => {initChat()});
await initChat();
server.listen(config['http']['port']);
}
@ -168,6 +174,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']
},
@ -246,6 +253,7 @@ async function initAPI() {
api.register(req.body.username, req.body.password, req.body.confirm, true).then((result) => {
if(result[0]) return genToken(req.body.username).then((t) => {
res.cookie('Authorization', t, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'});
res.cookie('X-Auth-As', req.body.username, {maxAge: 604800000, httpOnly: false, sameSite: 'Lax'})
res.json(result);
api.useInvite(req.body.invite);
return;
@ -260,6 +268,7 @@ async function initAPI() {
api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => {
if(result[0]) return genToken(req.body.username).then((t) => {
res.cookie('Authorization', t, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'});
res.cookie('X-Auth-As', req.body.username, {maxAge: 604800000, httpOnly: false, sameSite: 'Lax'})
res.json(result);
return;
});
@ -581,9 +590,10 @@ async function initChat() {
socket.on('JOINROOM', async (data) => {
let t: any = await db.query('select username from users where username='+db.raw.escape(data));
if(t[0]){
if(banlist.get(data) && banlist.get(data)[socket['handshake']['address']]){
if(Math.floor(banlist.get(data)[socket['handshake']['address']]['time'] + (banlist.get(data)[socket['handshake']['address']]['length'] * 60)) < Math.floor(Date.now() / 1000)){
banlist.set(data, Object.assign({}, banlist.get(data), {[socket['handshake']['address']]: null}));
let b = await db.query('select * from ch_bans where target='+db.raw.escape(socket['handshake']['address'])+' and channel='+db.raw.escape(data));
if(b[0]){
if(Math.floor(b[0].time + (b[0].length * 60)) < Math.floor(Date.now() / 1000)){
await db.query('delete from ch_bans where target='+db.raw.escape(b[0].target)+'and channel='+db.raw.escape(b[0].channel)+' and time='+db.raw.escape(b[0].time)+' and length='+db.raw.escape(b[0].length));
}
else {
socket.emit('ALERT', 'You are banned from that room');
@ -672,23 +682,27 @@ async function initChat() {
}
else socket.emit('ALERT', 'Not authorized to do that.');
});
socket.on('BAN', (data: Object) => {
socket.on('BAN', async (data: Object) => {
if(socket.nick === data['room']){
let id: string = store.get(data['nick']);
if(id){
if(Array.isArray(id)) {
for(let i=0;i<id.length;i++){
let target = io.sockets.connected[id[i]];
if(typeof(data['time']) === 'number' && (data['time'] !== 0 && data['time'] !== NaN)) banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[target.ip]: {time: Math.floor(Date.now() / 1000), length: data['time']}}));
else banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[target.ip]: {time: Math.floor(Date.now() / 1000), length: 30}}));
if(typeof(data['time']) === 'number' && (data['time'] !== 0 && data['time'] !== NaN))
await db.query('insert into ch_bans (channel, target, time, length) VALUES ('+db.raw.escape(data['room'])+', '+db.raw.escape(target.ip)+', '+db.raw.escape(Math.floor(Date.now() / 1000))+', '+db.raw.escape(data['time'])+')');
else
await db.query('insert into ch_bans (channel, target, time, length) VALUES ('+db.raw.escape(data['room'])+', '+db.raw.escape(target.ip)+', '+db.raw.escape(Math.floor(Date.now() / 1000))+', '+db.raw.escape(30)+')');
target.leave(data['room']);
}
io.to(data['room']).emit('ALERT', data['nick']+' was banned.');
return;
}
let target = io.sockets.connected[id];
if(typeof(data['time']) === 'number' && (data['time'] !== 0 && data['time'] !== NaN)) banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[target.ip]: {time: Math.floor(Date.now() / 1000), length: data['time']}}));
else banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[target.ip]: {time: Math.floor(Date.now() / 1000), length: 30}}));
if(typeof(data['time']) === 'number' && (data['time'] !== 0 && data['time'] !== NaN))
await db.query('insert into ch_bans (channel, target, time, length) VALUES ('+db.raw.escape(data['room'])+', '+db.raw.escape(target.ip)+', '+db.raw.escape(Math.floor(Date.now() / 1000))+', '+db.raw.escape(data['time'])+')');
else
await db.query('insert into ch_bans (channel, target, time, length) VALUES ('+db.raw.escape(data['room'])+', '+db.raw.escape(target.ip)+', '+db.raw.escape(Math.floor(Date.now() / 1000))+', '+db.raw.escape(30)+')');
target.leave(data['room']);
io.to(data['room']).emit('ALERT', target.nick+' was banned.');
}
@ -696,10 +710,11 @@ async function initChat() {
}
else socket.emit('ALERT', 'Not authorized to do that.');
});
socket.on('UNBAN', (data: Object) => {
socket.on('UNBAN', async (data: Object) => {
if(socket.nick === data['room']){
if(banlist.get(data['room']) && banlist.get(data['room'])[data['ip']]){
banlist.set(data['room'], Object.assign({}, banlist.get(data['room']), {[data['ip']]: null}));
let b = await db.query('select * from ch_bans where channel='+db.raw.escape(data['room'])+' and target='+db.raw.escape(data['ip']));
if(b[0]){
await db.query('delete from ch_bans where channel='+db.raw.escape(data['room'])+' and target='+db.raw.escape(data['ip']));
socket.emit('ALERT', data['ip']+' was unbanned.');
}
else
@ -707,13 +722,13 @@ async function initChat() {
}
else socket.emit('ALERT', 'Not authorized to do that.');
});
socket.on('LISTBAN', (data: Object) => {
socket.on('LISTBAN', async (data: Object) => {
if(socket.nick === data['room']){
if(banlist.get(data['room'])) {
let bans = Object.keys(banlist.get(data['room']));
let b = await db.query('select target from ch_bans where channel='+db.raw.escape(data['room']));
if(b[0]) {
let str = '';
for(let i=0;i<bans.length;i++){
str += bans[i]+', ';
for(let i=0;i<b.length;i++){
str += b[i].target+', ';
}
socket.emit('ALERT', 'Banned IP adresses: '+str.substring(0, str.length - 2));
return;

View File

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

View File

@ -3,7 +3,8 @@ import {init as clean} from "./cleanup";
import { config } from "./config";
async function run() {
process.argv = process.argv.concat(['--skip-compile']);
await initDB();
await clean(false);
await clean();
}
run().then(() => {process.exit()});

View File

@ -42,9 +42,10 @@ function init () {
if(session.audioCodec !== 0 && session.videoCodec !== 0){
transCommand(results[0].username, key).then((r) => {
execFile(config['media']['ffmpeg'], r, {maxBuffer: Infinity}, (err, stdout, stderr) => {
/*console.log(err);
console.log(stdout);
console.log(stderr);*/
//console.log(r);
//console.log(err);
//console.log(stdout);
//console.log(stderr);
});
});
break;
@ -126,29 +127,51 @@ function init () {
async function transCommand(user: string, key: string): Promise<string[]>{
let args: string[] = ['-loglevel', 'fatal', '-y'];
let vcodec: string = 'libx264';
if(config['hwaccel']['type'] === 'nvenc'){
vcodec = 'h264_nvenc';
if(config['hwaccel']['decode']){
args = args.concat(['-hwaccel', 'cuda']);
if(config['hwaccel']['device'])
args = args.concat(['-hwaccel_device', config['hwaccel']['device']]);
args = args.concat(['-hwaccel_output_format', 'cuda']);
}
}
else if (config['hwaccel']['type'] === 'vaapi') {
vcodec = 'h264_vaapi';
if(config['hwaccel']['decode'] === 'fallback'){
args = args.concat('init_hw_device', 'vaapi=foo:'+config['hwaccel']['device'], '-hwaccel vaapi', '-hwaccel_output_format', 'vaapi', '-hwaccel_device', 'foo');
} else if (config['hwaccel']['decode']) {
args = args.concat(['-hwaccel', 'vaapi', '-hwaccel_output_format', 'vaapi', '-vaapi_device', config['hwaccel']['device']]);
}
}
if(config['transcode']['inputflags'] !== null && config['transcode']['inputflags'] !== "") args = args.concat(config['transcode']['inputflags'].split(" "));
args = args.concat(['-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+config['media']['privateEndpoint']+'/'+key, '-movflags', '+faststart']);
if(config['transcode']['adaptive']===true && config['transcode']['variants'] > 1) {
for(let i=0;i<config['transcode']['variants'];i++){
args = args.concat(['-map', '0:2']);
args = args.concat(['-map', '0:v']);
}
args = args.concat(['-map', '0:1', '-c:a', 'aac', '-c:v:0', 'libx264']);
args = args.concat(['-map', '0:a', '-c:a', 'aac', '-c:v:0', vcodec]);
for(let i=1;i<config['transcode']['variants'];i++){
args = args.concat(['-c:v:'+i, 'libx264',]);
args = args.concat(['-c:v:'+i, vcodec,]);
}
if(!config['hwaccel']['type']){
for(let i=1;i<config['transcode']['variants'];i++){
let crf: number = Math.floor(18 + (i * 8)) > 51 ? 51 : Math.floor(18 + (i * 7));
args = args.concat(['-crf:'+i, ''+crf]);
}
}
for(let i=1;i<config['transcode']['variants'];i++){
let bv: number = Math.floor((5000 / config['transcode']['variants']) * (config['transcode']['variants'] - i));
let bv: number = Math.floor((10000 / config['transcode']['variants']) * (config['transcode']['variants'] - i));
args = args.concat(['-b:v:'+i, ''+bv]);
}
}
else {
args = args.concat(['-c:a', 'aac', '-c:v', 'libx264']);
args = args.concat(['-c:a', 'aac', '-c:v', vcodec]);
}
args = args.concat(['-preset', 'veryfast', '-tune', 'zerolatency']);
if(!config['hwaccel']['type'])
args = args.concat(['-preset', 'veryfast']);
args = args.concat(['-tune', 'zerolatency']);
//if(config['transcode']['format'] === 'dash')
args = args.concat(['-remove_at_exit', '1', '-seg_duration', '1', '-window_size', '30']);
if(config['transcode']['outputflags'] !== null && config['transcode']['outputflags'] !== "") args = args.concat(config['transcode']['outputflags'].split(" "));

6
src/v3manual.ts Normal file
View File

@ -0,0 +1,6 @@
import * as db from "./database";
async function main() {
await db.init();
await require('./db/3.ts').run();
}
main();

View File

@ -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 %}
@ -29,8 +52,8 @@
<div>
<div>
<b>Satyr</b></br>
<a href="https://gitlab.com/knotteye/satyr">About</a></br>
<a href="https://gitlab.com/knotteye/satyr/-/releases">v{{ version }}</a>
<a href="https://pond.waldn.net/git/knotteye/satyr">About</a></br>
<a href="https://pond.waldn.net/git/knotteye/satyr/releases">v{{ version }}</a>
</div>
<div>
<img src="/logo.svg" height="50" />

View File

@ -1,15 +1,55 @@
{% extends "base.njk" %}
{% block head %}
<!--<script src="/videojs/video.min.js"></script>
<link rel="stylesheet" type="text/css" href="/videojs/video-js.min.css">-->
<script src="/dashjs/dash.all.min.js"></script>
<script src="/shaka-player.compiled.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');
shakaPolyFilled = false;
var manifestUri = document.location.protocol+'//'+document.location.host+'/live/{{ username }}/index.mpd';
async function initPlayer() {
console.log('Trying to initialize player.');
if(!shakaPolyFilled){
shaka.polyfill.installAll();
shakaPolyFilled = true;
}
var live = JSON.parse(await makeRequest("GET", "/api/{{ username }}/config")).live;
if(live){
// Create a Player instance.
const video = document.getElementById('video');
const player = new shaka.Player(video);
// Listen for error events.
player.addEventListener('error', onErrorEvent);
video.addEventListener('play', () => {
document.getElementById('playbtn').style.visibility = 'hidden';
});
video.addEventListener('pause', () => {
document.getElementById('playbtn').style.visibility = 'visible';
});
// Try to load a manifest.
// This is an asynchronous process.
try {
await player.load(manifestUri);
// This runs if the asynchronous load is successful.
console.log('The video has now been loaded!');
} catch (e) {
// onError is executed if the asynchronous load fails.
onError(e);
}
} else {
setTimeout(initPlayer, 5000);
}
}
function onErrorEvent(event) {
// Extract the shaka.util.Error object from the event.
onError(event.detail);
}
function onError(error) {
// Log the error.
console.error('Error code', error.code, 'object', error);
}
document.addEventListener('DOMContentLoaded', initPlayer);
</script>
{% endblock %}
{% block content %}
@ -22,13 +62,9 @@ function newPopup(url) {
</br>
<span style="float: left;font-size: large;"><a href="/live/{{ username }}/index.mpd">{{ username }}</a> | {{ title | escape }}</b></span><span style="float: right;font-size: large;"> Links | <a href="rtmp://{{ domain }}/live/{{ username }}">Watch</a> <a href="JavaScript:newPopup('/chat?room={{ username }}');">Chat</a> <a href="/vods/{{ username }}">VODs</a></span>
<div id="jscontainer">
<div id="jschild" style="width: 70%;height: 100%;">
<!--<video controls poster="/thumbnail.jpg" class="video-js vjs-default-skin" id="live-video" style="width:100%;height:100%;min-height: 500px;"></video>-->
<video id="videoPlayer" style="width:100%;height:100%;width: 950px;height: 534px;background-color: rgb(0, 0, 0);" poster="/thumbnail.jpg" autoplay muted></video>
<!--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>
<div id="jschild" style="width: 70%;height: 100%;position: relative;">
<image id="playbtn" src="/play.svg" alt="Play Stream" style="width:100%;height:100%;width: 950px;height: 534px;position: absolute;" onclick="document.getElementById('video').paused ? document.getElementById('video').play() : document.getElementById('video') .pause();"></image>
<video id="video" style="width:100%;height:100%;width: 950px;height: 534px;background-color: rgb(0, 0, 0);" poster="/thumbnail.jpg" autoplay onclick="this.paused ? this.play() : this.pause();"></video>
</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>
@ -37,21 +73,4 @@ function newPopup(url) {
</br>
<noscript>The webclients for the stream and the chat require javascript, but feel free to use the direct links above!</br></br></noscript>
{{ about | escape }}
<!--<script>
window.HELP_IMPROVE_VIDEOJS = false;
var player = videojs('live-video', {
html: {
nativeCaptions: false,
},
});
player.ready(function() {
player.on("error", () => {
document.querySelector(".vjs-modal-dialog-content").textContent = "The stream is currently offline.";
});
player.src({
src: '/live/{{ username }}/index.mpd',
type: 'application/dash+xml'
});
})
</script>-->
{% endblock %}