I don't even know.
Filled out API for user management Proper escaping of user input for SQL queries (stream keys aren't user input) Filled out frontend with profile management, vods, etc. I don't remember there's probably more, fuck.merge-requests/1/merge
parent
eefa7c8dc7
commit
f9e4a7a5c6
|
@ -8,7 +8,8 @@ name = ''
|
||||||
domain = ''
|
domain = ''
|
||||||
registration = false
|
registration = false
|
||||||
webFormat = 'hls'
|
webFormat = 'hls'
|
||||||
restrictedNames = ['live','stream']
|
restrictedNames = ['live']
|
||||||
|
rootredirect = '/users/live'
|
||||||
|
|
||||||
[ircd]
|
[ircd]
|
||||||
enable = false
|
enable = false
|
||||||
|
@ -45,7 +46,6 @@ allow_origin = '*'
|
||||||
directory = './site'
|
directory = './site'
|
||||||
|
|
||||||
[media]
|
[media]
|
||||||
streamKeys = false
|
|
||||||
record = false
|
record = false
|
||||||
publicEndpoint = 'live'
|
publicEndpoint = 'live'
|
||||||
privateEndpoint = 'stream'
|
privateEndpoint = 'stream'
|
||||||
|
|
|
@ -7,5 +7,10 @@ CREATE TABLE users(
|
||||||
password_hash BINARY(60),
|
password_hash BINARY(60),
|
||||||
stream_key CHAR(20),
|
stream_key CHAR(20),
|
||||||
record_flag TINYINT,
|
record_flag TINYINT,
|
||||||
is_mod TINYINT
|
);
|
||||||
|
CREATE TABLE user_meta(
|
||||||
|
username VARCHAR(25),
|
||||||
|
title VARCHAR(120),
|
||||||
|
about VARCHAR(5000),
|
||||||
|
live TINYINT
|
||||||
);
|
);
|
|
@ -13,6 +13,8 @@ do
|
||||||
echo "Please the domain name for your instance.[]"
|
echo "Please the domain name for your instance.[]"
|
||||||
read domain
|
read domain
|
||||||
done
|
done
|
||||||
|
echo "Enter the contact email for the instance."
|
||||||
|
read email
|
||||||
echo "Please enter the path to the ffmpeg binary on your system.[$(which ffmpeg)]"
|
echo "Please enter the path to the ffmpeg binary on your system.[$(which ffmpeg)]"
|
||||||
read ffmpeg
|
read ffmpeg
|
||||||
ffmpeg="${ffmpeg:=$(which ffmpeg)}"
|
ffmpeg="${ffmpeg:=$(which ffmpeg)}"
|
||||||
|
@ -30,13 +32,13 @@ read dbhost
|
||||||
dbhost="${dbhost:=localhost}"
|
dbhost="${dbhost:=localhost}"
|
||||||
if [ "$dbhost" != "localhost" ]
|
if [ "$dbhost" != "localhost" ]
|
||||||
then
|
then
|
||||||
echo "Please enter the ip this server will connect to the database with.[*]"
|
echo "Please enter the public ip or hostname this server will connect to the database with.[*]"
|
||||||
read dbclient
|
read dbclient
|
||||||
dbclient="${dbclient:='*'}"
|
dbclient="${dbclient:='*'}"
|
||||||
else
|
else
|
||||||
dbclient="localhost"
|
dbclient="localhost"
|
||||||
fi
|
fi
|
||||||
sed -e "s#<iname>#$name#g" -e "s#<domain>#$domain#g" -e "s#<ffmpeg>#$ffmpeg#g" -e "s#<dbuser>#$dbuser#g" -e "s#<dbname>#$dbname#g" -e "s#<dbpass>#$dbpass#g" -e "s#<dbhost>#$dbhost#g" install/template.local.toml > config/generated.toml
|
sed -e "s#<iname>#$name#g" -e "s#<domain>#$domain#g" -e "s#<ffmpeg>#$ffmpeg#g" -e "s#<dbuser>#$dbuser#g" -e "s#<dbname>#$dbname#g" -e "s#<dbpass>#$dbpass#g" -e "s#<dbhost>#$dbhost#g" -e "s#<email>#$email#g" install/template.local.toml > config/generated.toml
|
||||||
sed -e "s#<dbuser>#$dbuser#g" -e "s#<dbname>#$dbname#g" -e "s#<dbpass>#$dbpass#g" -e "s#<dbhost>#$dbhost#g" -e "s#<dbclient>#$dbclient#g" install/db_template.sql > install/db_setup.sql
|
sed -e "s#<dbuser>#$dbuser#g" -e "s#<dbname>#$dbname#g" -e "s#<dbpass>#$dbpass#g" -e "s#<dbhost>#$dbhost#g" -e "s#<dbclient>#$dbclient#g" install/db_template.sql > install/db_setup.sql
|
||||||
echo "A setup script for the database has been generated at install/db_setup.sql. Please run it by connecting to your database software and executing 'source install/db_setup.sql;''"
|
echo "A setup script for the database has been generated at install/db_setup.sql. Please run it by connecting to your database software and executing 'source install/db_setup.sql;''"
|
||||||
echo "A default configuration file has been generated at config/generated.toml"
|
echo "A default configuration file has been generated at config/generated.toml"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
[satyr]
|
[satyr]
|
||||||
name = '<iname>'
|
name = '<iname>'
|
||||||
domain = '<domain>'
|
domain = '<domain>'
|
||||||
|
email = '<email>'
|
||||||
registration = false
|
registration = false
|
||||||
|
|
||||||
[media]
|
[media]
|
||||||
streamKeys = false
|
|
||||||
record = false
|
record = false
|
||||||
ffmpeg = '<ffmpeg>'
|
ffmpeg = '<ffmpeg>'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" 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" id="fife_xA0_Image_1_" xml:space="preserve" viewBox="-111.032 -46.682 321.472 283.564" version="1.1" x="0px" y="0px" enable-background="new -111.032 -46.682 321.472 283.564">
|
||||||
|
<path d="m155.67-37.952c-5.231 6.267-24.74 4.75-32.224 1.611 5.56-7.555 24.68-6.525 32.22-1.611zm-22.56 0.806c3.974 1.07 14.413 2.217 16.113-1.609-7.09 0.682-12.45-2.785-16.11 1.609z" fill="#fff"/>
|
||||||
|
<path d="m195.14-36.341c-2.6 1.162-6.759 0.762-10.477 0.805v-4.027h-12.888c0.046 3.984 7.746 0.312 8.054 4.027-6.107-0.065-12.688 0.339-16.111-2.416 5.75-6.895 26.65-4.586 31.43 1.611z" fill="#fff"/>
|
||||||
|
<path d="m-82.778-40.368c4.669 1.775 13.573-0.688 16.11 3.222-6.816 4.873-22.413 3.147-28.999-0.806 5.384-3.125 14.301 3.877 24.166 1.611-0.826-4.276-11.636 1.434-11.277-4.027z" fill="#fff"/>
|
||||||
|
<path d="m3.415-36.341c-5.877 3.93-24.39 3.347-31.416 0.805 5.411-6.736 24.669-5.846 31.416-0.805zm-11.278 0c6.027-0.385 3.923-0.477-1.611-0.805-3.873-0.229-6.028-1.129-9.667 0.805 4.882-0.118 8.703 0.163 11.278 0z" fill="#fff"/>
|
||||||
|
<path d="m79.135-34.73c-10.325 3.346-22.793 2.555-33.83 0 5.519-8.16 27.184-6.887 33.83 0zm-24.971-0.807c5.646-0.529 13.421 1.065 17.721-0.805-4.903-0.996-13.963-1.748-17.721 0.805z" fill="#fff"/>
|
||||||
|
<path d="m117-37.146c-6.592 4.504-25.177 7.36-32.224 1.609 4.705-6.848 25.514-6.296 32.224-1.609zm-26.586 0.805c4.541 1.131 14.092 0.81 17.725-1.611-5.91 0.537-14.255-1.364-17.726 1.611z" fill="#fff"/>
|
||||||
|
<path d="m41.276-36.341c-6.116 5.196-25.757 7.405-33.027 1.611 6.963-7.585 23.659-4.958 33.027-1.611zm-7.251 0.804c-4.663-1.254-14.69-1.403-19.332 0 4.724 0.615 13.264 1.672 19.332 0z" fill="#fff"/>
|
||||||
|
<path d="m149.22-38.759c-1.7 3.83-12.14 2.683-16.113 1.61 3.66-4.391 9.02-0.924 16.11-1.61z"/>
|
||||||
|
<path d="m-32.835-35.537c-5.256 4.797-23.3 2.689-29 0 5.72-5.52 22.724-3.504 29 0zm-11.277 0.807h5.638c-4.523-1.564-6.38-1.083-11.278-1.611 0.478 3.738 3.975-0.215 5.64 1.611z" fill="#fff"/>
|
||||||
|
<path d="m-19.14-36.341c3.639-1.934 5.793-1.038 9.667-0.805 5.534 0.331 7.638 0.419 1.611 0.805-2.576 0.163-6.397-0.118-11.278 0z"/>
|
||||||
|
<path d="m108.14-37.952c-3.633 2.421-13.184 2.742-17.725 1.611 3.47-2.975 11.815-1.074 17.725-1.611z"/>
|
||||||
|
<path d="m71.884-36.341c-4.3 1.87-12.074 0.275-17.721 0.805 3.759-2.554 12.819-1.802 17.721-0.805z"/>
|
||||||
|
<path d="m14.693-35.537c4.642-1.403 14.669-1.254 19.332 0-6.068 1.672-14.608 0.615-19.332 0z"/>
|
||||||
|
<path d="m160.5-35.537c9.24 4.864 27.5 0.944 37.056 1.612 2.549 17.809-0.805 40.288 0.803 57.194-10.791 1.335-27.071 1.335-37.858 0-1.46-18.603-1.15-42.174 0-58.806zm6.44 33.029c-6.007 9.176 0.349 24.895 14.502 21.75 5.746-3.373 12.422-12.616 8.05-20.945-5.98-1.802-15.24-0.322-22.55-0.805z" fill="#fff"/>
|
||||||
|
<path d="m-98.084-34.73c9.25 1.662 16.537 4.239 27.39 2.417 1.842 17.487 0.295 38.37 0.804 57.192-7.783 1.616-18.335 0.457-27.388 0.806-2.882-18.735-1.027-40.26-0.806-60.415zm4.029 34.639c-2.588 20.034 24.421 23.554 22.555 0.804-7.838-2.456-15.198-2.695-22.555-0.804z" fill="#fff"/>
|
||||||
|
<path d="m-65.861-33.924c6.995 4.252 22.921 3.06 31.416 3.223-0.526 18.806 1.06 39.727-0.806 57.194-9.523-1.22-20.844-0.639-30.61-1.613 0.25-19.598-2.277-38.546 0-58.804zm11.277 51.555c12.228 4.01 20.408-6.059 16.916-17.722-5.719-1.799-14.707-0.33-21.75-0.806-1.073 5.772 0.722 15.288 4.834 18.528z" fill="#fff"/>
|
||||||
|
<path d="m154.86-33.924h1.606c-0.741 21.376 0.163 35.205-1.606 57.999h-31.417c-2.662-18.434-1.597-41.267-0.807-57.194 11.13 1.171 22.47 2.216 32.23-0.805zm-28.2 31.416c-3.625 29.265 30.511 22.387 24.971 0.805-6.57-1.57-19.69-0.6-24.97-0.805z" fill="#fff"/>
|
||||||
|
<path d="m117.8-33.12c1.561 21.344-0.039 36.579 0 57.194-15.91-0.721-24.74 1.954-35.446-0.805-0.249-21.311-1.314-36.401 0.807-55.583 10.004 3.694 23.879 1.343 34.639-0.806zm-26.582 31.416c-2.53 6.71 0.53 14.399 4.025 17.723 12.518 3.856 20.117-3.27 18.531-16.11-9.89-2.129-14.085-0.844-22.552-1.613z" fill="#fff"/>
|
||||||
|
<path d="m2.609-32.313c0.816 16.782 0.107 41.998-0.805 57.192-11.031-0.292-21.271 0.213-31.416 0.806-1.08-19.235-1.615-41.213-0.805-57.999 11.623 2.038 21.9 0.662 33.026 0.001zm-25.777 32.222c1.613 5.141 0.649 9.017 1.61 13.693 8.727 12.01 28.752-2.249 20.139-14.499-5.997 1.973-15.248-1.836-21.749 0.806z" fill="#fff"/>
|
||||||
|
<path d="m6.638-31.508c9.206 4.981 24.508 1.426 34.638 0 1.102 16.984 1.399 37.029 0 56.388h-34.638c-2.008-17.926-0.128-42.707 0-56.388zm8.861 32.221c-0.543 7.258 0.421 13.006 4.028 16.111 12.942 4.615 21.644-5.471 18.528-16.915-8.133 0.171-15.605-2.528-22.556 0.804z" fill="#fff"/>
|
||||||
|
<path d="m76.719-31.508c4.046 13.211 0.346 38.062 1.61 56.388h-32.224c-0.669-14.82-1.717-35.73 0-55.581 10.617 0.878 20.352 0.471 30.614-0.807zm-24.97 32.221c-3.844 22.079 27.584 20.958 24.165 0-8.886-1.251-17.271-2.494-24.165 0z" fill="#fff"/>
|
||||||
|
<path d="m151.63-1.704c5.537 21.586-28.599 28.46-24.971-0.805 5.28 0.206 18.4-0.764 24.97 0.805zm-21.74 9.668c0.53 9.938 18.385 11.046 17.722-0.806-5.73 0.45-13.31-0.955-17.72 0.806z"/>
|
||||||
|
<path d="m189.5-1.704c4.365 8.329-2.311 17.572-8.057 20.945-14.153 3.145-20.506-12.574-14.498-21.75 7.31 0.484 16.57-0.996 22.56 0.805zm-21.75 10.473c-0.288 12.131 20.181 11.106 19.332-0.805-4.73-1.068-13.89-0.053-19.33 0.805z"/>
|
||||||
|
<path d="m-71.5 0.713c1.866 22.747-25.143 19.229-22.555-0.804 7.357-1.891 14.717-1.652 22.555 0.804zm-4.834 6.445h-12.083c-0.491 7.492 12.442 7.27 12.083 0z"/>
|
||||||
|
<path d="m113.78-0.091c1.591 12.841-6.011 19.97-18.531 16.11-3.495-3.327-6.556-11.017-4.025-17.723 8.461 0.769 12.656-0.516 22.556 1.613zm-19.337 8.055c-2.248 10.146 17.497 9.884 15.305 0-3.25-1.517-9.09 0.215-15.307 0z"/>
|
||||||
|
<path d="m-59.417-0.897c7.042 0.477 16.03-0.993 21.75 0.806 3.492 11.666-4.688 21.732-16.916 17.722-4.113-3.24-5.908-12.756-4.834-18.528zm4.028 10.473c-0.928 10.566 15.589 8.363 15.303 0-3.462-1.307-11.84-1.307-15.303 0z"/>
|
||||||
|
<path d="m-1.419-0.897c8.614 12.25-11.412 26.506-20.139 14.499-0.961-4.676 0.003-8.552-1.61-13.693 6.501-2.642 15.752 1.167 21.749-0.806zm-16.111 16.11c5.285 3.028 13.695 0.563 14.5-5.638-3.898-1.743-10.908-0.371-16.11-0.807 0.199 2.489 0.816 4.555 1.61 6.445z"/>
|
||||||
|
<path d="m38.054-0.091c3.116 11.444-5.586 21.53-18.528 16.915-3.606-3.104-4.571-8.853-4.028-16.111 6.952-3.332 14.424-0.633 22.556-0.804zm-19.334 8.055c-1.618 12.351 16.951 10.428 16.112 0-6.655 0.257-10.288-1.273-16.112 0z"/>
|
||||||
|
<path d="m75.914 0.713c3.419 20.958-28.009 22.079-24.165 0 6.894-2.494 15.279-1.251 24.165 0zm-19.333 7.251c-2.312 8.975 16.636 11.408 15.304-0.806-4.942 0.426-11.659-0.919-15.304 0.806z"/>
|
||||||
|
<path d="m-88.417 7.158h12.083c0.359 7.27-12.574 7.492-12.083 0z" fill="#fff"/>
|
||||||
|
<path d="m34.832 7.964c0.839 10.428-17.732 12.351-16.112 0 5.824-1.273 9.457 0.257 16.112 0z" fill="#fff"/>
|
||||||
|
<path d="m71.884 7.158c1.332 12.218-17.615 9.781-15.304 0.806 3.646-1.725 10.363-0.38 15.304-0.806z" fill="#fff"/>
|
||||||
|
<path d="m109.75 7.964c2.192 9.884-17.553 10.146-15.305 0 6.215 0.215 12.055-1.517 15.305 0z" fill="#fff"/>
|
||||||
|
<path d="m147.61 7.158c0.656 11.852-17.191 10.748-17.722 0.806 4.41-1.761 11.99-0.356 17.72-0.806z" fill="#fff"/>
|
||||||
|
<path d="m187.08 7.964c0.849 11.911-19.62 12.936-19.332 0.805 5.44-0.858 14.6-1.873 19.33-0.805z" fill="#fff"/>
|
||||||
|
<path d="m-40.085 9.576c0.283 8.363-16.231 10.566-15.303 0 3.462-1.307 11.84-1.307 15.303 0z" fill="#fff"/>
|
||||||
|
<path d="m-19.14 8.769c5.202 0.436 12.212-0.933 16.11 0.807-0.805 6.201-9.215 8.666-14.5 5.638-0.794-1.891-1.411-3.957-1.61-6.445z" fill="#fff"/>
|
||||||
|
<path d="m204 25.686v15.306c-104.84-0.058-199.26 1.636-308.53 2.417-1.876-4.032 0.116-11.933-1.61-16.112 37.658 0.876 71.814-2.208 110.36 0.806 51.482-5.085 134.01-1.075 199.78-2.417z" fill="#fff"/>
|
||||||
|
<path d="m197.55 46.631c-0.777 57.474 1.235 122.58-0.807 181.25-10.428 5.448-26.192 7.001-35.442-0.806-1.121-57.807-3.209-127.65 0-181.25 9.04-0.767 23.31-1.254 36.25 0.811zm-24.17 11.277c0.132 8.277 15.557 8.897 14.501-3.223-4.48-6.33-14.59-2.628-14.5 3.223zm-0.8 45.912c1.954 2.883 5.012 4.66 9.67 4.834 9.31-6.78-9.39-16.956-9.67-4.83zm3.22 43.51c2.386 0.563 3.507 2.398 7.246 1.609 8.82-7.17-8.96-14.02-7.25-1.61zm-1.61 41.88c-1.605 6.328 8.987 10.591 10.469 4.028 1.86-3.49-7.07-8.18-10.47-4.03z" fill="#fff"/>
|
||||||
|
<path d="m79.135 46.631c-0.498 53.364 2.17 87.958 0 145-9.397 5.206-29.09 5.795-33.026-4.831-2.507-6.759 0.581-23.275 0-35.446-1.544-32.403-1.031-79.587 0-104.72 11.036 0.392 21.361-2.317 33.026 0.001zm-16.109 16.11c6.863 0.157 9.442-14.524-1.61-11.276-9.041 2.65-2.366 11.186 1.61 11.276zm-4.835 33.833c2.085 5.598 10.659 4.601 12.084-0.806-1.13-6.614-12.346-7.681-12.084 0.806zm2.419 44.306c2.052 5.418 12.679 5.273 12.081-2.419-1.672-4.27-13.566-5.24-12.081 2.42zm-0.807 41.08c0.63 0.18 0.938 0.675 0.807 1.609 3.825 1.621 10.266 1.205 9.665-4.025-1.3-4.2-11.707-3.19-10.472 2.42z" fill="#fff"/>
|
||||||
|
<path d="m84.774 45.824h33.033c2.163 52.314 0.655 99.574 0.8 156.28-10.213 7.318-26.131 3.386-34.633-3.223-0.64-55.093-2.825-98.969-0.807-150.64-0.042-1.375 0.594-2.083 1.604-2.416zm17.726 18.528c2.746-1.819 5.646-3.484 5.642-8.056-7.1-12.045-20.306 6.059-5.64 8.056zm-6.448 34.638c0.37 3.43 3.438 4.131 7.251 5.639 12.66-7.409-8.575-17.951-7.248-5.64zm0 43.5c-0.213 7.689 11.288 6.673 10.473-0.804-2.94-1.55-8.816-2.84-10.468 0.8zm11.278 35.44c-3.416-5.152-10.935-0.327-10.473 5.639 3.373 6.45 11.333 1.4 10.473-5.64z" fill="#fff"/>
|
||||||
|
<path d="m124.24 45.824h32.224c-0.719 58.876 1.137 122.88 0 170.77-7.316 7.932-27.779 5.448-33.026-2.419-2.062-63.779-1.142-105.76-0.807-165.94 0.71-0.641 0.49-2.198 1.62-2.406zm23.37 9.667c-4.346-14.15-25.717 10.056-3.225 8.861 1.54-3.286 4.64-4.267 3.23-8.861zm-5.64 52.359c3.265-0.377 4.375-3.326 5.64-7.247-8.81-13.9-19.54 8.86-5.64 7.25zm4.03 33.83c-19.86-7.07-2.97 17.17 0 0zm-7.25 46.73c0.501 3.74 8.584 4.749 8.865 0-0.49-4.87-8.4-4.87-8.87 0z" fill="#fff"/>
|
||||||
|
<path d="m2.609 46.631c1.874 38.672 0.276 80.815 0.806 120.83-10.667 5.552-28.472 3.113-32.221-6.445-3.144-8.01-1.007-24.596-1.611-37.058-1.188-24.418 1.75-49.179 0.805-75.72 9.499-1.778 20.874-1.68 32.221-1.609zm-21.749 12.081c1.691 6.565 12.855 6.094 12.888-1.611-3.587-4.558-11.344-5.055-12.888 1.611zm4.028 35.445c3.974 1.772 9.797 0.735 9.667-4.026-1.159-2.338-3.659-3.324-6.444-4.029-2.502 1.523-4.929 4.663-3.223 8.055zm-0.805 38.663c-0.052 7.938 11.568 8.322 10.472-0.807-2.517-2.42-8.855-2.09-10.472 0.81z" fill="#fff"/>
|
||||||
|
<path d="m9.054 46.631h33.026c0.259 44.472-0.654 95.961 0.807 132.11-11.499 8.339-28.48 2.091-34.638-6.444-2.907-34.43-1.098-80.282-1.611-123.25 0.449-1.164 1.256-1.97 2.416-2.419zm12.083 11.277c1.967 5.987 11.93 6.2 11.278-2.417-2.256-4.833-11.349-3.72-11.278 2.417zm6.444 40.277c2.191-2.104 4.654-3.941 4.833-8.055-8.391-7.5-15.288 5.938-4.833 8.055zm0.807 45.915c2.409-2.107 4.637-6.542 1.611-9.666-10.534-5.6-11.506 10.95-1.611 9.67z" fill="#fff"/>
|
||||||
|
<path d="m-95.667 47.435h26.583c1.345 29.017 1.332 63.621 0 92.64-6.858 3.597-21.211 5.159-26.583-0.806-3.153-25.049-1.35-61.105-1.612-89.416-0.033-1.378 0.598-2.086 1.612-2.419zm7.25 8.056c-0.517 5.887 2.802 7.938 8.861 7.25 1.044-2.446 3.222-3.759 2.416-8.056-3.709-2.202-8.13-1.375-11.277 0.806zm-0.805 29c0.052 4.778 4.323 5.344 9.666 4.833 0.426-2.842 2.363-3.601 0.804-6.444-2.969-2.829-8.971-1.547-10.47 1.611z" fill="#fff"/>
|
||||||
|
<path d="m-34.445 47.435c1.887 31.239 0.668 74.789 0.807 106.34-8.308 5.286-25.125 5.447-30.612-2.42-0.678-30.103-2.171-74.948 0.805-103.92 9.667-0.005 19.333-0.005 29-0.005zm-16.917 12.084c0.685 3.746 3.286 4.555 9.666 4.028 9.57-10.469-11.559-14.403-9.666-4.028zm-2.416 30.611c1.52 5.433 13.484 5.836 11.277-3.223-3.859-1.644-11.666-3.24-11.277 3.223zm0 39.47c-1.126 6.913 8.344 9.105 11.277 4.832-0.265-4.9-7.475-8.46-11.277-4.83z" fill="#fff"/>
|
||||||
|
<path d="m61.416 51.464c11.053-3.248 8.474 11.434 1.61 11.276-3.976-0.089-10.651-8.625-1.61-11.276z"/>
|
||||||
|
<path d="m144.38 64.352c-22.492 1.191-1.121-23.012 3.225-8.861 1.42 4.594-1.68 5.575-3.22 8.861z"/>
|
||||||
|
<path d="m187.88 54.685c1.056 12.12-14.369 11.5-14.501 3.223-0.09-5.851 10.02-9.553 14.5-3.223z"/>
|
||||||
|
<path d="m32.415 55.491c0.652 8.617-9.311 8.404-11.278 2.417-0.071-6.137 9.022-7.25 11.278-2.417z"/>
|
||||||
|
<path d="m108.14 56.296c0.005 4.571-2.896 6.236-5.646 8.056-14.656-1.997-1.45-20.101 5.65-8.056z"/>
|
||||||
|
<path d="m-77.14 54.685c0.806 4.297-1.375 5.605-2.416 8.056-6.059 0.688-9.378-1.363-8.861-7.25 3.147-2.181 7.568-3.008 11.277-0.806z"/>
|
||||||
|
<path d="m-41.696 63.547c-6.38 0.529-8.981-0.282-9.666-4.028-1.893-10.375 19.236-6.441 9.666 4.028z"/>
|
||||||
|
<path d="m-6.252 57.101c-0.032 7.705-11.197 8.177-12.888 1.611 1.544-6.666 9.301-6.169 12.888-1.611z"/>
|
||||||
|
<path d="m-78.751 82.879c1.559 2.844-0.375 3.603-0.804 6.444-5.343 0.511-9.614-0.055-9.666-4.833 1.498-3.157 7.5-4.439 10.47-1.611z"/>
|
||||||
|
<path d="m-42.5 86.908c2.207 9.059-9.758 8.655-11.277 3.223-0.39-6.464 7.417-4.868 11.277-3.223z"/>
|
||||||
|
<path d="m-11.89 86.101c2.785 0.705 5.285 1.691 6.444 4.029 0.131 4.762-5.697 5.799-9.667 4.026-1.705-3.391 0.722-6.531 3.223-8.055z"/>
|
||||||
|
<path d="m32.415 90.13c-0.18 4.113-2.642 5.95-4.833 8.055-10.456-2.117-3.559-15.555 4.833-8.055z"/>
|
||||||
|
<path d="m70.275 95.768c-1.425 5.406-9.999 6.409-12.084 0.806-0.262-8.487 10.954-7.42 12.084-0.806z"/>
|
||||||
|
<path d="m103.3 104.63c-3.813-1.508-6.881-2.209-7.251-5.639-1.324-12.312 19.911-1.77 7.251 5.639z"/>
|
||||||
|
<path d="m147.61 100.6c-1.265 3.921-2.376 6.87-5.644 7.247-13.9 1.61-3.17-21.15 5.64-7.25z"/>
|
||||||
|
<path d="m182.25 108.66c-4.658-0.18-7.716-1.951-9.67-4.834 0.28-12.136 18.98-1.96 9.67 4.83z"/>
|
||||||
|
<path d="m-42.5 134.43c-2.934 4.271-12.403 2.078-11.277-4.828 3.801-3.63 11.011-0.07 11.277 4.83z"/>
|
||||||
|
<path d="m-5.446 132.02c1.097 9.132-10.523 8.745-10.472 0.807 1.618-2.91 7.956-3.24 10.472-0.81z"/>
|
||||||
|
<path d="m29.999 134.43c3.026 3.124 0.798 7.559-1.611 9.666-9.895 1.28-8.923-15.27 1.611-9.67z"/>
|
||||||
|
<path d="m72.691 138.46c0.604 7.692-10.029 7.837-12.081 2.419-1.485-7.66 10.409-6.69 12.081-2.42z"/>
|
||||||
|
<path d="m106.52 141.68c0.815 7.483-10.686 8.493-10.473 0.804 1.657-3.63 7.533-2.34 10.473-0.8z"/>
|
||||||
|
<path d="m146 141.68c-2.97 17.17-19.86-7.07 0 0z"/>
|
||||||
|
<path d="m183.05 148.94c-3.739 0.783-4.86-1.046-7.246-1.609-1.71-12.41 16.07-5.56 7.25 1.61z"/>
|
||||||
|
<path d="m70.275 179.55c0.601 5.234-5.84 5.65-9.665 4.025 0.131-0.935-0.184-1.437-0.807-1.609-1.235-5.62 9.172-6.63 10.472-2.42z"/>
|
||||||
|
<path d="m147.61 188.41c-0.281 4.749-8.361 3.74-8.862 0 0.47-4.87 8.38-4.87 8.86 0z"/>
|
||||||
|
<path d="m184.66 193.24c-1.481 6.562-12.074 2.3-10.469-4.028 3.4-4.15 12.33 0.54 10.47 4.03z"/>
|
||||||
|
<path d="m-49.751-36.341c4.897 0.528 6.754 0.047 11.278 1.611h-5.638c-1.666-1.826-5.163 2.127-5.64-1.611z"/>
|
||||||
|
<path d="m203.19 19.242c-0.219 3.174 5.675 0.231 6.448 2.416 2.11 7.132-1.715 17.205 0.803 24.973-1.102 2.386-6.086 0.892-8.054 2.416-0.479 46.865 1.573 88.479 1.609 136.94 0.01 14.573-0.901 29.822-3.223 42.693-9.098 10.734-32.508 10.853-42.695 0.807-0.793-3.458-1.907-5.353-0.803-8.866-6.893 2.205-23.275 5.899-33.03 0.807-3.14-1.64-3.96-5.746-6.444-7.251-0.479-3.426 0.58-5.323 0.807-8.051-11.492 4.032-37.773 4.156-39.475-11.278-12.606 7.739-35.954 3.621-37.859-11.278-14.9 5.401-31.665-0.07-37.861-10.47-15.694 4.241-33.829-1.102-36.249-15.308-10.778 3.6-37.706 5.301-35.444-13.694-12.203 7.214-29.923 0.394-35.445-8.859 0.525-28.134 0.652-53.751 0.807-84.583-0.419-3.607-6.636-1.424-8.055-4.027-0.326-9.524 0.885-15.833 0-24.167 1.17-1.514 5.272-0.097 6.444-1.61 1.295-16.078 1.111-41.7 0-60.417 9.596-4.078 28.396-7.801 38.667-1.611 9.77-3.561 23.629-4.371 33.833 0.805 9.616-5.275 27.909-6.894 37.055 0 7.868-1.952 14.577-4.289 23.362-3.222 4.077 0.497 7.984 3.403 12.082 3.222 3.676-0.16 6.716-3.348 10.472-4.026 4.945-0.894 11.642-0.605 16.923 0 3.658 0.419 7.82 3.251 12.081 3.222 5.953-0.041 11.977-3.984 18.526-4.028 7.854-0.053 13.19 2.27 20.944 4.028 9.159-5.958 28.786-6.633 37.862 0 11.718-9.092 38.838-6.733 45.105 5.638 0.03 18.494-0.34 37.391 0.81 54.778zm-79.75-55.583c7.483 3.139 26.992 4.656 32.224-1.611-7.53-4.914-26.65-5.944-32.22 1.611zm40.28-1.611c3.418 2.755 9.999 2.354 16.11 2.416-0.312-3.715-8.012-0.046-8.051-4.027h12.888v4.027c3.711-0.042 7.874 0.357 10.47-0.805-4.78-6.197-25.68-8.506-31.42-1.611zm-235.22 1.611c-9.865 2.267-18.787-4.736-24.166-1.611 6.585 3.953 22.183 5.679 28.999 0.806-2.534-3.911-11.44-1.447-16.11-3.222-0.36 5.461 10.45-0.249 11.277 4.027zm43.499 0.804c7.029 2.539 25.539 3.125 31.416-0.805-6.747-5.04-26.005-5.93-31.416 0.805zm73.306 0.807c11.037 2.555 23.505 3.346 33.83 0-6.646-6.887-28.311-8.16-33.83 0zm39.469-0.807c7.051 5.748 25.636 2.895 32.227-1.609-6.71-4.687-27.519-5.239-32.226 1.609zm-76.525 0.807c7.27 5.793 26.911 3.584 33.027-1.611-9.368-3.347-26.064-5.974-33.027 1.611zm-70.083-0.807c5.7 2.689 23.743 4.797 29 0-6.277-3.504-23.281-5.52-29 0zm222.33 58.807c10.787 1.335 27.067 1.335 37.858 0-1.607-16.907 1.746-39.386-0.803-57.194-9.556-0.668-27.815 3.252-37.056-1.612-1.15 16.631-1.46 40.202 0 58.806zm-257.78 2.416c9.05-0.349 19.605 0.81 27.388-0.806-0.509-18.822 1.041-39.706-0.804-57.192-10.853 1.822-18.139-0.755-27.39-2.417-0.221 20.154-2.076 41.679 0.806 60.415zm31.417-0.807c9.766 0.974 21.087 0.393 30.61 1.613 1.869-17.467 0.279-38.391 0.806-57.194-8.495-0.163-24.418 1.029-31.416-3.223-2.277 20.259 0.25 39.207 0 58.804zm188.5-57.999c-0.784 15.927-1.855 38.76 0.807 57.194h31.417c1.77-22.794 0.868-36.623 1.606-57.999h-1.606c-9.77 3.022-21.11 1.977-32.23 0.805zm-39.475 0.807c-2.121 19.178-1.06 34.27-0.807 55.583 10.702 2.758 19.532 0.084 35.449 0.805-0.046-20.616 1.557-35.851 0-57.194-10.77 2.148-24.645 4.499-34.645 0.806zm-113.58 0c-0.812 16.786-0.275 38.76 0.805 57.999 10.146-0.593 20.385-1.098 31.416-0.806 0.913-15.194 1.622-40.41 0.805-57.192-11.126 0.66-21.403 2.036-33.026-0.001zm37.055 57.192h34.638c1.399-19.358 1.102-39.404 0-56.388-10.13 1.426-25.432 4.981-34.638 0-0.128 13.682-2.008 38.463 0 56.388zm39.471-55.581c-1.714 19.853-0.668 40.761 0 55.581h32.221c-1.265-18.326 2.438-43.177-1.606-56.388-10.267 1.279-20.002 1.686-30.615 0.807zm-41.888 58.805c-38.543-3.014-72.702 0.07-110.36-0.806 1.727 4.179-0.266 12.08 1.61 16.112 109.27-0.777 203.68-2.472 308.52-2.417v-15.306c-65.76 1.342-148.29-2.668-199.77 2.417zm157.08 17.721c-3.209 53.591-1.121 123.44 0 181.25 9.25 7.801 25.01 6.247 35.442 0.8 2.042-58.666 0.029-123.77 0.807-181.25-12.94-2.054-27.21-1.567-36.25-0.796zm-115.19 0.807c-1.031 25.132-1.544 72.316 0 104.72 0.581 12.171-2.507 28.688 0 35.446 3.937 10.626 23.629 10.037 33.026 4.831 2.17-57.039-0.498-91.63 0-145-11.665-2.317-21.99 0.392-33.026 0.001zm37.056 1.611c-2.022 51.668 0.166 95.544 0.803 150.64 8.509 6.608 24.427 10.541 34.64 3.223-0.15-56.703 1.356-103.96-0.807-156.28h-33.026c-1.01 0.337-1.646 1.045-1.609 2.422zm39.475 0c-0.335 60.183-1.256 102.16 0.807 165.94 5.247 7.867 25.71 10.351 33.026 2.419 1.137-47.898-0.719-111.9 0-170.77h-32.224c-1.15 0.202-0.93 1.759-1.62 2.412zm-152.25 0c0.947 26.539-1.99 51.299-0.805 75.72 0.604 12.462-1.533 29.048 1.611 37.058 3.749 9.559 21.555 11.997 32.221 6.445-0.53-40.01 1.064-82.154-0.806-120.83-11.347-0.07-22.722-0.168-32.221 1.612zm36.25 0.804c0.513 42.967-1.296 88.819 1.611 123.25 6.158 8.535 23.139 14.783 34.638 6.444-1.464-36.147-0.548-87.637-0.807-132.11h-33.026c-1.16 0.45-1.967 1.256-2.416 2.416zm-103.92 0.807c0.262 28.311-1.542 64.367 1.612 89.416 5.376 5.965 19.726 4.402 26.583 0.799 1.332-29.015 1.345-63.619 0-92.636h-26.583c-1.015 0.334-1.646 1.042-1.612 2.419zm33.833-2.418c-2.977 28.967-1.48 73.812-0.805 103.92 5.487 7.867 22.304 7.706 30.612 2.42-0.142-31.543 1.08-75.096-0.807-106.34-9.667-0.005-19.333-0.005-29-0.005z"/>
|
||||||
|
<path d="m96.859 183.57c-0.462-5.963 7.057-10.791 10.473-5.639 0.86 7.04-7.1 12.09-10.471 5.64z"/>
|
||||||
|
<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>pan pipes</dc:title><dc:date>2007-07-20T14:57:30</dc:date><dc:description>I cannot find what the proper name of this instrument must be - any idea post here. For now I am calling it pan pipes but I don't think they have finger holes. This is from a concert brochure for the 54th Regt. Band from Library of Congress</dc:description><dc:source>http://openclipart.org/detail/4518/pan-pipes-by-johnny_automatic</dc:source><dc:creator><cc:Agent><dc:title>johnny_automatic</dc:title></cc:Agent></dc:creator><dc:subject><rdf:Bag><rdf:li>clip art</rdf:li><rdf:li>clipart</rdf:li><rdf:li>externalsource</rdf:li><rdf:li>flute</rdf:li><rdf:li>image</rdf:li><rdf:li>instrument</rdf:li><rdf:li>loc</rdf:li><rdf:li>media</rdf:li><rdf:li>music</rdf:li><rdf:li>musical</rdf:li><rdf:li>pipes</rdf:li><rdf:li>png</rdf:li><rdf:li>public domain</rdf:li><rdf:li>svg</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: 21 KiB |
|
@ -13,14 +13,18 @@ a {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-top: 0px;
|
margin: 0 0 0 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 0px;
|
box-shadow: 0px 0px 7px rgb(14, 15, 17);
|
||||||
margin-bottom: 10px;
|
padding: 0px 10px 0px 10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-left: -8px;
|
||||||
|
margin-right: -8px;
|
||||||
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
|
|
55
src/api.ts
55
src/api.ts
|
@ -5,35 +5,44 @@ function init(conf: object){
|
||||||
config = conf;
|
config = conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function register(name: string, password: string, streamer: boolean) {
|
async function register(name: string, password: string, confirm: string) {
|
||||||
if(!config.registration){
|
if(!config.registration) return {"error":"registration disabled"};
|
||||||
return {"error":"registration disabled"};
|
if(name.includes(';') || name.includes(' ') || name.includes('\'')) return {"error":"illegal characters"};
|
||||||
}
|
if(password !== confirm) return {"error":"mismatched passwords"};
|
||||||
else {
|
for(let i=0;i<config.restrictedNames.length;i++){
|
||||||
if(name.includes(';') || name.includes(' ')) return {"error":"illegal characters"};
|
if (name === config.restrictedNames[i]) return {"error":"restricted name"};
|
||||||
let s: boolean;
|
|
||||||
if(streamer && config.streamKeys) s = true;
|
|
||||||
else s = false;
|
|
||||||
let r: boolean = await db.addUser(name, password, s, false);
|
|
||||||
if(r) return {"success":""};
|
|
||||||
else return {"error":""};
|
|
||||||
}
|
}
|
||||||
|
let r: boolean = await db.addUser(name, password);
|
||||||
|
if(r) return {"success":""};
|
||||||
|
return {"error":""};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(name: string, pass: string) {
|
async function update(name: string, password: string, title: string, bio: string, record: boolean){
|
||||||
return await db.validatePassword(name, pass);
|
if(!name || !password) return {"error":"Insufficient parameters"};
|
||||||
|
let auth: boolean = await db.validatePassword(name, password);
|
||||||
|
if(!auth) return {"error":"Username or Password Incorrect"};
|
||||||
|
await db.query('UPDATE user_meta set title='+db.raw.escape(title)+', about='+db.raw.escape(bio)+' where username='+db.raw.escape(name));
|
||||||
|
if(!record) await db.query('UPDATE users set record_flag=false where username='+db.raw.escape(name));
|
||||||
|
else await db.query('UPDATE users set record_flag=true where username='+db.raw.escape(name));
|
||||||
|
return {"success":""};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function users(num: number) {
|
async function changepwd(name: string, password: string, newpwd: string){
|
||||||
return await db.query('select username from users limit '+num);
|
if(!name || !password) return {"error":"Insufficient parameters"};
|
||||||
|
let auth: boolean = await db.validatePassword(name, password);
|
||||||
|
if(!auth) return {"error":"Username or Password Incorrect"};
|
||||||
|
let newhash: string = await db.hash(newpwd);
|
||||||
|
await db.query('UPDATE users set password_hash='+db.raw.escape(newhash)+'where username='+db.raw.escape(name)+' limit 1');
|
||||||
|
return {"success":""};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function user(name: string) {
|
async function changesk(name: string, password: string){
|
||||||
|
if(!name || !password) return {"error":"Insufficient parameters"};
|
||||||
|
let auth: boolean = await db.validatePassword(name, password);
|
||||||
|
if(!auth) return {"error":"Username or Password Incorrect"};
|
||||||
|
let key: string = await db.genKey();
|
||||||
|
await db.query('UPDATE users set stream_key='+db.raw.escape(key)+'where username='+db.raw.escape(name)+' limit 1');
|
||||||
|
return {"success":key};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function instance() {
|
export { init, register, update, changepwd, changesk };
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export { init, register };
|
|
20
src/cli.ts
20
src/cli.ts
|
@ -6,8 +6,6 @@ db.init(config.database, config.bcrypt);
|
||||||
|
|
||||||
flags.defineString('add', '', 'User to add');
|
flags.defineString('add', '', 'User to add');
|
||||||
flags.defineString('remove', '', 'User to remove');
|
flags.defineString('remove', '', 'User to remove');
|
||||||
flags.defineString('mkstreamer', '', 'Give a stream key to a user');
|
|
||||||
flags.defineString('rmstreamer', '', 'Remove a stream key from a user');
|
|
||||||
flags.defineString('password', '', 'password to hash');
|
flags.defineString('password', '', 'password to hash');
|
||||||
flags.defineBoolean('admin');
|
flags.defineBoolean('admin');
|
||||||
flags.defineBoolean('streamer');
|
flags.defineBoolean('streamer');
|
||||||
|
@ -15,7 +13,7 @@ flags.defineBoolean('streamer');
|
||||||
flags.parse();
|
flags.parse();
|
||||||
|
|
||||||
if(flags.get('add') !== ''){
|
if(flags.get('add') !== ''){
|
||||||
db.addUser(flags.get('add'), flags.get('password'), flags.get('streamer'), flags.get('admin')).then((result) => {
|
db.addUser(flags.get('add'), flags.get('password')).then((result) => {
|
||||||
if(result) console.log("User added successfully.");
|
if(result) console.log("User added successfully.");
|
||||||
else console.log("Could not add user. Is the password field empty?");
|
else console.log("Could not add user. Is the password field empty?");
|
||||||
process.exit();
|
process.exit();
|
||||||
|
@ -28,20 +26,4 @@ if(flags.get('remove') !== ''){
|
||||||
else console.log("Could not remove user.");
|
else console.log("Could not remove user.");
|
||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if(flags.get('mkstreamer') !== ''){
|
|
||||||
db.addStreamKey(flags.get('mkstreamer')).then((result) => {
|
|
||||||
if(result) console.log("Key added successfully.");
|
|
||||||
else console.log("Could not add key.");
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(flags.get('rmstreamer') !== ''){
|
|
||||||
db.rmStreamKey(flags.get('rmstreamer')).then((result) => {
|
|
||||||
if(result) console.log("Key removed successfully.");
|
|
||||||
else console.log("Could not remove key.");
|
|
||||||
process.exit();
|
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -11,13 +11,14 @@ function run(): void{
|
||||||
const satyr: object = {
|
const satyr: object = {
|
||||||
privateEndpoint: config.media.privateEndpoint,
|
privateEndpoint: config.media.privateEndpoint,
|
||||||
record: config.media.record,
|
record: config.media.record,
|
||||||
streamKeys: config.media.streamKeys,
|
|
||||||
registration: config.satyr.registration,
|
registration: config.satyr.registration,
|
||||||
webFormat: config.satyr.webFormat,
|
webFormat: config.satyr.webFormat,
|
||||||
restrictedNames: config.satyr.restrictedNames,
|
restrictedNames: config.satyr.restrictedNames,
|
||||||
name: config.satyr.name,
|
name: config.satyr.name,
|
||||||
domain: config.satyr.domain,
|
domain: config.satyr.domain,
|
||||||
email: config.satyr.email
|
email: config.satyr.email,
|
||||||
|
rootredirect: config.satyr.rootredirect,
|
||||||
|
version: process.env.npm_package_version
|
||||||
};
|
};
|
||||||
const nms: object = {
|
const nms: object = {
|
||||||
logType: config.server.logs,
|
logType: config.server.logs,
|
||||||
|
|
|
@ -10,24 +10,23 @@ function init (db: object, bcrypt: object){
|
||||||
cryptoconfig = bcrypt;
|
cryptoconfig = bcrypt;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addUser(name: string, password: string, streamer: boolean, admin: boolean){
|
async function addUser(name: string, password: string){
|
||||||
//does not respect registration setting in config
|
//does not respect registration setting in config
|
||||||
//nor stream keys
|
|
||||||
if(password === '') return false;
|
if(password === '') return false;
|
||||||
let key: string = ' ';
|
let key: string = await genKey();
|
||||||
if (streamer) key = await genKey();
|
|
||||||
let hash: string = await bcrypt.hash(password, cryptoconfig.saltRounds);
|
let hash: string = await bcrypt.hash(password, cryptoconfig.saltRounds);
|
||||||
let dupe = await query('select * from users where username=\''+name+'\'');
|
let dupe = await query('select * from users where username='+raw.escape(name));
|
||||||
if(dupe[0]) return false;
|
if(dupe[0]) return false;
|
||||||
let q: string = 'INSERT INTO users (username, password_hash, stream_key, record_flag, is_mod) VALUES (\''+name+'\', \''+hash+'\', \''+key+'\', 0, '+admin+')';
|
await query('INSERT INTO users (username, password_hash, stream_key, record_flag) VALUES ('+raw.escape(name)+', '+raw.escape(hash)+', '+raw.escape(key)+', 0');
|
||||||
await query(q);
|
await query('INSERT INTO user_meta (username, title, about, live) VALUES ('+raw.escape(name)+',\'\',\'\',false)');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rmUser(name: string){
|
async function rmUser(name: string){
|
||||||
let exist = await query('select * from users where username=\''+name+'\'');
|
let exist = await query('select * from users where username='+raw.escape(name));
|
||||||
if(!exist[0]) return false;
|
if(!exist[0]) return false;
|
||||||
await query('delete from users where username=\''+name+'\' limit 1');
|
await query('delete from users where username='+raw.escape(name)+' limit 1');
|
||||||
|
await query('delete from user_meta where username='+raw.escape(name)+' limit 1');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,21 +37,6 @@ async function genKey(){
|
||||||
else return key;
|
else return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addStreamKey(name: string){
|
|
||||||
let exist = await query('select * from users where username=\''+name+'\'');
|
|
||||||
if(!exist[0]) return false;
|
|
||||||
let key = await genKey();
|
|
||||||
await query('update users set stream_key=\''+key+'\' where username=\''+name+'\' limit 1');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function rmStreamKey(name: string){
|
|
||||||
let exist = await query('select * from users where username=\''+name+'\'');
|
|
||||||
if(!exist[0]) return false;
|
|
||||||
await query('update users set stream_key=\'\' where username=\''+name+'\' limit 1');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function query(query: string){
|
async function query(query: string){
|
||||||
return new Promise(resolve => raw.query(query, (error, results, fields) => {
|
return new Promise(resolve => raw.query(query, (error, results, fields) => {
|
||||||
if(error) throw error;
|
if(error) throw error;
|
||||||
|
@ -61,8 +45,12 @@ async function query(query: string){
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validatePassword(username: string, password: string){
|
async function validatePassword(username: string, password: string){
|
||||||
let pass: any= await query('select password from users where username=\''+username+'\' limit 1');
|
let pass: any = await query('select password_hash from users where username='+raw.escape(username)+' limit 1');
|
||||||
return await bcrypt.compare(password, pass[0].password_hash);
|
return await bcrypt.compare(password, pass[0].password_hash.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
export { query, raw, init, addUser, rmUser, addStreamKey, rmStreamKey, validatePassword };
|
async function hash(pwd){
|
||||||
|
return await bcrypt.hash(pwd, cryptoconfig.saltRounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { query, raw, init, addUser, rmUser, validatePassword, hash, genKey };
|
92
src/http.ts
92
src/http.ts
|
@ -1,6 +1,7 @@
|
||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import * as njk from "nunjucks";
|
import * as njk from "nunjucks";
|
||||||
import * as bodyparser from "body-parser";
|
import * as bodyparser from "body-parser";
|
||||||
|
import * as fs from "fs";
|
||||||
import * as api from "./api";
|
import * as api from "./api";
|
||||||
import * as db from "./database";
|
import * as db from "./database";
|
||||||
|
|
||||||
|
@ -18,30 +19,95 @@ function init(satyr: any){
|
||||||
sitename: satyr.name,
|
sitename: satyr.name,
|
||||||
domain: satyr.domain,
|
domain: satyr.domain,
|
||||||
email: satyr.email,
|
email: satyr.email,
|
||||||
user: '',
|
rootredirect: satyr.rootredirect,
|
||||||
streamtitle: '',
|
version: satyr.version
|
||||||
};
|
};
|
||||||
app.use(bodyparser.json());
|
app.use(bodyparser.json());
|
||||||
app.use(bodyparser.urlencoded({ extended: true }));
|
app.use(bodyparser.urlencoded({ extended: true }));
|
||||||
|
//site handlers
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.render('index.njk', njkconf);
|
res.redirect(njkconf.rootredirect);
|
||||||
});
|
});
|
||||||
app.get('/about', (req, res) => {
|
app.get('/about', (req, res) => {
|
||||||
res.render('about.njk', njkconf);
|
res.render('about.njk', njkconf);
|
||||||
});
|
});
|
||||||
app.get('/users/*', (req, res) => {
|
app.get('/users', (req, res) => {
|
||||||
njkconf.user = req.url.split('/')[2].toLowerCase();
|
db.query('select username from users').then((result) => {
|
||||||
res.render('user.njk', njkconf);
|
njkconf.list = result;
|
||||||
});
|
res.render('list.njk', njkconf);
|
||||||
app.get('/registration', (req, res) => {
|
njkconf.list = '';
|
||||||
res.render('registration.njk', njkconf);
|
|
||||||
});
|
|
||||||
app.post('/api/register', (req, res) => {
|
|
||||||
api.register(req.body.username, req.body.password, req.body.streamer).then( (result) => {
|
|
||||||
res.send({"error":""});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
app.get('/users/live', (req, res) => {
|
||||||
|
db.query('select username,title from user_meta where live=1;').then((result) => {
|
||||||
|
njkconf.list = result;
|
||||||
|
res.render('live.njk', njkconf);
|
||||||
|
njkconf.list = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get('/users/*', (req, res) => {
|
||||||
|
njkconf.user = req.url.split('/')[2].toLowerCase();
|
||||||
|
db.query('select title,about from user_meta where username='+db.raw.escape(njkconf.user)).then((result) => {
|
||||||
|
if(result[0]){
|
||||||
|
njkconf.streamtitle = result[0].title;
|
||||||
|
njkconf.about = result[0].about;
|
||||||
|
res.render('user.njk', njkconf);
|
||||||
|
}
|
||||||
|
else res.render('404.njk', njkconf);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get('/vods/*', (req, res) => {
|
||||||
|
njkconf.user = req.url.split('/')[2].toLowerCase();
|
||||||
|
db.query('select username from user_meta where username='+db.raw.escape(njkconf.user)).then((result) => {
|
||||||
|
if(result[0]){
|
||||||
|
fs.readdir('./site/live/'+njkconf.user, {withFileTypes: true} , (err, files) => {
|
||||||
|
if(files) njkconf.list = files.filter(fn => fn.name.endsWith('.mp4'));
|
||||||
|
else njkconf.list = [];
|
||||||
|
res.render('vods.njk', njkconf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else res.render('404.njk', njkconf);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get('/register', (req, res) => {
|
||||||
|
res.render('registration.njk', njkconf);
|
||||||
|
});
|
||||||
|
app.get('/profile', (req, res) => {
|
||||||
|
res.render('profile.njk', njkconf);
|
||||||
|
});
|
||||||
|
app.get('/changepwd', (req, res) => {
|
||||||
|
res.render('changepwd.njk', njkconf);
|
||||||
|
});
|
||||||
|
app.get('/changesk', (req, res) => {
|
||||||
|
res.render('changesk.njk', njkconf);
|
||||||
|
});
|
||||||
|
//api handlers
|
||||||
|
app.post('/api/register', (req, res) => {
|
||||||
|
api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => {
|
||||||
|
res.send(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.post('/api/user', (req, res) => {
|
||||||
|
api.update(req.body.username, req.body.password, req.body.title, req.body.bio, req.body.record).then((result) => {
|
||||||
|
res.send(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.post('/api/user/password', (req, res) => {
|
||||||
|
api.changepwd(req.body.username, req.body.password, req.body.newpassword).then((result) => {
|
||||||
|
res.send(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.post('/api/user/streamkey', (req, res) => {
|
||||||
|
api.changesk(req.body.username, req.body.password).then((result) => {
|
||||||
|
res.send(result);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
//static files if nothing else matches first
|
||||||
app.use(express.static('site'));
|
app.use(express.static('site'));
|
||||||
|
//404 Handler
|
||||||
|
app.use(function (req, res, next) {
|
||||||
|
res.status(404).render('404.njk', njkconf);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { init };
|
export { init };
|
|
@ -8,7 +8,7 @@ function init (mediaconfig: any, satyrconfig: any) {
|
||||||
nms.run();
|
nms.run();
|
||||||
|
|
||||||
nms.on('postPublish', (id, StreamPath, args) => {
|
nms.on('postPublish', (id, StreamPath, args) => {
|
||||||
console.log("[NodeMediaServer] Prepublish Hook for stream:",id);
|
console.log("[NodeMediaServer] Publish Hook for stream:",id);
|
||||||
let session = nms.getSession(id);
|
let session = nms.getSession(id);
|
||||||
let app: string = StreamPath.split("/")[1];
|
let app: string = StreamPath.split("/")[1];
|
||||||
let key: string = StreamPath.split("/")[2];
|
let key: string = StreamPath.split("/")[2];
|
||||||
|
@ -31,6 +31,8 @@ function init (mediaconfig: any, satyrconfig: any) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.log("[NodeMediaServer] Public endpoint, checking record flag.");
|
console.log("[NodeMediaServer] Public endpoint, checking record flag.");
|
||||||
|
//set live flag
|
||||||
|
db.query('update user_meta set live=true where username=\''+key+'\' limit 1');
|
||||||
//if this stream is from the public endpoint, check if we should be recording
|
//if this stream is from the public endpoint, check if we should be recording
|
||||||
return db.query('select username,record_flag from users where username=\''+key+'\' limit 1').then((results) => {
|
return db.query('select username,record_flag from users where username=\''+key+'\' limit 1').then((results) => {
|
||||||
if(results[0].record_flag && satyrconfig.record){
|
if(results[0].record_flag && satyrconfig.record){
|
||||||
|
@ -61,7 +63,11 @@ function init (mediaconfig: any, satyrconfig: any) {
|
||||||
//if the url is formatted correctly and the user is streaming to the correct private endpoint
|
//if the url is formatted correctly and the user is streaming to the correct private endpoint
|
||||||
//grab the username from the database and redirect the stream there if the key is valid
|
//grab the username from the database and redirect the stream there if the key is valid
|
||||||
//otherwise kill the session
|
//otherwise kill the session
|
||||||
db.query('select username from users where stream_key=\''+key+'\' limit 1').then((results) => {
|
if(key.includes(' ')) {
|
||||||
|
session.reject();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then((results) => {
|
||||||
if(results[0]){
|
if(results[0]){
|
||||||
exec('ffmpeg -analyzeduration 0 -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key+' -vcodec copy -acodec copy -crf 18 -f flv rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username);
|
exec('ffmpeg -analyzeduration 0 -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key+' -vcodec copy -acodec copy -crf 18 -f flv rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username);
|
||||||
console.log('[NodeMediaServer] Stream key okay for stream:',id);
|
console.log('[NodeMediaServer] Stream key okay for stream:',id);
|
||||||
|
@ -72,6 +78,13 @@ function init (mediaconfig: any, satyrconfig: any) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
nms.on('donePublish', (id, StreamPath, args) => {
|
||||||
|
let app: string = StreamPath.split("/")[1];
|
||||||
|
let key: string = StreamPath.split("/")[2];
|
||||||
|
if(app === mediaconfig.trans.tasks[0].app) {
|
||||||
|
db.query('update user_meta set live=false where username=\''+key+'\' limit 1');
|
||||||
|
}
|
||||||
|
});
|
||||||
nms.on('prePlay', (id, StreamPath, args) => {
|
nms.on('prePlay', (id, StreamPath, args) => {
|
||||||
let session = nms.getSession(id);
|
let session = nms.getSession(id);
|
||||||
let app: string = StreamPath.split("/")[1];
|
let app: string = StreamPath.split("/")[1];
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% extends "base.njk" %}
|
||||||
|
{% block content %}
|
||||||
|
<p></p>
|
||||||
|
Couldn't find that page!
|
||||||
|
{% endblock %}
|
|
@ -1 +1 @@
|
||||||
<p>Add a description of your instance here!</p>
|
<p>Add a description of your instance here!</br>You can find this file in templates/about.html</p>
|
|
@ -1,12 +1,14 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="/styles.css">
|
<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">
|
||||||
<title>{{ sitename }}</title>
|
<title>{{ sitename }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<span style="float:left;"><h4><a href="/">{{ sitename }}</a> | <a href="/users/live">Live</a> <a href="/users">Users</a> <a href="/about">About</a></h4></span><span style="float:right;"><h4>| <a href="/login">Login</a></h4></span>
|
<span style="float:left;"><h4><a href="/">{{ sitename }}</a> | <a href="/users">Users</a> <a href="/users/live">Live</a> <a href="/about">About</a></h4></span><span style="float:right;"><h4>| <a href="/profile">Profile</a></h4></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -17,10 +19,10 @@
|
||||||
<div>
|
<div>
|
||||||
<b>Satyr</b></br>
|
<b>Satyr</b></br>
|
||||||
<a href="https://gitlab.com/knotteye/satyr">About</a></br>
|
<a href="https://gitlab.com/knotteye/satyr">About</a></br>
|
||||||
<a href="">v0.1.0</a>
|
<a href="https://gitlab.com/knotteye/satyr/-/releases">v{{ version }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img src="/satyr.png" height="50" />
|
<img src="/logo.svg" height="50" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>{{ sitename }}</b></br>
|
<b>{{ sitename }}</b></br>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "base.njk" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>Change your password on {{ sitename }}</h3><span style="font-size: small;">Not registered yet? Sign up <a href="/register">here</a>.</br> Update your <a href="/profile">profile</a> or <a href="/changesk">stream key</a>.</span>
|
||||||
|
<p></p>
|
||||||
|
<form action="/api/user/password" method="POST" target="responseFrame">
|
||||||
|
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
|
||||||
|
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
|
||||||
|
New Password: </br><input type="password" name="newpassword" style="min-width: 300px"/></br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "base.njk" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>Change your password on {{ sitename }}</h3><span style="font-size: small;">Not registered yet? Sign up <a href="/register">here</a>.</br> Update your <a href="/profile">profile</a> or <a href="/changepwd">password</a>.</span>
|
||||||
|
<p></p>
|
||||||
|
<form action="/api/user/streamkey" method="POST" target="responseFrame">
|
||||||
|
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
|
||||||
|
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
|
||||||
|
{% endblock %}
|
|
@ -1,4 +0,0 @@
|
||||||
{% extends "base.njk" %}
|
|
||||||
{% block content %}
|
|
||||||
What to put on index?
|
|
||||||
{% endblock %}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.njk" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>Streaming on {{ sitename }}</h3>
|
||||||
|
{% asyncEach user in list%}
|
||||||
|
<a href="/users/{{user.username}}">{{ user.username }}</a></br></br>
|
||||||
|
{% else %}
|
||||||
|
No users found!
|
||||||
|
{% endeach %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.njk" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>Currently live on {{ sitename }}</h3>
|
||||||
|
{% for user in list%}
|
||||||
|
<a href="/users/{{user.username}}">{{ user.username | capitalize}} | {{user.title}}</a></br></br>
|
||||||
|
{% else %}
|
||||||
|
No one is live!
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "base.njk" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>Update your profile on {{ sitename }}</h3><span style="font-size: small;">Not registered yet? Sign up <a href="/register">here</a>.</br> Change your <a href="/changepwd">password</a> or <a href="/changesk">stream key</a>.</span>
|
||||||
|
<p></p>
|
||||||
|
<form action="/api/user" method="POST" target="responseFrame">
|
||||||
|
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
|
||||||
|
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
|
||||||
|
Stream Title: </br><input type="text" name="title" style="min-width: 300px"/></br>
|
||||||
|
Bio: </br><input type="text" name="bio" style="min-width: 300px; min-height: 150px;"/></br>
|
||||||
|
Record VODs: <input type="checkbox" name="record" value="true"></br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
|
||||||
|
{% endblock %}
|
|
@ -2,12 +2,13 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="jscontainer" style="height: 100%;">
|
<div id="jscontainer" style="height: 100%;">
|
||||||
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;">
|
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;">
|
||||||
<form action="/api/register" method="POST" target="_self">
|
<form action="/api/register" method="POST" target="responseFrame">
|
||||||
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
|
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
|
||||||
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
|
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
|
||||||
Request Stream Key: <input type="checkbox" name="streamer" value="true" style="min-heigh: 50px;"> </br>
|
Confirm: </br><input type="password" name="confirm" style="min-width: 300px"/></br>
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
|
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
|
||||||
</div>
|
</div>
|
||||||
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;">
|
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;">
|
||||||
{% include "tos.html" %}
|
{% include "tos.html" %}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
This is example terms of service!</br>
|
This is an example TOS!</br>
|
||||||
You should change it by editing templates/tos.html
|
You should change it by editing templates/tos.html
|
|
@ -1,6 +1,7 @@
|
||||||
{% extends "base.njk" %}
|
{% extends "base.njk" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<span style="float: left;font-size: large;"><b>{{ user | capitalize }}'s Stream</b></span><span style="float: right;font-size: large;">Direct Links: <a href="rtmp://{{ domain }}/live/{{ user }}">RTMP</a> <a href="/live/{{ user }}/index.m3u8">HLS</a></span>
|
</br>
|
||||||
|
<span style="float: left;font-size: large;"><b>{{ user }} | {{ streamtitle | escape }}</b></span><span style="float: right;font-size: large;"> Links | <a href="/vods/{{ user }}">VODs</a></span>
|
||||||
<div id="jscontainer">
|
<div id="jscontainer">
|
||||||
<div id="jschild" style="width: 70%;height: 100%;">
|
<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%;"></video>
|
<video controls poster="/thumbnail.jpg" class="video-js vjs-default-skin" id="live-video" style="width:100%;height:100%;"></video>
|
||||||
|
@ -28,4 +29,6 @@
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
</script></br>
|
</script></br>
|
||||||
|
<noscript>The webclients for the stream and the chat require javascript, but feel free to use the direct links above!</br></br></noscript>
|
||||||
|
{{ about | escape }}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.njk" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>{{ user }}'s VODs</h3>
|
||||||
|
{% asyncEach vid in list%}
|
||||||
|
<a href="/live/{{ user }}/{{ vid.name }}">{{ vid.name }}</a></br></br>
|
||||||
|
{% else %}
|
||||||
|
No recordings found!
|
||||||
|
{% endeach %}
|
||||||
|
{% endblock %}
|
Reference in New Issue