From 5fe4728c1103f7ba51a07536a751cd9793962e41 Mon Sep 17 00:00:00 2001
From: knotteye <knotteye@airmail.cc>
Date: Sat, 10 Oct 2020 16:35:05 -0500
Subject: [PATCH 1/7] Add migration script and update remove and adduser
 functions. Needs a UI, API, and functionality.

---
 src/database.ts | 3 +++
 src/db/1.ts     | 9 +++++++++
 2 files changed, 12 insertions(+)
 create mode 100644 src/db/1.ts

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

From 4ff4a6329dfc924a4b9974957233d5e5139b8345 Mon Sep 17 00:00:00 2001
From: knotteye <knotteye@airmail.cc>
Date: Mon, 12 Oct 2020 10:54:55 -0500
Subject: [PATCH 2/7] Add configuration options for twitch mirror

---
 install/config.example.yml | 6 +++++-
 src/config.ts              | 5 ++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/install/config.example.yml b/install/config.example.yml
index da27427..fe66753 100644
--- a/install/config.example.yml
+++ b/install/config.example.yml
@@ -56,4 +56,8 @@ 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
+  enabled: false
\ No newline at end of file
diff --git a/src/config.ts b/src/config.ts
index 3f74000..d118a25 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -81,6 +81,9 @@ const config: Object = {
 			username: null,
 			token: null
 		}, localconfig['chat']['twitch'])
-	}
+	},
+	twitch_mirror: Object.assign({
+		enabled: false
+	}, localconfig['twitch_mirror'])
 };
 export { config };
\ No newline at end of file

From 44cc3213ca780bf274818b69c4b1999edbe8d165 Mon Sep 17 00:00:00 2001
From: knotteye <knotteye@airmail.cc>
Date: Mon, 12 Oct 2020 11:14:59 -0500
Subject: [PATCH 3/7] Tweak config changes, add functionality in server.ts
 Still needs an API and a UI, then good to go.

---
 install/config.example.yml | 6 +++++-
 src/config.ts              | 3 ++-
 src/server.ts              | 9 +++++++++
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/install/config.example.yml b/install/config.example.yml
index fe66753..c67d16b 100644
--- a/install/config.example.yml
+++ b/install/config.example.yml
@@ -60,4 +60,8 @@ chat:
 
 twitch_mirror:
 # enable to allow users to mirror video streams to twitch
-  enabled: false
\ No newline at end of file
+# 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/config.ts b/src/config.ts
index d118a25..4bd6ec0 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -83,7 +83,8 @@ const config: Object = {
 		}, localconfig['chat']['twitch'])
 	},
 	twitch_mirror: Object.assign({
-		enabled: false
+		enabled: false,
+		ingest: null
 	}, localconfig['twitch_mirror'])
 };
 export { config };
\ No newline at end of file
diff --git a/src/server.ts b/src/server.ts
index dd58ee5..fcd409d 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;
+					else
+					execFile(config['media']['ffmpeg'], ['-loglevel', 'fatal', '-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+config['media']['privateEndpoint']+'/'+key, '-vcodec', 'libx264', '-acodec', 'libaac', '-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{

From 98927bd7b84ddba895f341670e09a661f28bbfb2 Mon Sep 17 00:00:00 2001
From: knotteye <knotteye@airmail.cc>
Date: Mon, 12 Oct 2020 12:11:04 -0500
Subject: [PATCH 4/7] Add API functionality for twitch mirror.

---
 src/api.ts  | 15 +++++++++++++--
 src/http.ts |  6 +++++-
 2 files changed, 18 insertions(+), 3 deletions(-)

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<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;
 	}
-	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']));
+	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,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/http.ts b/src/http.ts
index 7474e5d..a179b2d 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;

From d4bb2ceebec8bd39c7ee3da14a40c4d98b4427c3 Mon Sep 17 00:00:00 2001
From: knotteye <knotteye@airmail.cc>
Date: Mon, 12 Oct 2020 12:12:27 -0500
Subject: [PATCH 5/7] Update documentation for API. All that's left for twitch
 mirroring is a UI and then testing.

---
 docs/REST.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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: ""}`
 

From 93738d27bc15a3daa32cc88fd217eeb3eb383d5c Mon Sep 17 00:00:00 2001
From: knotteye <knotteye@airmail.cc>
Date: Mon, 12 Oct 2020 13:34:24 -0500
Subject: [PATCH 6/7] Add sections in profile.njk for adjusting settings.
 Everything tested and working apart from the actual streaming functionality.

---
 src/http.ts           | 4 +++-
 templates/profile.njk | 4 +++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/http.ts b/src/http.ts
index a179b2d..b5f9c53 100644
--- a/src/http.ts
+++ b/src/http.ts
@@ -496,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/templates/profile.njk b/templates/profile.njk
index 465a8da..33ed1dd 100644
--- a/templates/profile.njk
+++ b/templates/profile.njk
@@ -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">

From 7b84253fc145ae9743cf0a782bf4bcbdac921cd5 Mon Sep 17 00:00:00 2001
From: knotteye <knotteye@airmail.cc>
Date: Mon, 12 Oct 2020 20:53:22 -0500
Subject: [PATCH 7/7] Add some logging for twitch mirror

---
 src/server.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/server.ts b/src/server.ts
index fcd409d..dd60538 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -70,8 +70,8 @@ function init () {
 				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;
-					else
-					execFile(config['media']['ffmpeg'], ['-loglevel', 'fatal', '-i', 'rtmp://127.0.0.1:'+config['rtmp']['port']+'/'+config['media']['privateEndpoint']+'/'+key, '-vcodec', 'libx264', '-acodec', 'libaac', '-f', 'flv', config['twitch_mirror']['ingest']+tm[0]['twitch_key']], {
+					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