Merge branch 'twitch-mirror' into 'develop'

Twitch mirror

See merge request knotteye/satyr!25
merge-requests/26/merge
knotteye 4 years ago
commit 444c3c8f7e
  1. 4
      docs/REST.md
  2. 10
      install/config.example.yml
  3. 15
      src/api.ts
  4. 6
      src/config.ts
  5. 3
      src/database.ts
  6. 9
      src/db/1.ts
  7. 10
      src/http.ts
  8. 9
      src/server.ts
  9. 4
      templates/profile.njk

@ -124,9 +124,9 @@ Update the current user's information
**Authentication**: yes
**Parameters**: title, bio, rec
**Parameters**: title, bio, rec, twitch, twitch_key
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), 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: ""}`

@ -56,4 +56,12 @@ chat:
enabled: false
username:
#https://twitchapps.com/tmi/
password:
password:
twitch_mirror:
# enable to allow users to mirror video streams to twitch
# for those with truly no bandwidth limits
enabled: false
# https://stream.twitch.tv/ingests/
# do not include {stream_key}
ingest: 'rtmp://live-ord02.twitch.tv/app/

@ -18,7 +18,7 @@ async function register(name: string, password: string, confirm: string): Promis
}
async function update(fields: object): Promise<object>{
if(!fields['title'] && !fields['bio'] && (fields['rec'] !== 'true' && fields['rec'] !== 'false')) return {"error":"no valid fields specified"};
if(!fields['title'] && !fields['bio'] && (fields['rec'] !== 'true' && fields['rec'] !== 'false') && (fields['twitch'] !== 'true' && fields['twitch'] !== 'false') && !fields['twitch_key']) return {"error":"no valid fields specified"};
let qs: string = "";
let f: boolean = false;
if(fields['title']) {qs += ' user_meta.title='+db.raw.escape(fields['title']);f = true;}
@ -30,8 +30,19 @@ async function update(fields: object): Promise<object>{
if(typeof(fields['rec']) === 'boolean' || typeof(fields['rec']) === 'number') {
if(f) qs+=',';
qs += ' users.record_flag='+db.raw.escape(fields['rec']);
f=true;
}
if(typeof(fields['twitch']) === 'boolean' || typeof(fields['twitch']) === 'number') {
if(f) qs+=',';
qs += ' twitch_mirror.enabled='+db.raw.escape(fields['twitch']);
f=true;
}
if(fields['twitch_key']){
if(f) qs+=',';
qs += ' twitch_mirror.twitch_key='+db.raw.escape(fields['twitch_key']);
f = true;
}
await db.query('UPDATE users,user_meta SET'+qs+' WHERE users.username='+db.raw.escape(fields['name'])+' AND user_meta.username='+db.raw.escape(fields['name']));
await db.query('UPDATE users,user_meta,twitch_mirror SET'+qs+' WHERE users.username='+db.raw.escape(fields['name'])+' AND user_meta.username='+db.raw.escape(fields['name'])+' AND twitch_mirror.username='+db.raw.escape(fields['name']));
return {success:""};
}

@ -81,6 +81,10 @@ const config: Object = {
username: null,
token: null
}, localconfig['chat']['twitch'])
}
},
twitch_mirror: Object.assign({
enabled: false,
ingest: null
}, localconfig['twitch_mirror'])
};
export { config };

@ -22,6 +22,7 @@ async function addUser(name: string, password: string){
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('INSERT INTO user_meta (username, title, about, live) VALUES ('+raw.escape(name)+',\'\',\'\',false)');
await query('INSERT INTO chat_integration (username, irc, xmpp, twitch, discord) VALUES ('+raw.escape(name)+',\'\',\'\',\'\',\'\')');
await query('INSERT INTO twitch_mirror (username) VALUES ('+raw.escape(name)+')');
return true;
}
@ -30,6 +31,8 @@ async function rmUser(name: string){
if(!exist[0]) return false;
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');
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');
return true;
}

@ -0,0 +1,9 @@
import * as db from "../database";
async function run () {
await db.query('CREATE TABLE IF NOT EXISTS twitch_mirror(username VARCHAR(25), enabled TINYINT DEFAULT 0, twitch_key VARCHAR(50) DEFAULT \"\")');
await db.query('INSERT INTO twitch_mirror(username) SELECT username FROM users');
await db.query('INSERT INTO db_meta (version) VALUES (1)');
}
export { run }

@ -238,10 +238,14 @@ async function initAPI() {
if(t) {
if(req.body.record === "true") req.body.record = true;
else if(req.body.record === "false") req.body.record = false;
if(req.body.twitch === "true") req.body.twitch = true;
else if(req.body.twitch === "false") req.body.twitch = false;
return api.update({name: t['username'],
title: "title" in req.body ? req.body.title : false,
bio: "bio" in req.body ? req.body.bio : false,
rec: "record" in req.body ? req.body.record : "NA"
rec: "record" in req.body ? req.body.record : "NA",
twitch: "twitch" in req.body ? req.body.twitch: "NA",
twitch_key: "twitch_key" in req.body ? req.body.twitch_key : false
}).then((r) => {
res.json(r);
return;
@ -492,7 +496,9 @@ async function initSite(openReg) {
if(tryDecode(req.cookies.Authorization)) {
db.query('select * from user_meta where username='+db.raw.escape(JWT.decode(req.cookies.Authorization)['username'])).then((result) => {
db.query('select record_flag from users where username='+db.raw.escape(JWT.decode(req.cookies.Authorization)['username'])).then((r2) => {
res.render('profile.njk', Object.assign({rflag: r2[0]}, {meta: result[0]}, {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
db.query('select enabled from twitch_mirror where username='+db.raw.escape(JWT.decode(req.cookies.Authorization)['username'])).then((r3) => {
res.render('profile.njk', Object.assign({twitch: r3[0]}, {rflag: r2[0]}, {meta: result[0]}, {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
});
});
});
//res.render('profile.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));

@ -68,6 +68,15 @@ function init () {
console.log('[NodeMediaServer] Skipping recording for stream:',id);
}
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)
execFile(config['media']['ffmpeg'], ['-loglevel', 'fatal', '-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+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);
}
else{

@ -5,7 +5,9 @@
<form action="/api/user/update" method="POST" target="responseFrame" id="profile">
Stream Title: </br><textarea form="profile" name="title" style="min-width: 320px;resize: none;font-size: large;text-align: center;" value="{{meta.title}}">{{meta.title}}</textarea></br>
Bio: </br><textarea form="profile" name="bio" style="min-width: 320px; min-height: 150px;resize: none;font-size: inherit;" value="{{meta.about}}">{{meta.about}}</textarea></br>
Record VODs: <input type="radio" name="record" value="true" {% if rflag.record_flag %}checked{% endif %}> Yes<input type="radio" name="record" value="false" {% if rflag.record_flag %}{% else %}checked{% endif %}/> No</br></br>
ReStream to Twitch: <input type="radio" name="twitch" value="true" {% if twitch.enabled %}checked{% endif %}> Yes<input type="radio" name="twitch" value="false" {% if twitch.enabled %}{% else %}checked{% endif %}/> No</br>
Record VODs: <input type="radio" name="record" value="true" {% if rflag.record_flag %}checked{% endif %}> Yes<input type="radio" name="record" value="false" {% if rflag.record_flag %}{% else %}checked{% endif %}/> No</br>
Twitch Key: <textarea form="profile" name="twitch_key" style="max-height: 18px;min-width: 238px;resize: none;font-size: large;text-align: center;"></textarea></br></br>
<input type="submit" value="Update Profile">
</form></br>
<form action="/api/user/streamkey" method="POST" target="responseFrame">