Merge branch 'twitch-mirror' into 'develop'
Twitch mirror See merge request knotteye/satyr!25merge-requests/26/merge
commit
444c3c8f7e
|
@ -124,9 +124,9 @@ Update the current user's information
|
||||||
|
|
||||||
**Authentication**: yes
|
**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: ""}`
|
**Response**: Returns `{error: "error code"}` or `{success: ""}`
|
||||||
|
|
||||||
|
|
|
@ -56,4 +56,12 @@ chat:
|
||||||
enabled: false
|
enabled: false
|
||||||
username:
|
username:
|
||||||
#https://twitchapps.com/tmi/
|
#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/
|
15
src/api.ts
15
src/api.ts
|
@ -18,7 +18,7 @@ async function register(name: string, password: string, confirm: string): Promis
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update(fields: object): Promise<object>{
|
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 qs: string = "";
|
||||||
let f: boolean = false;
|
let f: boolean = false;
|
||||||
if(fields['title']) {qs += ' user_meta.title='+db.raw.escape(fields['title']);f = true;}
|
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(typeof(fields['rec']) === 'boolean' || typeof(fields['rec']) === 'number') {
|
||||||
if(f) qs+=',';
|
if(f) qs+=',';
|
||||||
qs += ' users.record_flag='+db.raw.escape(fields['rec']);
|
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:""};
|
return {success:""};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,10 @@ const config: Object = {
|
||||||
username: null,
|
username: null,
|
||||||
token: null
|
token: null
|
||||||
}, localconfig['chat']['twitch'])
|
}, localconfig['chat']['twitch'])
|
||||||
}
|
},
|
||||||
|
twitch_mirror: Object.assign({
|
||||||
|
enabled: false,
|
||||||
|
ingest: null
|
||||||
|
}, localconfig['twitch_mirror'])
|
||||||
};
|
};
|
||||||
export { config };
|
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 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 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 chat_integration (username, irc, xmpp, twitch, discord) VALUES ('+raw.escape(name)+',\'\',\'\',\'\',\'\')');
|
||||||
|
await query('INSERT INTO twitch_mirror (username) VALUES ('+raw.escape(name)+')');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ async function rmUser(name: string){
|
||||||
if(!exist[0]) return false;
|
if(!exist[0]) return false;
|
||||||
await query('delete from users where username='+raw.escape(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');
|
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;
|
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 }
|
10
src/http.ts
10
src/http.ts
|
@ -238,10 +238,14 @@ async function initAPI() {
|
||||||
if(t) {
|
if(t) {
|
||||||
if(req.body.record === "true") req.body.record = true;
|
if(req.body.record === "true") req.body.record = true;
|
||||||
else if(req.body.record === "false") req.body.record = false;
|
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'],
|
return api.update({name: t['username'],
|
||||||
title: "title" in req.body ? req.body.title : false,
|
title: "title" in req.body ? req.body.title : false,
|
||||||
bio: "bio" in req.body ? req.body.bio : 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) => {
|
}).then((r) => {
|
||||||
res.json(r);
|
res.json(r);
|
||||||
return;
|
return;
|
||||||
|
@ -492,7 +496,9 @@ async function initSite(openReg) {
|
||||||
if(tryDecode(req.cookies.Authorization)) {
|
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 * 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) => {
|
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));
|
//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);
|
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('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);
|
console.log('[NodeMediaServer] Stream key ok for stream:',id);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
<form action="/api/user/update" method="POST" target="responseFrame" id="profile">
|
<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>
|
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>
|
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">
|
<input type="submit" value="Update Profile">
|
||||||
</form></br>
|
</form></br>
|
||||||
<form action="/api/user/streamkey" method="POST" target="responseFrame">
|
<form action="/api/user/streamkey" method="POST" target="responseFrame">
|
||||||
|
|
Reference in New Issue