Merge branch 'develop' into 'master'

Develop

Closes #8

See merge request knotteye/satyr!15
merge-requests/16/merge
knotteye 2020-07-30 07:57:54 +00:00
commit 3a06bae0c4
4 changed files with 214 additions and 84 deletions

View File

@ -1,34 +1,34 @@
## Using Satyr's Rest API # Using Satyr's Rest API
### /api/instance/info ## /api/instance/info
Generic enformation about the instance. Generic enformation about the instance.
Method: GET **Method**: GET
Authentication: no **Authentication**: no
Parameters: none **Parameters**: none
Response: Returns a JSON object containing the name, domain, version, email, and whether registration is open. Email will be null if not specified. **Response**: Returns a JSON object containing the name, domain, version, email, and whether registration is open. Email will be null if not specified.
Example: `{name: "Example Instance", domain: "example.com", registration: false, version: 0.7, email: null}` **Example**: `{name: "Example Instance", domain: "example.com", registration: false, version: 0.7, email: null}`
### /api/instance/config ## /api/instance/config
Configuration of the instance relating to media Configuration of the instance relating to media
Method: GET **Method**: GET
Authentication: no **Authentication**: no
Parameters: none **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, public and private play endpoints, and whether adaptive livestreaming and VOD recording are enabled.
Example: **Example**:
``` ```
{ {
rtmp: { rtmp: {
@ -43,160 +43,189 @@ Example:
} }
} }
``` ```
Public and private endpoints work like this, from the above example: Public and private endpoints work like this, from the above example:
stream to: rtmp://example.com/stream stream to: rtmp://example.com/stream
play from: rtmp://example.com/live/username or https://example.com/live/username/index.mpd play from: rtmp://example.com/live/username or https://example.com/live/username/index.mpd
### /api/users/live/ ## /api/users/live/
Returns the usernames and stream titles of 10 users who are currently streaming Returns the usernames and stream titles of 10 users who are currently streaming
Method: GET **Method**: POST
Authentication: no **Authentication**: no
Parameters: none **Parameters**: num (optional), sort (optional), page (optional)
Response: Returns an array of objects containing the username and title of each stream. Returns an empty array if no one is streaming. number is the number of results (maximum is 50)
Example: `[{username:"foo", title:"bar"}]` sort is the optional way to sort results. current options are "alphabet" and "alphabet-r" (reversed)
page is the page number (i.e. skip the first num * page results)
**Response**: Returns an array of 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.
**Example**: `[{username:"foo", title:"bar"}]`
### /api/users/live/:num ## /api/users/all
Same as above, with number indicated the number of users to list. Maximum of 50. Same as above, but returns all users regardless of whether they are streaming. Also unfinished.
### /api/register ## /api/register
Register a new user. Register a new user.
Method: POST **Method**: POST
Authentication: no **Authentication**: no
Parameters: Username, password, confirm **Parameters**: Username, password, confirm
Response: If successful, returns a json object with the users stream key. Otherwise returns `{error: "error reason"}` **Response**: If successful, returns a json object with the users stream key. Otherwise returns `{error: "error reason"}`
Examples: **Examples**:
`{stream_key: "asdfghjkltyuiop12345"}` `{stream_key: "asdfghjkltyuiop12345"}`
`{error: "registration disabled"}` `{error: "registration disabled"}`
### /api/login ## /api/login
Obtain a signed json web token for authentication Obtain a signed json web token for authentication
Method: POST **Method**: POST
Authentication: no **Authentication**: no
Parameters: Username and password OR a valid JWT cookie expiring in less than 24 hours **Parameters**: Username and password OR a valid JWT cookie expiring in less than 24 hours
Response: If succesful, will return `{success: ""}` or `{success: "already verified"}` if the JWT provided is too early to be renewed. If unsuccesful, will return `{error: "invalid password"}` or `{error: "Username or Password Incorrect"}` depending on the authentication method. Note that if a JWT is available, the parameters will be ignored. **Response**: If succesful, will return `{success: ""}` or `{success: "already verified"}` if the JWT provided is too early to be renewed. If unsuccesful, will return `{error: "invalid password"}` or `{error: "Username or Password Incorrect"}` depending on the authentication method. Note that if a JWT is available, the parameters will be ignored.
Notes: I've already listed nearly every response. My final note is that the JWT is set as the cookie 'Authorization', not returned in the response. **Notes**: I've already listed nearly every response. My final note is that the JWT is set as the cookie 'Authorization', not returned in the response.
### /api/user/update ## /api/user/update
Update the current user's information Update the current user's information
Method: POST **Method**: POST
Authentication: yes **Authentication**: yes
**Parameters**: title, bio, rec
Parameters: title, bio, rec
Rec is a boolean (whether to record VODs), others are strings. Parameters that are not included in the request will not be updated. Rec is a boolean (whether to record VODs), others are strings. Parameters that are not included in the request will not be updated.
Response: Returns `{error: "error code"}` or `{success: ""}` **Response**: Returns `{error: "error code"}` or `{success: ""}`
### /api/user/update/chat ## /api/user/update/chat
Update the chatrooms on other platforms to integrate with the user's stream chat Update the chatrooms on other platforms to integrate with the user's stream chat
Method: POST **Method**: POST
Authentication: yes **Authentication**: yes
Parameters: discord, xmpp, twitch irc **Parameters**: discord, xmpp, twitch irc
All strings corresponding to a channel name to mirror to. XMPP is currently unused. Parameters not included in the request will not be updated.
Response: Returns `{error: "error code"}` or `{success: ""}` Strings corresponding to a channel name to mirror to. XMPP is currently unused. Parameters not included in the request will not be updated.
**Response**: Returns `{error: "error code"}` or `{success: ""}`
### /api/user/vods/delete ## /api/user/vods/delete
Delete the specified vods of the current user Delete the specified vods of the current user
Method: POST **Method:** POST
Authentication: yes **Authentication**: yes
Paramters: A string array of the names of vods to be deleted. **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 code"}` or `{success: ""}`
### /api/user/password ## /api/user/password
Change the current user's password Change the current user's password
Method: POST **Method**: POST
Authentication: yes **Authentication**: yes
Parameters: The user's current password, the new password, AND a valid JWT cookie. **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 code"}` or `{success: ""}`
### /api/user/streamkey ## /api/user/streamkey
Change the current user's stream key. This will not affect the stream if the user is currently live. Change the current user's stream key. This will not affect the stream if the user is currently live.
Method: POST **Method**: POST
Authentication: yes **Authentication**: yes
Parameters: A valid JWT cookie. No other parameters. **Parameters**: A valid JWT cookie. No other parameters.
Response: Returns `{error: "error code"}` or `{success: "new_stream_key"}` **Response**: Returns `{error: "error code"}` or `{success: "new_stream_key"}`
### /api/:user/vods/list
## /api/:user/vods
Get a list of the named users VODs Get a list of the named users VODs
Method: GET **Method**: GET
Authentication: no **Authentication**: no
Parameters: none **Parameters**: user
Response: Returns an array of VODs with the format `[{"name":"yote.mp4"},{"name":"yeet.mp4"}]` **Response**: Returns an array of VODs with the format `[{"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.com/publicEndpoint/username/filename.mp4
## /api/:user/config
## Not Yet Implemented Get information about the specified user.
#### /api/:user/info **Method**: GET
List bio, stream title. If authenticated, list all settings. **Authentication**: optional
#### /api/user/streamkey/current **Parameters**: user
Return current stream key **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.
#### /api/users/live **Example**: `{username: "foo", title: "bar", about: "This is an example bio"}`
Paging and sorting coming Soon(tm)
## /api/user/streamkey/current
Returns the current stream key for the authenticated user.
**Method**: GET
**Authentication**: yes
**Parameters**: none
**Response**: returns a JSON object with the stream key
**Example**: `{"stream_key": "abcdefghijklmno12345"}` or `{"error":"error reason"}`

View File

@ -1,4 +1,4 @@
import * as db from "./database" import * as db from "./database";
import { config } from "./config"; import { config } from "./config";
import {unlink} from "fs"; import {unlink} from "fs";
@ -69,4 +69,21 @@ async function deleteVODs(vodlist: Array<string>, username: string): Promise<obj
return {"success":""}; return {"success":""};
} }
export { register, update, changepwd, changesk, login, updateChat, deleteVODs }; async function getConfig(username: string, all?: boolean): Promise<object>{
let t = {username: username};
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));
if(usermeta[0]) Object.assign(t, users[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]);
}
else {
let um = await db.query('SELECT title,about FROM user_meta WHERE username='+db.raw.escape(username));
if(um[0]) Object.assign(t, um[0]);
}
return t;
}
export { register, update, changepwd, changesk, login, updateChat, deleteVODs, getConfig };

View File

@ -1,6 +1,11 @@
import {parseAsYaml as parse} from "parse-yaml"; import {parseAsYaml as parse} from "parse-yaml";
import {readFileSync as read} from "fs"; import {readFileSync as read} from "fs";
var localconfig: Object = parse(read('config/config.yml')); try {
var localconfig: Object = parse(read('config/config.yml'));
} catch (e) {
console.log('No config file found. Exiting.');
process.exit();
}
const config: Object = { const config: Object = {
crypto: Object.assign({ crypto: Object.assign({
saltRounds: 12 saltRounds: 12
@ -10,7 +15,7 @@ const config: Object = {
domain: '', domain: '',
registration: false, registration: false,
email: null, email: null,
restrictedNames: [ 'live' ], restrictedNames: [ 'live', 'user', 'users', 'register', 'login' ],
rootredirect: '/users/live', rootredirect: '/users/live',
version: process.env.npm_package_version, version: process.env.npm_package_version,
}, localconfig['satyr']), }, localconfig['satyr']),

View File

@ -167,15 +167,64 @@ async function initAPI() {
}) })
); );
}); });
app.get('/api/users/live', (req, res) => { app.post('/api/users/live', (req, res) => {
db.query('select username,title from user_meta where live=1 limit 10;').then((result) => { let qs = 'SELECT username,title FROM user_meta WHERE live=1';
res.send(result);
if(req.body.sort) {
switch (req.body.sort) {
case "alphabet":
qs += ' ORDER BY username ASC';
break;
case "alphabet-r":
qs += ' ORDER BY username DESC';
break;
default:
break;
}
}
if(!req.body.num || req.body.num > 50 || req.body.num === NaN || req.body.num === Infinity || typeof(req.body.num) !== 'number'){
req.body.num = 10;
}
qs += ' LIMIT '+req.body.num;
if(req.body.page && typeof(req.body.page) === 'number' && req.body.page !== NaN && req.body.page !== Infinity){
qs += ' OFFSET '+Math.floor(req.body.num * req.body.page);
}
db.query(qs+';').then((result) => {
if(result) res.send(result);
else res.send('{"error":""}');
}); });
}); });
app.get('/api/users/live/:num', (req, res) => { app.post('/api/users/all', (req, res) => {
if(req.params.num > 50) req.params.num = 50; let qs = 'SELECT username,title FROM user_meta';
db.query('select username,title from user_meta where live=1 limit '+req.params.num+';').then((result) => {
res.send(result); if(req.body.sort) {
switch (req.body.sort) {
case "alphabet":
qs += ' ORDER BY username ASC';
break;
case "alphabet-r":
qs += ' ORDER BY username DESC';
break;
default:
break;
}
}
if(!req.body.num || req.body.num > 50 || req.body.num === NaN || req.body.num === Infinity || typeof(req.body.num) !== 'number'){
req.body.num = 10;
}
qs += ' LIMIT '+req.body.num;
if(req.body.page && typeof(req.body.page) === 'number' && req.body.page !== NaN && req.body.page !== Infinity){
qs += ' OFFSET '+Math.floor(req.body.num * req.body.page);
}
db.query(qs+';').then((result) => {
if(result) res.send(result);
else res.send('{"error":""}');
}); });
}); });
app.post('/api/register', (req, res) => { app.post('/api/register', (req, res) => {
@ -266,6 +315,19 @@ async function initAPI() {
} }
}); });
}); });
app.get('/api/user/streamkey/current', (req, res) => {
validToken(req.cookies.Authorization).then((t) => {
if(t) {
db.query('SELECT stream_key FROM users WHERE username='+db.raw.escape(t['username'])).then(o => {
if(o[0]) res.send(o[0]);
else res.send('{"error":""}');
});
}
else {
res.send('{"error":"invalid token"}');
}
});
});
app.post('/api/user/streamkey', (req, res) => { app.post('/api/user/streamkey', (req, res) => {
validToken(req.cookies.Authorization).then((t) => { validToken(req.cookies.Authorization).then((t) => {
if(t) { if(t) {
@ -312,7 +374,7 @@ async function initAPI() {
}); });
} }
}); });
app.get('/api/:user/vods/list', (req, res) => { app.get('/api/:user/vods', (req, res) => {
readdir('./site/live/'+req.params.user, {withFileTypes: true} , (err, files) => { readdir('./site/live/'+req.params.user, {withFileTypes: true} , (err, files) => {
if(err) { if(err) {
res.send([]); res.send([]);
@ -322,6 +384,23 @@ async function initAPI() {
res.send(list); res.send(list);
}); });
}); });
app.get('/api/:user/config', (req, res) => {
if(req.cookies.Authorization) validToken(req.cookies.Authorization).then(r => {
if(r && r['username'] === req.params.user) {
api.getConfig(req.params.user, true).then(re => {
res.send(JSON.stringify(re));
});
return;
}
else api.getConfig(req.params.user).then(re => {
res.send(JSON.stringify(re));
});
return;
});
else api.getConfig(req.params.user).then(r => {
res.send(JSON.stringify(r));
});
});
} }
async function initSite(openReg) { async function initSite(openReg) {