diff --git a/docs/REST.md b/docs/REST.md index 74786c9..3160cf7 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -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: ""}` diff --git a/install/config.example.yml b/install/config.example.yml index da27427..c67d16b 100644 --- a/install/config.example.yml +++ b/install/config.example.yml @@ -56,4 +56,12 @@ chat: enabled: false username: #https://twitchapps.com/tmi/ - password: \ No newline at end of file + 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/ \ No newline at end of file diff --git a/src/api.ts b/src/api.ts index bcc79ae..a803d63 100644 --- a/src/api.ts +++ b/src/api.ts @@ -18,7 +18,7 @@ async function register(name: string, password: string, confirm: string): Promis } async function update(fields: object): Promise{ - 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{ 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:""}; } diff --git a/src/config.ts b/src/config.ts index 3f74000..4bd6ec0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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 }; \ No newline at end of file diff --git a/src/database.ts b/src/database.ts index 2cbc440..1ee8a17 100644 --- a/src/database.ts +++ b/src/database.ts @@ -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; } diff --git a/src/db/1.ts b/src/db/1.ts new file mode 100644 index 0000000..55b8d89 --- /dev/null +++ b/src/db/1.ts @@ -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 } \ No newline at end of file diff --git a/src/http.ts b/src/http.ts index 7474e5d..b5f9c53 100644 --- a/src/http.ts +++ b/src/http.ts @@ -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)); diff --git a/src/server.ts b/src/server.ts index dd58ee5..dd60538 100644 --- a/src/server.ts +++ b/src/server.ts @@ -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{ diff --git a/templates/profile.njk b/templates/profile.njk index 465a8da..33ed1dd 100644 --- a/templates/profile.njk +++ b/templates/profile.njk @@ -5,7 +5,9 @@
Stream Title:

Bio:

- Record VODs: Yes No

+ ReStream to Twitch: Yes No
+ Record VODs: Yes No
+ Twitch Key: