1
0
Fork 0
mirror of https://gitlab.com/Shinobi-Systems/ShinobiCE.git synced 2025-03-09 15:40:15 +00:00

Blue Turtle - The Refactoring

This commit is contained in:
Moe 2018-10-24 21:42:47 -07:00
parent 04011678fb
commit b7d08eb500
67 changed files with 11651 additions and 8452 deletions

191
libs/auth.js Normal file
View file

@ -0,0 +1,191 @@
var fs = require('fs');
module.exports = function(s,config,lang){
//Authenticator functions
s.api = {}
s.superUsersApi = {}
s.factorAuth = {}
s.failedLoginAttempts = {}
//auth handler
//params = parameters
//cb = callback
//res = response, only needed for express (http server)
//request = request, only needed for express (http server)
s.auth = function(params,cb,res,req){
if(req){
//express (http server) use of auth function
params.ip=req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
var failed=function(){
if(!req.ret){req.ret={ok:false}}
req.ret.msg=lang['Not Authorized'];
res.end(s.s(req.ret));
}
}else{
//socket.io use of auth function
var failed = function(){
//maybe log
}
}
var clearAfterTime=function(){
//remove temp key from memory
clearTimeout(s.api[params.auth].timeout)
s.api[params.auth].timeout=setTimeout(function(){
delete(s.api[params.auth])
},1000*60*5)
}
//check IP address of connecting user
var finish=function(user){
if(s.api[params.auth].ip.indexOf('0.0.0.0')>-1||s.api[params.auth].ip.indexOf(params.ip)>-1){
cb(user);
}else{
failed();
}
}
//check if auth key is user's temporary session key
if(s.group[params.ke]&&s.group[params.ke].users&&s.group[params.ke].users[params.auth]){
s.group[params.ke].users[params.auth].permissions={};
cb(s.group[params.ke].users[params.auth]);
}else{
//check if key is already in memory to save query time
if(s.api[params.auth]&&s.api[params.auth].details){
finish(s.api[params.auth]);
if(s.api[params.auth].timeout){
clearAfterTime()
}
}else{
//no key in memory, query db to see if key exists
//check if using username and password in plain text or md5
if(params.username&&params.username!==''&&params.password&&params.password!==''){
s.sqlQuery('SELECT * FROM Users WHERE mail=? AND (pass=? OR pass=?)',[params.username,params.password,s.createHash(params.password)],function(err,r){
if(r&&r[0]){
r=r[0];
r.ip='0.0.0.0';
r.auth = s.gid(20);
params.auth = r.auth;
r.details=JSON.parse(r.details);
r.permissions = {};
s.api[r.auth]=r;
clearAfterTime();
finish(r);
}else{
failed();
}
})
}else{
//not using plain login
s.sqlQuery('SELECT * FROM API WHERE code=? AND ke=?',[params.auth,params.ke],function(err,r){
if(r&&r[0]){
r=r[0];
s.api[params.auth]={ip:r.ip,uid:r.uid,ke:r.ke,permissions:JSON.parse(r.details),details:{}};
s.sqlQuery('SELECT mail,details FROM Users WHERE uid=? AND ke=?',[r.uid,r.ke],function(err,rr){
if(rr&&rr[0]){
rr=rr[0];
try{
s.api[params.auth].mail=rr.mail
s.api[params.auth].details=JSON.parse(rr.details)
s.api[params.auth].lang=s.getLanguageFile(s.api[params.auth].details.lang)
}catch(er){}
}
finish(s.api[params.auth]);
})
}else{
s.sqlQuery('SELECT * FROM Users WHERE auth=? AND ke=?',[params.auth,params.ke],function(err,r){
if(r&&r[0]){
r=r[0];
r.ip='0.0.0.0'
s.api[params.auth]=r
s.api[params.auth].details=JSON.parse(r.details)
s.api[params.auth].permissions={}
clearAfterTime()
finish(r)
}else{
failed();
}
})
}
})
}
}
}
}
//super user authentication handler
s.superAuth = function(params,callback,res,req){
var userFound = false
var userSelected = false
var adminUsersSelected = null
try{
var success = function(){
if(req && res){
res.setHeader('Content-Type', 'application/json');
var ip = req.headers['cf-connecting-ip']||req.headers["CF-Connecting-IP"]||req.headers["'x-forwarded-for"]||req.connection.remoteAddress;
var resp = {
ok: userFound,
ip: ip
}
if(userFound === false){
resp.msg = lang['Not Authorized']
res.end(s.prettyPrint(resp))
}
if(userSelected){
resp.$user = userSelected
}
if(adminUsersSelected){
resp.users = adminUsersSelected
}
}
callback({
ip : ip,
$user:userSelected,
users:adminUsersSelected,
config:config,
lang:lang
})
}
var foundUser = function(){
if(params.users === true){
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r) {
adminUsersSelected = r
success()
})
}else{
success()
}
}
if(params.auth && s.superUsersApi[params.auth]){
userFound = true
userSelected = s.superUsersApi[params.auth].$user
foundUser()
}else{
var superUserList = JSON.parse(fs.readFileSync(s.location.super))
superUserList.forEach(function(superUser,n){
if(
userFound === false &&
(
params.auth && superUser.tokens && superUser.tokens[params.auth] || //using API key (object)
params.auth && superUser.tokens && superUser.tokens.indexOf && superUser.tokens.indexOf(params.auth) > -1 || //using API key (array)
(
params.mail && params.mail.toLowerCase() === superUser.mail.toLowerCase() && //email matches
(
params.pass === superUser.pass || //user give it already hashed
superUser.pass === s.createHash(params.pass) || //hash and check it
superUser.pass.toLowerCase() === s.md5(params.pass).toLowerCase() //check if still using md5
)
)
)
){
userFound = true
userSelected = superUser
foundUser()
}
})
}
}catch(err){
console.log('The following error may mean your super.json is not formatted correctly.')
console.log(err)
}
if(userFound === true){
return true
}else{
return false
}
}
}

227
libs/basic.js Normal file
View file

@ -0,0 +1,227 @@
var moment = require('moment');
var crypto = require('crypto');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var events = require('events');
var http = require('http');
var https = require('https');
module.exports = function(s,config){
//kill any ffmpeg running
s.ffmpegKill=function(){
var cmd=''
if(s.isWin===true){
cmd = "Taskkill /IM ffmpeg.exe /F"
}else{
cmd = "ps aux | grep -ie ffmpeg | awk '{print $2}' | xargs kill -9"
}
exec(cmd,{detached: true})
};
process.on('exit',s.ffmpegKill.bind(null,{cleanup:true}));
process.on('SIGINT',s.ffmpegKill.bind(null, {exit:true}));
s.checkRelativePath = function(x){
if(x.charAt(0)!=='/'){
x=s.mainDirectory+'/'+x
}
return x
}
s.checkDetails = function(e){
if(!e.id && e.mid){e.id = e.mid}
if(e.details&&(e.details instanceof Object)===false){
try{e.details=JSON.parse(e.details)}catch(err){}
}
}
s.parseJSON = function(string){
try{
string = JSON.parse(string)
}catch(err){
}
return string
}
s.stringJSON = function(json){
try{
if(json instanceof Object){
json = JSON.stringify(json)
}
}catch(err){
}
return json
}
s.addUserPassToUrl = function(url,user,pass){
var splitted = url.split('://')
splitted[1] = user + ':' + pass + '@' + splitted[1]
return splitted.join('://')
}
s.checkCorrectPathEnding = function(x){
var length=x.length
if(x.charAt(length-1)!=='/'){
x=x+'/'
}
return x.replace('__DIR__',s.mainDirectory)
}
s.md5 = function(x){return crypto.createHash('md5').update(x).digest("hex")}
s.createHash = s.md5
switch(config.passwordType){
case'sha512':
if(config.passwordSalt){
s.createHash = function(x){return crypto.pbkdf2Sync(x, config.passwordSalt, 100000, 64, 'sha512').toString('hex')}
}
break;
case'sha256':
s.createHash = function(x){return crypto.createHash('sha256').update(x).digest("hex")}
break;
}
//load camera controller vars
s.nameToTime=function(x){x=x.split('.')[0].split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
s.ratio=function(width,height,ratio){ratio = width / height;return ( Math.abs( ratio - 4 / 3 ) < Math.abs( ratio - 16 / 9 ) ) ? '4:3' : '16:9';}
s.randomNumber=function(x){
if(!x){x=10};
return Math.floor((Math.random() * x) + 1);
};
s.gid=function(x){
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.nid=function(x){
if(!x){x=6};var t = "";var p = "0123456789";
for( var i=0; i < x; i++ )
t += p.charAt(Math.floor(Math.random() * p.length));
return t;
};
s.formattedTime_withOffset=function(e,x){
if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
e=s.timeObject(e);if(config.utcOffset){e=e.utcOffset(config.utcOffset)}
return e.format(x);
}
s.formattedTime=function(e,x){
if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
return s.timeObject(e).format(x);
}
s.utcToLocal = function(time){
return moment.utc(time).utcOffset(s.utcOffset).format()
}
s.localTimeObject = function(e,x){
return moment(e)
}
if(config.useUTC === true){
s.timeObject = function(time){
return moment(time).utc()
}
}else{
s.timeObject = moment
}
s.ipRange=function(start_ip, end_ip) {
var start_long = s.toLong(start_ip);
var end_long = s.toLong(end_ip);
if (start_long > end_long) {
var tmp=start_long;
start_long=end_long
end_long=tmp;
}
var range_array = [];
var i;
for (i=start_long; i<=end_long;i++) {
range_array.push(s.fromLong(i));
}
return range_array;
}
s.portRange=function(lowEnd,highEnd){
var list = [];
for (var i = lowEnd; i <= highEnd; i++) {
list.push(i);
}
return list;
}
//toLong taken from NPM package 'ip'
s.toLong=function(ip) {
var ipl = 0;
ip.split('.').forEach(function(octet) {
ipl <<= 8;
ipl += parseInt(octet);
});
return(ipl >>> 0);
}
//fromLong taken from NPM package 'ip'
s.fromLong=function(ipl) {
return ((ipl >>> 24) + '.' +
(ipl >> 16 & 255) + '.' +
(ipl >> 8 & 255) + '.' +
(ipl & 255) );
}
s.getFunctionParamNames = function(func) {
var fnStr = func.toString().replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '');
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g);
if(result === null)
result = [];
return result;
}
s.getRequest = function(url,callback){
return http.get(url, function(res){
var body = '';
res.on('data', function(chunk){
body += chunk;
});
res.on('end',function(){
try{body = JSON.parse(body)}catch(err){}
callback(body)
});
}).on('error', function(e){
// s.systemLog("Get Snapshot Error", e);
});
}
//system log
s.systemLog = function(q,w,e){
if(!w){w=''}
if(!e){e=''}
if(config.systemLog===true){
if(typeof q==='string'&&s.databaseEngine){
s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',['$','$SYSTEM',s.s({type:q,msg:w})]);
s.tx({f:'log',log:{time:s.timeObject(),ke:'$',mid:'$SYSTEM',time:s.timeObject(),info:s.s({type:q,msg:w})}},'$');
}
return console.log(s.timeObject().format(),q,w,e)
}
}
//system log
s.debugLog = function(q,w,e){
if(config.debugLog === true){
if(!w){w = ''}
if(!e){e = ''}
console.log(s.timeObject().format(),q,w,e)
if(config.debugLogVerbose === true){
console.log(new Error())
}
}
}
s.getOriginalUrl = function(req){
var url
if(config.baseURL || config.baseURL === ''){
url = config.baseURL
}else{
url = req.protocol + '://' + req.get('host') + '/'
}
return url
}
s.file=function(x,e){
if(!e){e={}};
switch(x){
case'size':
return fs.statSync(e.filename)["size"];
break;
case'delete':
if(!e){return false;}
return exec('rm -f '+e,{detached: true});
break;
case'deleteFolder':
if(!e){return false;}
return exec('rm -rf '+e,{detached: true});
break;
case'deleteFiles':
if(!e.age_type){e.age_type='min'};if(!e.age){e.age='1'};
exec('find '+e.path+' -type f -c'+e.age_type+' +'+e.age+' -exec rm -f {} +',{detached: true});
break;
}
}
}

217
libs/childNode.js Normal file
View file

@ -0,0 +1,217 @@
var fs = require('fs');
var http = require('http');
var https = require('https');
var express = require('express');
module.exports = function(s,config,lang,app,io){
//setup Master for childNodes
if(config.childNodes.enabled === true && config.childNodes.mode === 'master'){
s.childNodes = {};
var childNodeHTTP = express();
var childNodeServer = http.createServer(app);
var childNodeWebsocket = new (require('socket.io'))()
childNodeServer.listen(config.childNodes.port,config.bindip,function(){
console.log(lang.Shinobi+' - CHILD NODE PORT : '+config.childNodes.port);
});
s.debugLog('childNodeWebsocket.attach(childNodeServer)')
childNodeWebsocket.attach(childNodeServer);
//send data to child node function (experimental)
s.cx = function(z,y,x){
if(!z.mid && !z.d){
console.error('Missing ID')
}else if(x){
x.broadcast.to(y).emit('c',z)
}else{
childNodeWebsocket.to(y).emit('c',z)
}
}
//child Node Websocket
childNodeWebsocket.on('connection', function (cn) {
//functions for dispersing work to child servers;
cn.on('c',function(d){
if(config.childNodes.key.indexOf(d.socketKey) > -1){
if(!cn.shinobi_child&&d.f=='init'){
cn.ip = cn.request.connection.remoteAddress.replace('::ffff:','')+':'+d.port
cn.shinobi_child = 1
tx = function(z){
cn.emit('c',z)
}
if(!s.childNodes[cn.ip]){
s.childNodes[cn.ip] = {}
};
s.childNodes[cn.ip].cnid = cn.id
s.childNodes[cn.ip].cpu = 0
s.childNodes[cn.ip].activeCameras = {}
d.availableHWAccels.forEach(function(accel){
if(config.availableHWAccels.indexOf(accel) === -1)config.availableHWAccels.push(accel)
})
tx({
f : 'init_success',
childNodes : s.childNodes
})
s.childNodes[cn.ip].coreCount = d.coreCount
}else{
switch(d.f){
case'cpu':
s.childNodes[cn.ip].cpu = d.cpu;
break;
case'sql':
s.sqlQuery(d.query,d.values,function(err,rows){
cn.emit('c',{f:'sqlCallback',rows:rows,err:err,callbackId:d.callbackId});
});
break;
case'camera':
s.camera(d.mode,d.data)
break;
case's.tx':
s.tx(d.data,d.to)
break;
case's.userLog':
if(!d.mon || !d.data)return console.log('LOG DROPPED',d.mon,d.data);
s.userLog(d.mon,d.data)
break;
case'created_file_chunk':
if(!s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename]){
d.dir = s.getVideoDirectory(s.group[d.ke].mon_conf[d.mid])
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename] = fs.createWriteStream(d.dir+d.filename)
}
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename].write(d.chunk)
break;
case'created_file':
if(!s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename]){
return console.log('FILE NOT EXIST')
}
s.group[d.ke].mon[d.mid].childNodeStreamWriters[d.filename].end();
tx({
f:'delete',
file:d.filename,
ke:d.ke,
mid:d.mid
});
s.txWithSubPermissions({
f:'video_build_success',
hrefNoAuth:'/videos/'+d.ke+'/'+d.mid+'/'+d.filename,
filename:d.filename,
mid:d.mid,
ke:d.ke,
time:d.time,
size:d.filesize,
end:d.end
},'GRP_'+d.ke,'video_view')
//save database row
var insert = {
startTime : d.time,
filesize : d.filesize,
endTime : d.end,
dir : s.getVideoDirectory(d.d),
file : d.filename,
filename : d.filename,
filesizeMB : parseFloat((d.filesize/1000000).toFixed(2))
}
s.insertDatabaseRow(d.d,insert)
s.insertCompletedVideoExtensions.forEach(function(extender){
extender(d.d,insert)
})
//purge over max
s.purgeDiskForGroup(d)
//send new diskUsage values
s.setDiskUsedForGroup(d,insert.filesizeMB)
clearTimeout(s.group[d.ke].mon[d.mid].recordingChecker)
clearTimeout(s.group[d.ke].mon[d.mid].streamChecker)
break;
}
}
}
})
cn.on('disconnect',function(){
console.log('childNodeWebsocket.disconnect')
if(s.childNodes[cn.ip]){
var activeCameraKeys = Object.keys(s.childNodes[cn.ip].activeCameras)
activeCameraKeys.forEach(function(key){
var monitor = s.childNodes[cn.ip].activeCameras[key]
s.camera('stop',s.cleanMonitorObject(monitor))
delete(s.group[monitor.ke].mon[monitor.mid].childNode)
delete(s.group[monitor.ke].mon[monitor.mid].childNodeId)
setTimeout(function(){
s.camera(monitor.mode,s.cleanMonitorObject(monitor))
},1300)
})
delete(s.childNodes[cn.ip]);
}
})
})
}else
//setup Child for childNodes
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
s.connected = false;
childIO = require('socket.io-client')('ws://'+config.childNodes.host);
s.cx = function(x){x.socketKey = config.childNodes.key;childIO.emit('c',x)}
s.tx = function(x,y){s.cx({f:'s.tx',data:x,to:y})}
s.userLog = function(x,y){s.cx({f:'s.userLog',mon:x,data:y})}
s.queuedSqlCallbacks = {}
s.sqlQuery = function(query,values,onMoveOn){
var callbackId = s.gid()
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(typeof onMoveOn !== 'function'){onMoveOn=function(){}}
s.queuedSqlCallbacks[callbackId] = onMoveOn
s.cx({f:'sql',query:query,values:values,callbackId:callbackId});
}
setInterval(function(){
s.cpuUsage(function(cpu){
io.emit('c',{f:'cpu',cpu:parseFloat(cpu)});
})
},2000);
childIO.on('connect', function(d){
console.log('CHILD CONNECTION SUCCESS')
s.cx({
f : 'init',
port : config.port,
coreCount : s.coreCount,
availableHWAccels : config.availableHWAccels
})
})
childIO.on('c', function (d) {
switch(d.f){
case'sqlCallback':
if(s.queuedSqlCallbacks[d.callbackId]){
s.queuedSqlCallbacks[d.callbackId](d.err,d.rows)
delete(s.queuedSqlCallbacks[d.callbackId])
}
break;
case'init_success':
s.connected=true;
s.other_helpers=d.child_helpers;
break;
case'kill':
s.initiateMonitorObject(d.d);
s.cameraDestroy(s.group[d.d.ke].mon[d.d.id].spawn,d.d)
break;
case'sync':
s.initiateMonitorObject(d.sync);
Object.keys(d.sync).forEach(function(v){
s.group[d.sync.ke].mon[d.sync.mid][v]=d.sync[v];
});
break;
case'delete'://delete video
s.file('delete',s.dir.videos+d.ke+'/'+d.mid+'/'+d.file)
break;
case'insertCompleted'://close video
s.insertCompletedVideo(d.d,d.k)
break;
case'cameraStop'://start camera
s.camera('stop',d.d)
break;
case'cameraStart'://start or record camera
s.camera(d.mode,d.d)
break;
}
})
childIO.on('disconnect',function(d){
s.connected = false;
})
}
}

555
libs/cloudUploaders.js Normal file
View file

@ -0,0 +1,555 @@
var fs = require('fs');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var webdav = require("webdav-fs");
module.exports = function(s,config,lang){
// WebDAV
var beforeAccountSaveForWebDav = function(d){
//d = save event
d.form.details.use_webdav=d.d.use_webdav
}
var cloudDiskUseStartupForWebDav = function(group,userDetails){
group.cloudDiskUse['webdav'].name = 'WebDAV'
group.cloudDiskUse['webdav'].sizeLimitCheck = (userDetails.use_webdav_size_limit === '1')
if(!userDetails.webdav_size_limit || userDetails.webdav_size_limit === ''){
group.cloudDiskUse['webdav'].sizeLimit = 10000
}else{
group.cloudDiskUse['webdav'].sizeLimit = parseFloat(userDetails.webdav_size_limit)
}
}
var loadWebDavForUser = function(e){
// e = user
var ar = JSON.parse(e.details);
//owncloud/webdav
if(!s.group[e.ke].webdav &&
ar.webdav_user&&
ar.webdav_user!==''&&
ar.webdav_pass&&
ar.webdav_pass!==''&&
ar.webdav_url&&
ar.webdav_url!==''
){
if(!ar.webdav_dir||ar.webdav_dir===''){
ar.webdav_dir='/'
}
ar.webdav_dir = s.checkCorrectPathEnding(ar.webdav_dir)
s.group[e.ke].webdav = webdav(
ar.webdav_url,
ar.webdav_user,
ar.webdav_pass
)
}
}
var unloadWebDavForUser = function(user){
s.group[user.ke].webdav = null
}
var deleteVideoFromWebDav = function(e,video,callback){
// e = user
try{
var videoDetails = JSON.parse(video.details)
}catch(err){
var videoDetails = video.details
}
if(!videoDetails.location){
var prefix = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass)
videoDetails.location = video.href.replace(prefix,'')
}
s.group[e.ke].webdav.unlink(videoDetails.location, function(err) {
if (err) console.log(videoDetails.location,err)
callback()
})
}
var uploadVideoToWebDav = function(e,k){
//e = video object
//k = temporary values
if(!k)k={};
//cloud saver - webdav
var wfs = s.group[e.ke].webdav
if(wfs && s.group[e.ke].init.use_webdav !== '0' && s.group[e.ke].init.webdav_save === "1"){
var webdavUploadDir = s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'
var startWebDavUpload = function(){
s.group[e.ke].mon[e.id].webdavDirExist = true
var wfsWriteStream =
fs.createReadStream(k.dir + k.filename).pipe(wfs.createWriteStream(webdavUploadDir + k.filename))
if(s.group[e.ke].init.webdav_log === '1'){
var webdavRemoteUrl = s.addUserPassToUrl(s.checkCorrectPathEnding(s.group[e.ke].init.webdav_url),s.group[e.ke].init.webdav_user,s.group[e.ke].init.webdav_pass) + s.group[e.ke].init.webdav_dir + e.ke + '/'+e.mid+'/'+k.filename
var save = [
e.mid,
e.ke,
k.startTime,
1,
s.s({
type : 'webdav',
location : webdavUploadDir + k.filename
}),
k.filesize,
k.endTime,
webdavRemoteUrl
]
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
s.setCloudDiskUsedForGroup(e,{
amount : k.filesizeMB,
storageType : 'webdav'
})
s.purgeCloudDiskForGroup(e,'webdav')
}
}
if(s.group[e.ke].mon[e.id].webdavDirExist !== true){
//check if webdav dir exist
var parentPoint = 0
var webDavParentz = webdavUploadDir.split('/')
var webDavParents = []
webDavParentz.forEach(function(v){
if(v && v !== '')webDavParents.push(v)
})
var stitchPieces = './'
var lastParentCheck = function(){
++parentPoint
if(parentPoint === webDavParents.length){
startWebDavUpload()
}
checkPathPiece(webDavParents[parentPoint])
}
var checkPathPiece = function(pathPiece){
if(pathPiece && pathPiece !== ''){
stitchPieces += pathPiece + '/'
wfs.stat(stitchPieces, function(error, stats) {
if(error){
reply = {
status : error.status,
msg : lang.WebdavErrorTextTryCreatingDir,
dir : stitchPieces,
}
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
wfs.mkdir(stitchPieces, function(error) {
if(error){
reply = {
status : error.status,
msg : lang.WebdavErrorTextCreatingDir,
dir : stitchPieces,
}
s.userLog(e,{type:lang['Webdav Error'],msg:reply})
}else{
lastParentCheck()
}
})
}else{
lastParentCheck()
}
})
}else{
++parentPoint
}
}
checkPathPiece(webDavParents[0])
}else{
startWebDavUpload()
}
}
}
//Amazon S3
var beforeAccountSaveForAmazonS3 = function(d){
//d = save event
d.form.details.use_aws_s3=d.d.use_aws_s3
}
var cloudDiskUseStartupForAmazonS3 = function(group,userDetails){
group.cloudDiskUse['s3'].name = 'Amazon S3'
group.cloudDiskUse['s3'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
group.cloudDiskUse['s3'].sizeLimit = 10000
}else{
group.cloudDiskUse['s3'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
}
}
var loadAmazonS3ForUser = function(e){
// e = user
var ar = JSON.parse(e.details);
//Amazon S3
if(!s.group[e.ke].aws &&
!s.group[e.ke].aws_s3 &&
ar.aws_s3 !== '0' &&
ar.aws_accessKeyId !== ''&&
ar.aws_secretAccessKey &&
ar.aws_secretAccessKey !== ''&&
ar.aws_region &&
ar.aws_region !== ''&&
ar.aws_s3_bucket !== ''
){
if(!ar.aws_s3_dir || ar.aws_s3_dir === '/'){
ar.aws_s3_dir = ''
}
if(ar.aws_s3_dir !== ''){
ar.aws_s3_dir = s.checkCorrectPathEnding(ar.aws_s3_dir)
}
s.group[e.ke].aws = new require("aws-sdk")
s.group[e.ke].aws.config = new s.group[e.ke].aws.Config({
accessKeyId: ar.aws_accessKeyId,
secretAccessKey: ar.aws_secretAccessKey,
region: ar.aws_region
})
s.group[e.ke].aws_s3 = new s.group[e.ke].aws.S3();
}
}
var unloadAmazonS3ForUser = function(user){
s.group[user.ke].aws = null
s.group[user.ke].aws_s3 = null
}
var deleteVideoFromAmazonS3 = function(e,video,callback){
// e = user
try{
var videoDetails = JSON.parse(video.details)
}catch(err){
var videoDetails = video.details
}
if(!videoDetails.location){
videoDetails.location = video.href.split('.amazonaws.com')[1]
}
s.group[e.ke].aws_s3.deleteObject({
Bucket: s.group[e.ke].init.aws_s3_bucket,
Key: videoDetails.location,
}, function(err, data) {
if (err) console.log(err);
callback()
});
}
var uploadVideoToAmazonS3 = function(e,k){
//e = video object
//k = temporary values
if(!k)k={};
//cloud saver - amazon s3
if(s.group[e.ke].aws_s3 && s.group[e.ke].init.use_aws_s3 !== '0' && s.group[e.ke].init.aws_s3_save === '1'){
var ext = k.filename.split('.')
ext = ext[ext.length - 1]
var fileStream = fs.createReadStream(k.dir+k.filename);
fileStream.on('error', function (err) {
console.error(err)
})
var saveLocation = s.group[e.ke].init.aws_s3_dir+e.ke+'/'+e.mid+'/'+k.filename
s.group[e.ke].aws_s3.upload({
Bucket: s.group[e.ke].init.aws_s3_bucket,
Key: saveLocation,
Body:fileStream,
ACL:'public-read',
ContentType:'video/'+ext
},function(err,data){
if(err){
s.userLog(e,{type:lang['Amazon S3 Upload Error'],msg:err})
}
if(s.group[e.ke].init.aws_s3_log === '1' && data && data.Location){
var save = [
e.mid,
e.ke,
k.startTime,
1,
s.s({
type : 's3',
location : saveLocation
}),
k.filesize,
k.endTime,
data.Location
]
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
s.setCloudDiskUsedForGroup(e,{
amount : k.filesizeMB,
storageType : 's3'
})
s.purgeCloudDiskForGroup(e,'s3')
}
})
}
}
//Backblaze B2
var beforeAccountSaveForBackblazeB2 = function(d){
//d = save event
d.form.details.use_aws_s3=d.d.use_bb_b2
}
var cloudDiskUseStartupForBackblazeB2 = function(group,userDetails){
group.cloudDiskUse['b2'].name = 'Backblaze B2'
group.cloudDiskUse['b2'].sizeLimitCheck = (userDetails.use_bb_b2_size_limit === '1')
if(!userDetails.bb_b2_size_limit || userDetails.bb_b2_size_limit === ''){
group.cloudDiskUse['b2'].sizeLimit = 10000
}else{
group.cloudDiskUse['b2'].sizeLimit = parseFloat(userDetails.bb_b2_size_limit)
}
}
var loadBackblazeB2ForUser = function(e){
var ar = JSON.parse(e.details);
try{
if(!s.group[e.ke].bb_b2 &&
ar.bb_b2_accountId &&
ar.bb_b2_accountId !=='' &&
ar.bb_b2_applicationKey &&
ar.bb_b2_applicationKey !=='' &&
ar.bb_b2_bucket &&
ar.bb_b2_bucket !== ''
){
var B2 = require('backblaze-b2')
if(!ar.bb_b2_dir || ar.bb_b2_dir === '/'){
ar.bb_b2_dir = ''
}
if(ar.bb_b2_dir !== ''){
ar.bb_b2_dir = s.checkCorrectPathEnding(ar.bb_b2_dir)
}
var b2 = new B2({
accountId: ar.bb_b2_accountId,
applicationKey: ar.bb_b2_applicationKey
})
s.group[e.ke].bb_b2 = b2
var backblazeErr = function(err){
// console.log(err)
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
}
b2.authorize().then(function(resp){
s.group[e.ke].bb_b2_downloadUrl = resp.data.downloadUrl
b2.listBuckets().then(function(resp){
var buckets = resp.data.buckets
var bucketN = -2
buckets.forEach(function(item,n){
if(item.bucketName === ar.bb_b2_bucket){
bucketN = n
}
})
if(bucketN > -1){
s.group[e.ke].bb_b2_bucketId = buckets[bucketN].bucketId
}else{
b2.createBucket(
ar.bb_b2_bucket,
'allPublic'
).then(function(resp){
s.group[e.ke].bb_b2_bucketId = resp.data.bucketId
}).catch(backblazeErr)
}
}).catch(backblazeErr)
}).catch(backblazeErr)
}
}catch(err){
s.debugLog(err)
}
}
var unloadBackblazeB2ForUser = function(user){
s.group[user.ke].bb_b2 = null
}
var deleteVideoFromBackblazeB2 = function(e,video,callback){
// e = user
try{
var videoDetails = JSON.parse(video.details)
}catch(err){
var videoDetails = video.details
}
s.group[e.ke].bb_b2.deleteFileVersion({
fileId: videoDetails.fileId,
fileName: videoDetails.fileName
}).then(function(resp){
// console.log('deleteFileVersion',resp.data)
}).catch(function(err){
console.log('deleteFileVersion',err)
})
}
var uploadVideoToBackblazeB2 = function(e,k){
//e = video object
//k = temporary values
if(!k)k={};
//cloud saver - Backblaze B2
if(s.group[e.ke].bb_b2 && s.group[e.ke].init.use_bb_b2 !== '0' && s.group[e.ke].init.bb_b2_save === '1'){
var backblazeErr = function(err){
// console.log(err)
s.userLog({mid:'$USER',ke:e.ke},{type:lang['Backblaze Error'],msg:err.data})
}
fs.readFile(k.dir+k.filename,function(err,data){
var backblazeSavePath = s.group[e.ke].init.bb_b2_dir+e.ke+'/'+e.mid+'/'+k.filename
var getUploadUrl = function(bucketId,callback){
s.group[e.ke].bb_b2.getUploadUrl(bucketId).then(function(resp){
callback(resp.data)
}).catch(backblazeErr)
}
getUploadUrl(s.group[e.ke].bb_b2_bucketId,function(req){
s.group[e.ke].bb_b2.uploadFile({
uploadUrl: req.uploadUrl,
uploadAuthToken: req.authorizationToken,
filename: backblazeSavePath,
data: data,
onUploadProgress: null
}).then(function(resp){
if(s.group[e.ke].init.bb_b2_log === '1' && resp.data.fileId){
var backblazeDownloadUrl = s.group[e.ke].bb_b2_downloadUrl + '/file/' + s.group[e.ke].init.bb_b2_bucket + '/' + backblazeSavePath
var save = [
e.mid,
e.ke,
k.startTime,
1,
s.s({
type : 'b2',
bucketId : resp.data.bucketId,
fileId : resp.data.fileId,
fileName : resp.data.fileName
}),
k.filesize,
k.endTime,
backblazeDownloadUrl
]
s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
s.setCloudDiskUsedForGroup(e,{
amount : k.filesizeMB,
storageType : 'b2'
})
s.purgeCloudDiskForGroup(e,'b2')
}
}).catch(backblazeErr)
})
})
}
}
//SFTP
// var beforeAccountSaveForSftp = function(d){
// //d = save event
// d.form.details.use_sftp = d.d.use_sftp
// }
// var cloudDiskUseStartupForSftp = function(group,userDetails){
// group.cloudDiskUse['sftp'].name = 'SFTP'
// group.cloudDiskUse['sftp'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
// if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
// group.cloudDiskUse['sftp'].sizeLimit = 10000
// }else{
// group.cloudDiskUse['sftp'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
// }
// }
// var loadSftpForUser = function(e){
// // e = user
// var ar = JSON.parse(e.details);
// //SFTP
// if(!s.group[e.ke].sftp &&
// !s.group[e.ke].sftp &&
// ar.sftp !== '0' &&
// ar.sftp_accessKeyId !== ''&&
// ar.sftp_secretAccessKey &&
// ar.sftp_secretAccessKey !== ''&&
// ar.sftp_region &&
// ar.sftp_region !== ''&&
// ar.sftp_bucket !== ''
// ){
// if(!ar.sftp_dir || ar.sftp_dir === '/'){
// ar.sftp_dir = ''
// }
// if(ar.sftp_dir !== ''){
// ar.sftp_dir = s.checkCorrectPathEnding(ar.sftp_dir)
// }
// s.group[e.ke].sftp = new s.group[e.ke].sftp.S3();
// s.group[e.ke].sftp = new require('ssh2-sftp-client')();
// var connectionDetails = {
// host: ar.sftp_host,
// port: ar.sftp_port
// }
// if(!ar.sftp_port)ar.sftp_port = 22
// if(ar.sftp_username)connectionDetails.username = ar.sftp_username
// if(ar.sftp_password)connectionDetails.password = ar.sftp_password
// if(ar.sftp_privateKey)connectionDetails.privateKey = ar.sftp_privateKey
// sftp.connect(connectionDetails).then(() => {
// return sftp.list('/pathname');
// }).then((data) => {
// console.log(data, 'the data info');
// }).catch((err) => {
// console.log(err, 'catch error');
// });
// }
// }
// var unloadSftpForUser = function(user){
// s.group[user.ke].sftp = null
// }
// var deleteVideoFromSftp = function(e,video,callback){
// // e = user
// try{
// var videoDetails = JSON.parse(video.details)
// }catch(err){
// var videoDetails = video.details
// }
// s.group[e.ke].sftp.deleteObject({
// Bucket: s.group[e.ke].init.sftp_bucket,
// Key: videoDetails.location,
// }, function(err, data) {
// if (err) console.log(err);
// callback()
// });
// }
// var uploadVideoToSftp = function(e,k){
// //e = video object
// //k = temporary values
// if(!k)k={};
// //cloud saver - SFTP
// if(s.group[e.ke].sftp && s.group[e.ke].init.use_sftp !== '0' && s.group[e.ke].init.sftp_save === '1'){
// var fileStream = fs.createReadStream(k.dir+k.filename);
// fileStream.on('error', function (err) {
// console.error(err)
// })
// var saveLocation = s.group[e.ke].init.sftp_dir+e.ke+'/'+e.mid+'/'+k.filename
// s.group[e.ke].sftp.upload({
// Bucket: s.group[e.ke].init.sftp_bucket,
// Key: saveLocation,
// Body:fileStream,
// ACL:'public-read'
// },function(err,data){
// if(err){
// s.userLog(e,{type:lang['SFTP Upload Error'],msg:err})
// }
// if(s.group[e.ke].init.sftp_log === '1' && data && data.Location){
// var save = [
// e.mid,
// e.ke,
// k.startTime,
// 1,
// s.s({
// type : 'sftp',
// location : saveLocation
// }),
// k.filesize,
// k.endTime,
// data.Location
// ]
// s.sqlQuery('INSERT INTO `Cloud Videos` (mid,ke,time,status,details,size,end,href) VALUES (?,?,?,?,?,?,?,?)',save)
// s.setCloudDiskUsedForGroup(e,{
// amount : k.filesizeMB,
// storageType : 'sftp'
// })
// s.purgeCloudDiskForGroup(e,'sftp')
// }
// })
// }
// }
//add the extenders
//webdav
s.loadGroupAppExtender(loadWebDavForUser)
s.unloadGroupAppExtender(unloadWebDavForUser)
s.insertCompletedVideoExtender(uploadVideoToWebDav)
s.deleteVideoFromCloudExtensions['webdav'] = deleteVideoFromWebDav
s.cloudDiskUseStartupExtensions['webdav'] = cloudDiskUseStartupForWebDav
s.beforeAccountSave(beforeAccountSaveForWebDav)
s.onAccountSave(cloudDiskUseStartupForWebDav)
s.cloudDisksLoader('webdav')
//amazon s3
s.loadGroupAppExtender(loadAmazonS3ForUser)
s.unloadGroupAppExtender(unloadAmazonS3ForUser)
s.insertCompletedVideoExtender(uploadVideoToAmazonS3)
s.deleteVideoFromCloudExtensions['s3'] = deleteVideoFromAmazonS3
s.cloudDiskUseStartupExtensions['s3'] = cloudDiskUseStartupForAmazonS3
s.beforeAccountSave(beforeAccountSaveForAmazonS3)
s.onAccountSave(cloudDiskUseStartupForAmazonS3)
s.cloudDisksLoader('s3')
//backblaze b2
s.loadGroupAppExtender(loadBackblazeB2ForUser)
s.unloadGroupAppExtender(unloadBackblazeB2ForUser)
s.insertCompletedVideoExtender(uploadVideoToBackblazeB2)
s.deleteVideoFromCloudExtensions['b2'] = deleteVideoFromBackblazeB2
s.cloudDiskUseStartupExtensions['b2'] = cloudDiskUseStartupForBackblazeB2
s.beforeAccountSave(beforeAccountSaveForBackblazeB2)
s.onAccountSave(cloudDiskUseStartupForBackblazeB2)
s.cloudDisksLoader('b2')
//SFTP
// s.loadGroupAppExtender(loadSftpForUser)
// s.unloadGroupAppExtender(unloadSftpForUser)
// s.insertCompletedVideoExtender(uploadVideoToSftp)
// s.deleteVideoFromCloudExtensions['sftp'] = deleteVideoFromSftp
// s.cloudDiskUseStartupExtensions['sftp'] = cloudDiskUseStartupForSftp
// s.beforeAccountSave(beforeAccountSaveForSftp)
// s.onAccountSave(cloudDiskUseStartupForSftp)
// s.cloudDisksLoader('sftp')
}

55
libs/config.js Normal file
View file

@ -0,0 +1,55 @@
module.exports = function(s){
s.location = {
super : s.mainDirectory+'/super.json',
config : s.mainDirectory+'/conf.json',
languages : s.mainDirectory+'/languages'
}
var config = require(s.location.config);
if(!config.productType){
config.productType='CE'
}
//config defaults
if(config.cpuUsageMarker === undefined){config.cpuUsageMarker='%Cpu'}
if(config.customCpuCommand === undefined){config.customCpuCommand=null}
if(config.autoDropCache === undefined){config.autoDropCache=true}
if(config.doSnapshot === undefined){config.doSnapshot=true}
if(config.restart === undefined){config.restart={}}
if(config.systemLog === undefined){config.systemLog=true}
if(config.deleteCorruptFiles === undefined){config.deleteCorruptFiles=true}
if(config.restart.onVideoNotExist === undefined){config.restart.onVideoNotExist=true}
if(config.ip === undefined||config.ip===''||config.ip.indexOf('0.0.0.0')>-1){config.ip='localhost'}else{config.bindip=config.ip};
if(config.cron === undefined)config.cron={};
if(config.cron.enabled === undefined)config.cron.enabled=true;
if(config.cron.deleteOld === undefined)config.cron.deleteOld=true;
if(config.cron.deleteNoVideo === undefined)config.cron.deleteNoVideo=true;
if(config.cron.deleteNoVideoRecursion === undefined)config.cron.deleteNoVideoRecursion=false;
if(config.cron.deleteOverMax === undefined)config.cron.deleteOverMax=true;
if(config.cron.deleteOverMaxOffset === undefined)config.cron.deleteOverMaxOffset=0.9;
if(config.cron.deleteLogs === undefined)config.cron.deleteLogs=true;
if(config.cron.deleteEvents === undefined)config.cron.deleteEvents=true;
if(config.cron.deleteFileBins === undefined)config.cron.deleteFileBins=true;
if(config.cron.interval === undefined)config.cron.interval=1;
if(config.databaseType === undefined){config.databaseType='mysql'}
if(config.pluginKeys === undefined)config.pluginKeys={};
if(config.databaseLogs === undefined){config.databaseLogs=false}
if(config.useUTC === undefined){config.useUTC=false}
if(config.iconURL === undefined){config.iconURL = "https://shinobi.video/libs/assets/icon/apple-touch-icon-152x152.png"}
if(config.pipeAddition === undefined){config.pipeAddition=7}else{config.pipeAddition=parseInt(config.pipeAddition)}
if(config.hideCloudSaveUrls === undefined){config.hideCloudSaveUrls = true}
if(config.insertOrphans === undefined){config.insertOrphans = true}
if(config.orphanedVideoCheckMax === undefined){config.orphanedVideoCheckMax = 20}
//Child Nodes
if(config.childNodes === undefined)config.childNodes = {};
//enabled
if(config.childNodes.enabled === undefined)config.childNodes.enabled = false;
//mode, set value as `child` for all other machines in the cluster
if(config.childNodes.mode === undefined)config.childNodes.mode = 'master';
//child node connection port
if(config.childNodes.port === undefined)config.childNodes.port = 8288;
//child node connection key
if(config.childNodes.key === undefined)config.childNodes.key = [
'3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345'
];
return config
}

204
libs/detector.js Normal file
View file

@ -0,0 +1,204 @@
var P2P = require('pipe2pam');
var PamDiff = require('pam-diff');
module.exports = function(s,config){
s.createPamDiffEngine = function(e){
var width,
height,
globalSensitivity,
globalColorThreshold,
fullFrame = false
if(s.group[e.ke].mon_conf[e.id].details.detector_scale_x===''||s.group[e.ke].mon_conf[e.id].details.detector_scale_y===''){
width = s.group[e.ke].mon_conf[e.id].details.detector_scale_x;
height = s.group[e.ke].mon_conf[e.id].details.detector_scale_y;
}else{
width = e.width
height = e.height
}
if(e.details.detector_sensitivity===''){
globalSensitivity = 10
}else{
globalSensitivity = parseInt(e.details.detector_sensitivity)
}
if(e.details.detector_color_threshold===''){
globalColorThreshold = 9
}else{
globalColorThreshold = parseInt(e.details.detector_color_threshold)
}
globalThreshold = parseInt(e.details.detector_threshold) || 0
var regionJson
try{
regionJson = JSON.parse(s.group[e.ke].mon_conf[e.id].details.cords)
}catch(err){
regionJson = s.group[e.ke].mon_conf[e.id].details.cords
}
if(Object.keys(regionJson).length === 0 || e.details.detector_frame === '1'){
fullFrame = {
name:'FULL_FRAME',
sensitivity:globalSensitivity,
color_threshold:globalColorThreshold,
points:[
[0,0],
[0,height],
[width,height],
[width,0]
]
};
}
e.triggerTimer = {}
var regions = s.createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
s.group[e.ke].mon[e.id].pamDiff = new PamDiff({grayscale: 'luminosity', regions : regions.forPam});
s.group[e.ke].mon[e.id].p2p = new P2P();
var sendTrigger = function(trigger){
var detectorObject = {
f:'trigger',
id:e.id,
ke:e.ke,
name:trigger.name,
details:{
plug:'built-in',
name:trigger.name,
reason:'motion',
confidence:trigger.percent,
},
plates:[],
imgHeight:height,
imgWidth:width
}
var region = Object.values(regionJson).find(x => x.name == detectorObject.name)
s.checkMaximumSensitivity(e, region, detectorObject, function() {
s.checkTriggerThreshold(e, region, detectorObject, function() {
detectorObject.doObjectDetection = (s.ocv && e.details.detector_use_detect_object === '1')
s.triggerEvent(detectorObject)
})
})
}
if(e.details.detector_noise_filter==='1'){
if(!s.group[e.ke].mon[e.id].noiseFilterArray)s.group[e.ke].mon[e.id].noiseFilterArray = {}
var noiseFilterArray = s.group[e.ke].mon[e.id].noiseFilterArray
Object.keys(regions.notForPam).forEach(function(name){
if(!noiseFilterArray[name])noiseFilterArray[name]=[];
})
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
data.trigger.forEach(function(trigger){
s.filterTheNoise(e,noiseFilterArray,regions,trigger,function(){
sendTrigger(trigger)
})
})
})
}else{
s.group[e.ke].mon[e.id].pamDiff.on('diff', (data) => {
data.trigger.forEach(sendTrigger)
})
}
}
s.createPamDiffRegionArray = function(regions,globalColorThreshold,globalSensitivity,fullFrame){
var pamDiffCompliantArray = [],
arrayForOtherStuff = [],
json
try{
json = JSON.parse(regions)
}catch(err){
json = regions
}
if(fullFrame){
json[fullFrame.name]=fullFrame;
}
Object.values(json).forEach(function(region){
if(!region)return false;
region.polygon = [];
region.points.forEach(function(points){
region.polygon.push({x:parseFloat(points[0]),y:parseFloat(points[1])})
})
if(region.sensitivity===''){
region.sensitivity = globalSensitivity
}else{
region.sensitivity = parseInt(region.sensitivity)
}
if(region.color_threshold===''){
region.color_threshold = globalColorThreshold
}else{
region.color_threshold = parseInt(region.color_threshold)
}
pamDiffCompliantArray.push({name: region.name, difference: region.color_threshold, percent: region.sensitivity, polygon:region.polygon})
arrayForOtherStuff[region.name] = region;
})
if(pamDiffCompliantArray.length===0){pamDiffCompliantArray = null}
return {forPam:pamDiffCompliantArray,notForPam:arrayForOtherStuff};
}
s.filterTheNoise = function(e,noiseFilterArray,regions,trigger,callback){
if(noiseFilterArray[trigger.name].length > 2){
var thePreviousTriggerPercent = noiseFilterArray[trigger.name][noiseFilterArray[trigger.name].length - 1];
var triggerDifference = trigger.percent - thePreviousTriggerPercent;
var noiseRange = e.details.detector_noise_filter_range
if(!noiseRange || noiseRange === ''){
noiseRange = 6
}
noiseRange = parseFloat(noiseRange)
if(((trigger.percent - thePreviousTriggerPercent) < noiseRange)||(thePreviousTriggerPercent - trigger.percent) > -noiseRange){
noiseFilterArray[trigger.name].push(trigger.percent);
}
}else{
noiseFilterArray[trigger.name].push(trigger.percent);
}
if(noiseFilterArray[trigger.name].length > 10){
noiseFilterArray[trigger.name] = noiseFilterArray[trigger.name].splice(1,10)
}
var theNoise = 0;
noiseFilterArray[trigger.name].forEach(function(v,n){
theNoise += v;
})
theNoise = theNoise / noiseFilterArray[trigger.name].length;
var triggerPercentWithoutNoise = trigger.percent - theNoise;
if(triggerPercentWithoutNoise > regions.notForPam[trigger.name].sensitivity){
callback(trigger)
}
}
s.checkMaximumSensitivity = function(monitor, region, detectorObject, success) {
var logName = detectorObject.id + ':' + detectorObject.name
var globalMaxSensitivity = parseInt(monitor.details.detector_max_sensitivity) || undefined
var maxSensitivity = parseInt(region.max_sensitivity) || globalMaxSensitivity
if (maxSensitivity === undefined || detectorObject.details.confidence <= maxSensitivity) {
success()
} else {
if (monitor.triggerTimer[detectorObject.name] !== undefined) {
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
monitor.triggerTimer[detectorObject.name] = undefined
}
}
}
s.checkTriggerThreshold = function(monitor, region, detectorObject, success){
var threshold = parseInt(region.threshold) || globalThreshold
if (threshold <= 1) {
success()
} else {
if (monitor.triggerTimer[detectorObject.name] === undefined) {
monitor.triggerTimer[detectorObject.name] = {
count : threshold,
timeout : null
}
}
if (--monitor.triggerTimer[detectorObject.name].count == 0) {
success()
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
monitor.triggerTimer[detectorObject.name] = undefined
} else {
var fps = parseFloat(monitor.details.detector_fps) || 2
if (monitor.triggerTimer[detectorObject.name].timeout !== null)
clearTimeout(monitor.triggerTimer[detectorObject.name].timeout)
monitor.triggerTimer[detectorObject.name].timeout = setTimeout(function() {
monitor.triggerTimer[detectorObject.name] = undefined
}, ((threshold+0.5) * 1000) / fps)
}
}
}
}

384
libs/events.js Normal file
View file

@ -0,0 +1,384 @@
var moment = require('moment');
var execSync = require('child_process').execSync;
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var request = require('request');
module.exports = function(s,config,lang){
s.filterEvents = function(x,d){
switch(x){
case'archive':
d.videos.forEach(function(v,n){
s.video('archive',v)
})
break;
case'delete':
d.videos.forEach(function(v,n){
s.deleteVideo(v)
})
break;
case'execute':
exec(d.execute,{detached: true})
break;
}
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
extender(x,d)
})
}
s.triggerEvent = function(d){
var filter = {
halt : false,
addToMotionCounter : true,
useLock : true,
save : true,
webhook : true,
command : true,
record : true,
indifference : false
}
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
extender(d,filter)
})
if(s.group[d.ke].mon[d.id].open){
d.details.videoTime = s.group[d.ke].mon[d.id].open;
}
var detailString = JSON.stringify(d.details);
if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]){
return s.systemLog(lang['No Monitor Found, Ignoring Request'])
}
d.mon=s.group[d.ke].mon_conf[d.id];
var currentConfig = s.group[d.ke].mon[d.id].details
//read filters
if(
currentConfig.use_detector_filters === '1' &&
((currentConfig.use_detector_filters_object === '1' && d.details.matrices) ||
currentConfig.use_detector_filters_object !== '1')
){
var parseValue = function(key,val){
var newVal
switch(val){
case'':
newVal = filter[key]
break;
case'0':
newVal = false
break;
case'1':
newVal = true
break;
default:
newVal = val
break;
}
return newVal
}
var filters = currentConfig.detector_filters
Object.keys(filters).forEach(function(key){
var conditionChain = {}
var dFilter = filters[key]
dFilter.where.forEach(function(condition,place){
conditionChain[place] = {ok:false,next:condition.p4,matrixCount:0}
if(d.details.matrices)conditionChain[place].matrixCount = d.details.matrices.length
var modifyFilters = function(toCheck,matrixPosition){
var param = toCheck[condition.p1]
var pass = function(){
if(matrixPosition && dFilter.actions.halt === '1'){
delete(d.details.matrices[matrixPosition])
}else{
conditionChain[place].ok = true
}
}
switch(condition.p2){
case'indexOf':
if(param.indexOf(condition.p3) > -1){
pass()
}
break;
case'!indexOf':
if(param.indexOf(condition.p3) === -1){
pass()
}
break;
default:
if(eval('param '+condition.p2+' "'+condition.p3.replace(/"/g,'\\"')+'"')){
pass()
}
break;
}
}
switch(condition.p1){
case'tag':
case'x':
case'y':
case'height':
case'width':
if(d.details.matrices){
d.details.matrices.forEach(function(matrix,position){
modifyFilters(matrix,position)
})
}
break;
case'time':
var timeNow = new Date()
var timeCondition = new Date()
var doAtTime = condition.p3.split(':')
var atHour = parseInt(doAtTime[0]) - 1
var atHourNow = timeNow.getHours()
var atMinuteNow = timeNow.getMinutes()
var atSecondNow = timeNow.getSeconds()
if(atHour){
var atMinute = parseInt(doAtTime[1]) - 1 || timeNow.getMinutes()
var atSecond = parseInt(doAtTime[2]) - 1 || timeNow.getSeconds()
var nowAddedInSeconds = atHourNow * 60 * 60 + atMinuteNow * 60 + atSecondNow
var conditionAddedInSeconds = atHour * 60 * 60 + atMinute * 60 + atSecond
if(eval('nowAddedInSeconds '+condition.p2+' conditionAddedInSeconds')){
conditionChain[place].ok = true
}
}
break;
default:
modifyFilters(d.details)
break;
}
})
var conditionArray = Object.values(conditionChain)
var validationString = ''
conditionArray.forEach(function(condition,number){
validationString += condition.ok+' '
if(conditionArray.length-1 !== number){
validationString += condition.next+' '
}
})
if(eval(validationString)){
if(dFilter.actions.halt !== '1'){
delete(dFilter.actions.halt)
Object.keys(dFilter.actions).forEach(function(key){
var value = dFilter.actions[key]
filter[key] = parseValue(key,value)
})
}else{
filter.halt = true
}
}
})
if(d.details.matrices && d.details.matrices.length === 0 || filter.halt === true){
return
}else if(d.details.matrices && d.details.matrices.length > 0){
var reviewedMatrix = []
d.details.matrices.forEach(function(matrix){
if(matrix)reviewedMatrix.push(matrix)
})
d.details.matrices = reviewedMatrix
}
}
//motion counter
if(filter.addToMotionCounter && filter.record){
if(!s.group[d.ke].mon[d.id].detector_motion_count){
s.group[d.ke].mon[d.id].detector_motion_count=0
}
s.group[d.ke].mon[d.id].detector_motion_count+=1
}
if(filter.useLock){
if(s.group[d.ke].mon[d.id].motion_lock){
return
}
var detector_lock_timeout
if(!currentConfig.detector_lock_timeout||currentConfig.detector_lock_timeout===''){
detector_lock_timeout = 2000
}
detector_lock_timeout = parseFloat(currentConfig.detector_lock_timeout);
if(!s.group[d.ke].mon[d.id].detector_lock_timeout){
s.group[d.ke].mon[d.id].detector_lock_timeout=setTimeout(function(){
clearTimeout(s.group[d.ke].mon[d.id].detector_lock_timeout)
delete(s.group[d.ke].mon[d.id].detector_lock_timeout)
},detector_lock_timeout)
}else{
return
}
}
// check modified indifference
if(filter.indifference !== false && d.details.confidence < parseFloat(filter.indifference)){
// fails indifference check for modified indifference
return
}
//
if(d.doObjectDetection === true){
s.ocvTx({
f : 'frame',
mon : s.group[d.ke].mon_conf[d.id].details,
ke : d.ke,
id : d.id,
time : s.formattedTime(),
frame : s.group[d.ke].mon[d.id].lastJpegDetectorFrame
})
}else{
//save this detection result in SQL, only coords. not image.
if(filter.save && currentConfig.detector_save==='1'){
s.sqlQuery('INSERT INTO Events (ke,mid,details) VALUES (?,?,?)',[d.ke,d.id,detailString])
}
if(currentConfig.detector_notrigger === '1'){
var detector_notrigger_timeout
if(!currentConfig.detector_notrigger_timeout||currentConfig.detector_notrigger_timeout===''){
detector_notrigger_timeout = 10
}
detector_notrigger_timeout = parseFloat(currentConfig.detector_notrigger_timeout)*1000*60;
s.group[d.ke].mon[d.id].detector_notrigger_timeout = detector_notrigger_timeout;
clearInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout)
s.group[d.ke].mon[d.id].detector_notrigger_timeout = setInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout_function,detector_notrigger_timeout)
}
var detector_timeout
if(!currentConfig.detector_timeout||currentConfig.detector_timeout===''){
detector_timeout = 10
}else{
detector_timeout = parseFloat(currentConfig.detector_timeout)
}
if(filter.record && d.mon.mode=='start'&&currentConfig.detector_trigger==='1'&&currentConfig.detector_record_method==='sip'){
s.createEventBasedRecording(d)
}else if(filter.record && d.mon.mode!=='stop'&&currentConfig.detector_trigger=='1'&&currentConfig.detector_record_method==='hot'){
if(!d.auth){
d.auth=s.gid();
}
if(!s.group[d.ke].users[d.auth]){
s.group[d.ke].users[d.auth]={system:1,details:{},lang:lang}
}
d.urlQuery = []
d.url = 'http://'+config.ip+':'+config.port+'/'+d.auth+'/monitor/'+d.ke+'/'+d.id+'/record/'+detector_timeout+'/min';
if(currentConfig.watchdog_reset!=='0'){
d.urlQuery.push('reset=1')
}
if(currentConfig.detector_trigger_record_fps&&currentConfig.detector_trigger_record_fps!==''&&currentConfig.detector_trigger_record_fps!=='0'){
d.urlQuery.push('fps='+currentConfig.detector_trigger_record_fps)
}
if(d.urlQuery.length>0){
d.url+='?'+d.urlQuery.join('&')
}
request({url:d.url,method:'GET'},function(err,data){
if(err){
//could not start hotswap
}else{
delete(s.group[d.ke].users[d.auth])
d.cx.f='detector_record_engaged';
d.cx.msg = JSON.parse(data.body)
s.tx(d.cx,'GRP_'+d.ke);
}
})
}
d.currentTime = new Date()
d.currentTimestamp = s.timeObject(d.currentTime).format()
d.screenshotName = 'Motion_'+(d.mon.name.replace(/[^\w\s]/gi,''))+'_'+d.id+'_'+d.ke+'_'+s.formattedTime()
d.screenshotBuffer = null
s.onEventTriggerExtensions.forEach(function(extender){
extender(d,filter)
})
if(filter.webhook && currentConfig.detector_webhook === '1'){
var detector_webhook_url = currentConfig.detector_webhook_url
.replace(/{{TIME}}/g,d.currentTimestamp)
.replace(/{{REGION_NAME}}/g,d.details.name)
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
.replace(/{{MONITOR_ID}}/g,d.id)
.replace(/{{GROUP_KEY}}/g,d.ke)
.replace(/{{DETAILS}}/g,detailString)
if(d.details.confidence){
detector_webhook_url = detector_webhook_url
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
}
request({url:detector_webhook_url,method:'GET',encoding:null},function(err,data){
if(err){
s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
}
})
}
if(filter.command && currentConfig.detector_command_enable === '1' && !s.group[d.ke].mon[d.id].detector_command){
var detector_command_timeout
if(!currentConfig.detector_command_timeout||currentConfig.detector_command_timeout===''){
detector_command_timeout = 1000*60*10;
}else{
detector_command_timeout = parseFloat(currentConfig.detector_command_timeout)*1000*60;
}
s.group[d.ke].mon[d.id].detector_command=setTimeout(function(){
clearTimeout(s.group[d.ke].mon[d.id].detector_command);
delete(s.group[d.ke].mon[d.id].detector_command);
},detector_command_timeout);
var detector_command = currentConfig.detector_command
.replace(/{{TIME}}/g,d.currentTimestamp)
.replace(/{{REGION_NAME}}/g,d.details.name)
.replace(/{{SNAP_PATH}}/g,s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg')
.replace(/{{MONITOR_ID}}/g,d.id)
.replace(/{{GROUP_KEY}}/g,d.ke)
.replace(/{{DETAILS}}/g,detailString)
if(d.details.confidence){
detector_command = detector_command
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
}
exec(detector_command,{detached: true})
}
}
//show client machines the event
d.cx={f:'detector_trigger',id:d.id,ke:d.ke,details:d.details,doObjectDetection:d.doObjectDetection};
s.tx(d.cx,'DETECTOR_'+d.ke+d.id);
}
s.createEventBasedRecording = function(d){
var currentConfig = s.group[d.ke].mon[d.id].details
var detector_timeout
if(!currentConfig.detector_timeout||currentConfig.detector_timeout===''){
detector_timeout = 10
}else{
detector_timeout = parseFloat(currentConfig.detector_timeout)
}
s.group[d.ke].mon[d.id].eventBasedRecording.timeout = setTimeout(function(){
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd=true;
},detector_timeout * 950 * 60)
if(!s.group[d.ke].mon[d.id].eventBasedRecording.process){
if(!d.auth){
d.auth = s.gid(60)
}
if(!s.api[d.auth]){
s.api[d.auth] = {
system: 1,
ip: '0.0.0.0',
details: {},
lang: lang
}
}
s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd = false;
var runRecord = function(){
var filename = s.formattedTime()+'.mp4'
s.userLog(d,{type:"Traditional Recording",msg:"Started"})
//-t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+'
s.group[d.ke].mon[d.id].eventBasedRecording.process = spawn(config.ffmpegDir,s.splitForFFPMEG(('-loglevel warning -analyzeduration 1000000 -probesize 1000000 -re -i http://'+config.ip+':'+config.port+'/'+d.auth+'/hls/'+d.ke+'/'+d.id+'/detectorStream.m3u8 -t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+' -c:v copy -strftime 1 "'+s.getVideoDirectory(d.mon) + filename + '"')))
var ffmpegError='';
var error
s.group[d.ke].mon[d.id].eventBasedRecording.process.stderr.on('data',function(data){
s.userLog(d,{type:"Traditional Recording",msg:data.toString()})
})
s.group[d.ke].mon[d.id].eventBasedRecording.process.on('close',function(){
if(!s.group[d.ke].mon[d.id].eventBasedRecording.allowEnd){
s.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Process Exited Prematurely. Restarting."})
runRecord()
return
}
s.insertCompletedVideo(d.mon,{
file : filename
})
s.userLog(d,{type:"Traditional Recording",msg:"Detector Recording Complete"})
delete(s.api[d.auth])
s.userLog(d,{type:"Traditional Recording",msg:'Clear Recorder Process'})
delete(s.group[d.ke].mon[d.id].eventBasedRecording.process)
delete(s.group[d.ke].mon[d.id].eventBasedRecording.timeout)
clearTimeout(s.group[d.ke].mon[d.id].recordingChecker)
})
}
runRecord()
}
}
s.closeEventBasedRecording = function(e){
if(s.group[e.ke].mon[e.id].eventBasedRecording.process){
clearTimeout(s.group[e.ke].mon[e.id].eventBasedRecording.timeout)
s.group[e.ke].mon[e.id].eventBasedRecording.allowEnd = true;
s.group[e.ke].mon[e.id].eventBasedRecording.process.kill('SIGTERM');
}
}
}

58
libs/extenders.js Normal file
View file

@ -0,0 +1,58 @@
module.exports = function(s,config){
////// USER //////
s.loadGroupAppExtensions = []
s.loadGroupAppExtender = function(callback){
s.loadGroupAppExtensions.push(callback)
}
//
s.unloadGroupAppExtensions = []
s.unloadGroupAppExtender = function(callback){
s.unloadGroupAppExtensions.push(callback)
}
//
s.cloudDisksLoaded = []
s.cloudDisksLoader = function(storageType){
s.cloudDisksLoaded.push(storageType)
}
//
s.onAccountSaveExtensions = []
s.onAccountSave = function(callback){
s.onAccountSaveExtensions.push(callback)
}
//
s.beforeAccountSaveExtensions = []
s.beforeAccountSave = function(callback){
s.beforeAccountSaveExtensions.push(callback)
}
//
s.onTwoFactorAuthCodeNotificationExtensions = []
s.onTwoFactorAuthCodeNotification = function(callback){
s.onTwoFactorAuthCodeNotificationExtensions.push(callback)
}
//
s.cloudDiskUseStartupExtensions = {}
////// EVENTS //////
s.onEventTriggerExtensions = []
s.onEventTrigger = function(callback){
s.onEventTriggerExtensions.push(callback)
}
s.onEventTriggerBeforeFilterExtensions = []
s.onEventTriggerBeforeFilter = function(callback){
s.onEventTriggerBeforeFilterExtensions.push(callback)
}
s.onFilterEventExtensions = []
s.onFilterEvent = function(callback){
s.onFilterEventExtensions.push(callback)
}
////// MONITOR //////
s.onMonitorInitExtensions = []
s.onMonitorInit = function(callback){
s.onMonitorInitExtensions.push(callback)
}
s.onDetectorNoTriggerTimeoutExtensions = []
s.onDetectorNoTriggerTimeout = function(callback){
s.onDetectorNoTriggerTimeoutExtensions.push(callback)
}
}

910
libs/ffmpeg.js Normal file
View file

@ -0,0 +1,910 @@
var fs = require('fs');
var spawn = require('child_process').spawn;
var execSync = require('child_process').execSync;
module.exports = function(s,config,onFinish){
var ffmpeg = {}
var downloadingFfmpeg = false;
//check local ffmpeg
ffmpeg.checkForWindows = function(failback){
if (s.isWin && fs.existsSync(s.mainDirectory+'/ffmpeg/ffmpeg.exe')) {
config.ffmpegDir = s.mainDirectory+'/ffmpeg/ffmpeg.exe'
}else{
failback()
}
}
//check local ffmpeg
ffmpeg.checkForUnix = function(failback){
if(s.isWin === false){
if (fs.existsSync('/usr/bin/ffmpeg')) {
config.ffmpegDir = '/usr/bin/ffmpeg'
}else{
if (fs.existsSync('/usr/local/bin/ffmpeg')) {
config.ffmpegDir = '/usr/local/bin/ffmpeg'
}else{
failback()
}
}
}else{
failback()
}
}
//check node module : ffmpeg-static
ffmpeg.checkForNpmStatic = function(failback){
try{
var staticFFmpeg = require('ffmpeg-static').path;
if (fs.statSync(staticFFmpeg)) {
config.ffmpegDir = staticFFmpeg
}else{
console.log('"ffmpeg-static" from NPM has failed to provide a compatible library or has been corrupted.')
console.log('Run "npm uninstall ffmpeg-static" to remove it.')
console.log('Run "npm install ffbinaries" to get a different static FFmpeg downloader.')
}
}catch(err){
console.log('No "ffmpeg-static".')
failback()
}
}
//check node module : ffbinaries
ffmpeg.checkForFfbinary = function(failback){
try{
ffbinaries = require('ffbinaries')
var ffbinaryDir = s.mainDirectory + '/ffmpeg/'
var downloadFFmpeg = function(){
downloadingFfmpeg = true
console.log('ffbinaries : Downloading FFmpeg. Please Wait...');
ffbinaries.downloadBinaries(['ffmpeg', 'ffprobe'], {
destination: ffbinaryDir,
version : '3.4'
},function () {
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
console.log('ffbinaries : FFmpeg Downloaded.');
ffmpeg.completeCheck()
})
}
if (!fs.existsSync(ffbinaryDir + 'ffmpeg')) {
downloadFFmpeg()
}else{
config.ffmpegDir = ffbinaryDir + 'ffmpeg'
}
}catch(err){
console.log('No "ffbinaries". Continuing.')
console.log('Run "npm install ffbinaries" to get this static FFmpeg downloader.')
failback()
}
}
//ffmpeg version
ffmpeg.checkVersion = function(callback){
try{
s.ffmpegVersion = execSync(config.ffmpegDir+" -version").toString().split('Copyright')[0].replace('ffmpeg version','').trim()
if(s.ffmpegVersion.indexOf(': 2.')>-1){
s.systemLog('FFMPEG is too old : '+s.ffmpegVersion+', Needed : 3.2+',err)
throw (new Error())
}
}catch(err){
console.log('No FFmpeg found.')
// process.exit()
}
callback()
}
//check available hardware acceleration methods
ffmpeg.checkHwAccelMethods = function(callback){
if(config.availableHWAccels === undefined){
hwAccels = execSync(config.ffmpegDir+" -loglevel quiet -hwaccels").toString().split('\n')
hwAccels.shift()
availableHWAccels = []
hwAccels.forEach(function(method){
if(method && method !== '')availableHWAccels.push(method.trim())
})
config.availableHWAccels = availableHWAccels
config.availableHWAccels = ['auto'].concat(config.availableHWAccels)
console.log('Available Hardware Acceleration Methods : ',availableHWAccels.join(', '))
}
callback()
}
ffmpeg.completeCheck = function(){
ffmpeg.checkVersion(function(){
ffmpeg.checkHwAccelMethods(function(){
onFinish(ffmpeg)
})
})
}
//ffmpeg string cleaner, splits for use with spawn()
s.splitForFFPMEG = function (ffmpegCommandAsString) {
//this function ignores spaces inside quotes.
return ffmpegCommandAsString.replace(/\s+/g,' ').trim().match(/\\?.|^$/g).reduce((p, c) => {
if(c === '"'){
p.quote ^= 1;
}else if(!p.quote && c === ' '){
p.a.push('');
}else{
p.a[p.a.length-1] += c.replace(/\\(.)/,"$1");
}
return p;
}, {a: ['']}).a
};
s.createFFmpegMap = function(e,arrayOfMaps){
//`e` is the monitor object
var string = '';
if(e.details.input_maps && e.details.input_maps.length > 0){
if(arrayOfMaps && arrayOfMaps instanceof Array && arrayOfMaps.length>0){
arrayOfMaps.forEach(function(v){
if(v.map==='')v.map='0'
string += ' -map '+v.map
})
}else{
string += ' -map 0:0'
}
}
return string;
}
s.createInputMap = function(e,number,input){
//`e` is the monitor object
//`x` is an object used to contain temporary values.
var x = {}
x.cust_input = ''
x.hwaccel = ''
if(input.cust_input&&input.cust_input!==''){x.cust_input+=' '+input.cust_input}
//input - analyze duration
if(input.aduration&&input.aduration!==''){x.cust_input+=' -analyzeduration '+input.aduration}
//input - probe size
if(input.probesize&&input.probesize!==''){x.cust_input+=' -probesize '+input.probesize}
//input - stream loop (good for static files/lists)
if(input.stream_loop === '1'){x.cust_input+=' -stream_loop -1'}
//input - fps
if(x.cust_input.indexOf('-r ')===-1&&input.sfps&&input.sfps!==''){
input.sfps=parseFloat(input.sfps);
if(isNaN(input.sfps)){input.sfps=1}
x.cust_input+=' -r '+input.sfps
}
//input - is mjpeg
if(input.type==='mjpeg'){
if(x.cust_input.indexOf('-f ')===-1){
x.cust_input+=' -f mjpeg'
}
//input - frames per second
x.cust_input+=' -reconnect 1'
}else
//input - is h264 has rtsp in address and transport method is chosen
if((input.type==='h264'||input.type==='mp4')&&input.fulladdress.indexOf('rtsp://')>-1&&input.rtsp_transport!==''&&input.rtsp_transport!=='no'){
x.cust_input += ' -rtsp_transport '+input.rtsp_transport
}else
if((input.type==='mp4'||input.type==='mjpeg')&&x.cust_input.indexOf('-re')===-1){
x.cust_input += ' -re'
}
//hardware acceleration
if(input.accelerator&&input.accelerator==='1'){
if(input.hwaccel&&input.hwaccel!==''){
x.hwaccel+=' -hwaccel '+input.hwaccel;
}
if(input.hwaccel_vcodec&&input.hwaccel_vcodec!==''&&input.hwaccel_vcodec!=='auto'&&input.hwaccel_vcodec!=='no'){
x.hwaccel+=' -c:v '+input.hwaccel_vcodec;
}
if(input.hwaccel_device&&input.hwaccel_device!==''){
switch(input.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+input.hwaccel_device+' -hwaccel_output_format vaapi';
break;
default:
x.hwaccel+=' -hwaccel_device '+input.hwaccel_device;
break;
}
}
}
//custom - input flags
return x.hwaccel+x.cust_input+' -i "'+input.fulladdress+'"';
}
//create sub stream channel
s.createStreamChannel = function(e,number,channel){
//`e` is the monitor object
//`x` is an object used to contain temporary values.
var x = {
pipe:''
}
if(!number||number==''){
x.channel_sdir = e.sdir;
}else{
x.channel_sdir = e.sdir+'channel'+number+'/';
if (!fs.existsSync(x.channel_sdir)){
fs.mkdirSync(x.channel_sdir);
}
}
x.stream_video_filters=[]
//stream - frames per second
if(channel.stream_vcodec!=='copy'){
if(!channel.stream_fps||channel.stream_fps===''){
switch(channel.stream_type){
case'rtmp':
channel.stream_fps=30
break;
default:
// channel.stream_fps=5
break;
}
}
}
if(channel.stream_fps&&channel.stream_fps!==''){x.stream_fps=' -r '+channel.stream_fps}else{x.stream_fps=''}
//stream - hls vcodec
if(channel.stream_vcodec&&channel.stream_vcodec!=='no'){
if(channel.stream_vcodec!==''){x.stream_vcodec=' -c:v '+channel.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'}
}else{
x.stream_vcodec='';
}
//stream - hls acodec
if(channel.stream_acodec!=='no'){
if(channel.stream_acodec&&channel.stream_acodec!==''){x.stream_acodec=' -c:a '+channel.stream_acodec}else{x.stream_acodec=''}
}else{
x.stream_acodec=' -an';
}
//stream - resolution
if(channel.stream_scale_x&&channel.stream_scale_x!==''&&channel.stream_scale_y&&channel.stream_scale_y!==''){
x.dimensions = channel.stream_scale_x+'x'+channel.stream_scale_y;
}
//stream - hls segment time
if(channel.hls_time&&channel.hls_time!==''){x.hls_time=channel.hls_time}else{x.hls_time="2"}
//hls list size
if(channel.hls_list_size&&channel.hls_list_size!==''){x.hls_list_size=channel.hls_list_size}else{x.hls_list_size=2}
//stream - custom flags
if(channel.cust_stream&&channel.cust_stream!==''){x.cust_stream=' '+channel.cust_stream}else{x.cust_stream=''}
//stream - preset
if(channel.stream_type !== 'h265' && channel.preset_stream && channel.preset_stream!==''){x.preset_stream=' -preset '+channel.preset_stream;}else{x.preset_stream=''}
//hardware acceleration
if(e.details.accelerator&&e.details.accelerator==='1'){
if(e.details.hwaccel === 'auto')e.details.hwaccel = ''
if(e.details.hwaccel && e.details.hwaccel!==''){
x.hwaccel+=' -hwaccel '+e.details.hwaccel;
}
if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){
x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec;
}
if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){
switch(e.details.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device+' -hwaccel_output_format vaapi';
break;
default:
x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device;
break;
}
}
// else{
// if(e.details.hwaccel==='vaapi'){
// x.hwaccel+=' -hwaccel_device 0';
// }
// }
}
if(channel.rotate_stream&&channel.rotate_stream!==""&&channel.rotate_stream!=="no"){
x.stream_video_filters.push('transpose='+channel.rotate_stream);
}
//stream - video filter
if(channel.svf&&channel.svf!==''){
x.stream_video_filters.push(channel.svf)
}
if(x.stream_video_filters.length>0){
var string = x.stream_video_filters.join(',').trim()
if(string===''){
x.stream_video_filters=''
}else{
x.stream_video_filters=' -vf '+string
}
}else{
x.stream_video_filters=''
}
if(e.details.input_map_choices&&e.details.input_map_choices.record){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices['stream_channel-'+(number-config.pipeAddition)])
}
if(channel.stream_vcodec !== 'copy' || channel.stream_type === 'mjpeg' || channel.stream_type === 'b64'){
x.cust_stream += x.stream_fps
}
switch(channel.stream_type){
case'mp4':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1'
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'rtmp':
x.rtmp_server_url=s.checkCorrectPathEnding(channel.rtmp_server_url);
if(channel.stream_vcodec!=='copy'){
if(channel.stream_vcodec==='libx264'){
channel.stream_vcodec = 'h264'
}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
if(channel.stream_v_br&&channel.stream_v_br!==''){x.cust_stream+=' -b:v '+channel.stream_v_br}
}
if(channel.stream_vcodec!=='no'&&channel.stream_vcodec!==''){
x.cust_stream+=' -vcodec '+channel.stream_vcodec
}
if(channel.stream_acodec!=='copy'){
if(!channel.stream_acodec||channel.stream_acodec===''||channel.stream_acodec==='no'){
channel.stream_acodec = 'aac'
}
if(!channel.stream_a_br||channel.stream_a_br===''){channel.stream_a_br='128k'}
x.cust_stream+=' -ab '+channel.stream_a_br
}
if(channel.stream_acodec!==''){
x.cust_stream+=' -acodec '+channel.stream_acodec
}
x.pipe+=' -f flv'+x.stream_video_filters+x.cust_stream+' "'+x.rtmp_server_url+channel.rtmp_stream_key+'"';
break;
case'h264':
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mpegts'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'flv':
if(channel.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:'+number;
break;
case'hls':
if(channel.stream_vcodec!=='h264_vaapi'&&channel.stream_vcodec!=='copy'){
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -crf '+channel.stream_quality;
if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'}
if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'}
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.cust_stream+=x.stream_video_filters
}
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+x.channel_sdir+'s.m3u8"';
break;
case'mjpeg':
if(channel.stream_quality && channel.stream_quality !== '')x.cust_stream+=' -q:v '+channel.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:'+number;
break;
default:
x.pipe=''
break;
}
return x.pipe
}
ffmpeg.buildMainInput = function(e,x){
//e = monitor object
//x = temporary values
//check if CUDA is enabled
e.isStreamer = (e.type === 'dashcam'|| e.type === 'socket')
if(e.details.accelerator === '1' && e.details.hwaccel === 'cuvid' && e.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')){
e.cudaEnabled = true
}
//
x.hwaccel = ''
x.cust_input = ''
//input - frame rate (capture rate)
if(e.details.sfps && e.details.sfps!==''){x.input_fps=' -r '+e.details.sfps}else{x.input_fps=''}
//input - analyze duration
if(e.details.aduration&&e.details.aduration!==''){x.cust_input+=' -analyzeduration '+e.details.aduration};
//input - probe size
if(e.details.probesize&&e.details.probesize!==''){x.cust_input+=' -probesize '+e.details.probesize};
//input - stream loop (good for static files/lists)
if(e.details.stream_loop === '1' && (e.type === 'mp4' || e.type === 'local')){x.cust_input+=' -stream_loop -1'};
//input
if(e.details.cust_input.indexOf('-fflags') === -1){x.cust_input+=' -fflags +igndts'}
switch(e.type){
case'h264':
switch(e.protocol){
case'rtsp':
if(e.details.rtsp_transport&&e.details.rtsp_transport!==''&&e.details.rtsp_transport!=='no'){x.cust_input+=' -rtsp_transport '+e.details.rtsp_transport;}
break;
}
break;
}
//hardware acceleration
if(e.details.accelerator && e.details.accelerator==='1' && e.isStreamer === false){
if(e.details.hwaccel&&e.details.hwaccel!==''){
x.hwaccel+=' -hwaccel '+e.details.hwaccel;
}
if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){
x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec;
}
if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){
switch(e.details.hwaccel){
case'vaapi':
x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device;
break;
default:
x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device;
break;
}
}
// else{
// if(e.details.hwaccel==='vaapi'){
// x.hwaccel+=' -hwaccel_device 0';
// }
// }
}
//logging - level
if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'}
//custom - input flags
if(e.details.cust_input&&e.details.cust_input!==''){x.cust_input+=' '+e.details.cust_input;}
//add main input
if((e.type === 'mp4' || e.type === 'mjpeg') && x.cust_input.indexOf('-re') === -1){
x.cust_input += ' -re'
}
}
ffmpeg.buildMainStream = function(e,x){
//e = monitor object
//x = temporary values
x.stream_video_filters = []
x.pipe = ''
//stream - timestamp
if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.stream_timestamp_font&&e.details.stream_timestamp_font!==''){x.stream_timestamp_font=e.details.stream_timestamp_font}else{x.stream_timestamp_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.stream_timestamp_x&&e.details.stream_timestamp_x!==''){x.stream_timestamp_x=e.details.stream_timestamp_x}else{x.stream_timestamp_x='(w-tw)/2'}
//position y
if(e.details.stream_timestamp_y&&e.details.stream_timestamp_y!==''){x.stream_timestamp_y=e.details.stream_timestamp_y}else{x.stream_timestamp_y='0'}
//text color
if(e.details.stream_timestamp_color&&e.details.stream_timestamp_color!==''){x.stream_timestamp_color=e.details.stream_timestamp_color}else{x.stream_timestamp_color='white'}
//box color
if(e.details.stream_timestamp_box_color&&e.details.stream_timestamp_box_color!==''){x.stream_timestamp_box_color=e.details.stream_timestamp_box_color}else{x.stream_timestamp_box_color='0x00000000@1'}
//text size
if(e.details.stream_timestamp_font_size&&e.details.stream_timestamp_font_size!==''){x.stream_timestamp_font_size=e.details.stream_timestamp_font_size}else{x.stream_timestamp_font_size='10'}
x.stream_video_filters.push('drawtext=fontfile='+x.stream_timestamp_font+':text=\'%{localtime}\':x='+x.stream_timestamp_x+':y='+x.stream_timestamp_y+':fontcolor='+x.stream_timestamp_color+':box=1:boxcolor='+x.stream_timestamp_box_color+':fontsize='+x.stream_timestamp_font_size);
}
//stream - watermark for -vf
if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){
switch(e.details.stream_watermark_position){
case'tl'://top left
x.stream_watermark_position='10:10'
break;
case'tr'://top right
x.stream_watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.stream_watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]');
}
//stream - rotation
if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"&&e.details.stream_vcodec!=='copy'){
x.stream_video_filters.push('transpose='+e.details.rotate_stream);
}
//stream - hls vcodec
if(e.details.stream_vcodec&&e.details.stream_vcodec!=='no'){
if(e.details.stream_vcodec!==''){x.stream_vcodec=' -c:v '+e.details.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'}
}else{
x.stream_vcodec='';
}
//stream - hls acodec
if(e.details.stream_acodec!=='no'){
if(e.details.stream_acodec&&e.details.stream_acodec!==''){x.stream_acodec=' -c:a '+e.details.stream_acodec}else{x.stream_acodec=''}
}else{
x.stream_acodec=' -an';
}
//stream - hls segment time
if(e.details.hls_time&&e.details.hls_time!==''){x.hls_time=e.details.hls_time}else{x.hls_time="2"} //hls list size
if(e.details.hls_list_size&&e.details.hls_list_size!==''){x.hls_list_size=e.details.hls_list_size}else{x.hls_list_size=2}
//stream - custom flags
if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''}
//stream - preset
if(e.details.stream_type !== 'h265' && e.details.preset_stream && e.details.preset_stream !== ''){x.preset_stream=' -preset '+e.details.preset_stream;}else{x.preset_stream=''}
if(e.details.stream_vcodec==='h264_vaapi'){
x.stream_video_filters=[]
x.stream_video_filters.push('format=nv12,hwupload');
if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
x.stream_video_filters.push('scale_vaapi=w='+e.details.stream_scale_x+':h='+e.details.stream_scale_y)
}
}
if(e.cudaEnabled && (e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64')){
x.stream_video_filters.push('hwdownload,format=nv12')
}
//stream - video filter
if(e.details.svf && e.details.svf !== ''){
x.stream_video_filters.push(e.details.svf)
}
if(x.stream_video_filters.length>0){
x.stream_video_filters=' -vf "'+x.stream_video_filters.join(',')+'"'
}else{
x.stream_video_filters=''
}
//stream - pipe build
if(e.details.input_map_choices&&e.details.input_map_choices.stream){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.stream)
}
if(e.details.stream_vcodec !== 'copy' || e.details.stream_type === 'mjpeg' || e.details.stream_type === 'b64'){
x.cust_stream += x.stream_fps
}
switch(e.details.stream_type){
case'mp4':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream" -reset_timestamps 1'
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f mp4'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'flv':
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f flv'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'hls':
if(e.details.stream_vcodec!=='h264_vaapi'&&e.details.stream_vcodec!=='copy'){
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'}
if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'}
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.cust_stream+=x.stream_video_filters
}
x.pipe+=x.preset_stream+x.stream_acodec+x.stream_vcodec+' -f hls'+x.cust_stream+' -hls_time '+x.hls_time+' -hls_list_size '+x.hls_list_size+' -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'s.m3u8"';
break;
case'mjpeg':
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
case'h265':
x.cust_stream+=' -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1'
if(e.details.stream_vcodec!=='copy'){
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -crf '+e.details.stream_quality;
x.cust_stream+=x.preset_stream
x.cust_stream+=x.stream_video_filters
}
x.pipe+=' -f hevc'+x.stream_acodec+x.stream_vcodec+x.cust_stream+' pipe:1';
break;
case'b64':case'':case undefined:case null://base64
if(e.details.stream_quality && e.details.stream_quality !== '')x.cust_stream+=' -q:v '+e.details.stream_quality;
if(x.dimensions && x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.dimensions}
x.pipe+=' -an -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+' pipe:1';
break;
default:
x.pipe=''
break;
}
if(e.details.stream_channels){
e.details.stream_channels.forEach(function(v,n){
x.pipe += s.createStreamChannel(e,n+config.pipeAddition,v)
})
}
//api - snapshot bin/ cgi.bin (JPEG Mode)
if(e.details.snap === '1'){
if(e.details.input_map_choices&&e.details.input_map_choices.snap){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.snap)
}
if(!e.details.snap_fps || e.details.snap_fps === ''){e.details.snap_fps = 1}
if(e.details.snap_vf && e.details.snap_vf !== '' || e.cudaEnabled){
var snapVf = e.details.snap_vf.split(',')
if(e.details.snap_vf === '')snapVf.shift()
if(e.cudaEnabled){
snapVf.push('hwdownload,format=nv12')
}
//-vf "thumbnail_cuda=2,hwdownload,format=nv12"
x.snap_vf=' -vf "'+snapVf.join(',')+'"'
}else{
x.snap_vf=''
}
if(e.details.snap_scale_x && e.details.snap_scale_x !== '' && e.details.snap_scale_y && e.details.snap_scale_y !== ''){x.snap_ratio = ' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.snap_ratio=''}
if(e.details.cust_snap && e.details.cust_snap !== ''){x.cust_snap = ' '+e.details.cust_snap}else{x.cust_snap=''}
x.pipe+=' -update 1 -r '+e.details.snap_fps+x.cust_snap+x.snap_ratio+x.snap_vf+' "'+e.sdir+'s.jpg" -y';
}
//custom - output
if(e.details.custom_output&&e.details.custom_output!==''){x.pipe+=' '+e.details.custom_output;}
}
ffmpeg.buildMainRecording = function(e,x){
//e = monitor object
//x = temporary values
x.record_video_filters = []
x.record_string = ''
//record - resolution
if(e.width!==''&&e.height!==''&&!isNaN(e.width)&&!isNaN(e.height)){
x.record_dimensions=' -s '+e.width+'x'+e.height
}else{
x.record_dimensions=''
}
if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
x.dimensions = e.details.stream_scale_x+'x'+e.details.stream_scale_y;
}
//record - segmenting
x.segment=' -f segment -segment_atclocktime 1 -reset_timestamps 1 -strftime 1 -segment_list pipe:2 -segment_time '+(60*e.cutoff)+' "'+e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext+'"';
//record - set defaults for extension, video quality
switch(e.ext){
case'mp4':
x.vcodec='libx264';x.acodec='aac';
if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -crf '+e.details.crf}
break;
case'webm':
x.acodec='libvorbis',x.vcodec='libvpx';
if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -q:v '+e.details.crf}else{x.vcodec+=' -q:v 1';}
break;
}
if(e.details.vcodec==='h264_vaapi'){
x.record_video_filters.push('format=nv12,hwupload');
}
//record - use custom video codec
if(e.details.vcodec&&e.details.vcodec!==''&&e.details.vcodec!=='default'){x.vcodec=e.details.vcodec}
//record - use custom audio codec
if(e.details.acodec&&e.details.acodec!==''&&e.details.acodec!=='default'){x.acodec=e.details.acodec}
if(e.details.cust_record){
if(x.acodec=='aac'&&e.details.cust_record.indexOf('-strict -2')===-1){e.details.cust_record+=' -strict -2';}
if(e.details.cust_record.indexOf('-threads')===-1){e.details.cust_record+=' -threads 1';}
}
// if(e.details.cust_input&&(e.details.cust_input.indexOf('-use_wallclock_as_timestamps 1')>-1)===false){e.details.cust_input+=' -use_wallclock_as_timestamps 1';}
//record - ready or reset codecs
if(x.acodec!=='no'){
if(x.acodec.indexOf('none')>-1){x.acodec=''}else{x.acodec=' -acodec '+x.acodec}
}else{
x.acodec=' -an'
}
if(x.vcodec.indexOf('none')>-1){x.vcodec=''}else{x.vcodec=' -vcodec '+x.vcodec}
//record - frames per second (fps)
if(e.fps&&e.fps!==''&&e.details.vcodec!=='copy'){x.record_fps=' -r '+e.fps}else{x.record_fps=''}
//stream - frames per second (fps)
if(e.details.stream_fps&&e.details.stream_fps!==''){x.stream_fps=' -r '+e.details.stream_fps}else{x.stream_fps=''}
//record - timestamp options for -vf
if(e.details.timestamp&&e.details.timestamp=="1"&&e.details.vcodec!=='copy'){
//font
if(e.details.timestamp_font&&e.details.timestamp_font!==''){x.time_font=e.details.timestamp_font}else{x.time_font='/usr/share/fonts/truetype/freefont/FreeSans.ttf'}
//position x
if(e.details.timestamp_x&&e.details.timestamp_x!==''){x.timex=e.details.timestamp_x}else{x.timex='(w-tw)/2'}
//position y
if(e.details.timestamp_y&&e.details.timestamp_y!==''){x.timey=e.details.timestamp_y}else{x.timey='0'}
//text color
if(e.details.timestamp_color&&e.details.timestamp_color!==''){x.time_color=e.details.timestamp_color}else{x.time_color='white'}
//box color
if(e.details.timestamp_box_color&&e.details.timestamp_box_color!==''){x.time_box_color=e.details.timestamp_box_color}else{x.time_box_color='0x00000000@1'}
//text size
if(e.details.timestamp_font_size&&e.details.timestamp_font_size!==''){x.time_font_size=e.details.timestamp_font_size}else{x.time_font_size='10'}
x.record_video_filters.push('drawtext=fontfile='+x.time_font+':text=\'%{localtime}\':x='+x.timex+':y='+x.timey+':fontcolor='+x.time_color+':box=1:boxcolor='+x.time_box_color+':fontsize='+x.time_font_size);
}
//record - watermark for -vf
if(e.details.watermark&&e.details.watermark=="1"&&e.details.watermark_location&&e.details.watermark_location!==''){
switch(e.details.watermark_position){
case'tl'://top left
x.watermark_position='10:10'
break;
case'tr'://top right
x.watermark_position='main_w-overlay_w-10:10'
break;
case'bl'://bottom left
x.watermark_position='10:main_h-overlay_h-10'
break;
default://bottom right
x.watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
break;
}
x.record_video_filters.push('movie='+e.details.watermark_location+'[watermark],[in][watermark]overlay='+x.watermark_position+'[out]');
}
//record - rotation
if(e.details.rotate_record&&e.details.rotate_record!==""&&e.details.rotate_record!=="no"&&e.details.stream_vcodec!=="copy"){
x.record_video_filters.push('transpose='+e.details.rotate_record);
}
//check custom record filters for -vf
if(e.details.vf&&e.details.vf!==''){
x.record_video_filters.push(e.details.vf)
}
//compile filter string for -vf
if(x.record_video_filters.length>0){
x.record_video_filters=' -vf '+x.record_video_filters.join(',')
}else{
x.record_video_filters=''
}
//build record string.
if(e.mode === 'record'){
if(e.details.input_map_choices&&e.details.input_map_choices.record){
//add input feed map
x.record_string += s.createFFmpegMap(e,e.details.input_map_choices.record)
}
//if h264, hls, mp4, or local add the audio codec flag
switch(e.type){
case'h264':case'hls':case'mp4':case'local':
x.record_string+=x.acodec;
break;
}
//custom flags
if(e.details.cust_record&&e.details.cust_record!==''){x.record_string+=' '+e.details.cust_record;}
//preset flag
if(e.details.preset_record&&e.details.preset_record!==''){x.record_string+=' -preset '+e.details.preset_record;}
//main string write
x.record_string+=x.vcodec+x.record_fps+x.record_video_filters+x.record_dimensions+x.segment;
}
}
ffmpeg.buildMainDetector = function(e,x){
//e = monitor object
//x = temporary values
x.cust_detect = ' '
//detector - plugins, motion
if(e.details.detector === '1' && e.details.detector_send_frames === '1'){
if(e.details.input_map_choices&&e.details.input_map_choices.detector){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
}
if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2}
if(e.details.detector_scale_x&&e.details.detector_scale_x!==''&&e.details.detector_scale_y&&e.details.detector_scale_y!==''){x.dratio=' -s '+e.details.detector_scale_x+'x'+e.details.detector_scale_y}else{x.dratio=' -s 320x240'}
if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
x.detector_vf = ['fps='+e.details.detector_fps]
if(e.cudaEnabled){
x.detector_vf.push('hwdownload,format=nv12')
}
x.detector_vf = '-vf "'+x.detector_vf.join(',')+'"'
if(e.details.detector_pam==='1'){
if(e.cudaEnabled){
x.pipe += ' -vf "hwdownload,format=nv12"'
}
x.pipe+=' -an -c:v pam -pix_fmt gray -f image2pipe -r '+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:3'
if(e.details.detector_use_detect_object === '1'){
//for object detection
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector)
x.pipe += ' -f singlejpeg '+x.detector_vf+x.cust_detect+x.dratio+' pipe:4';
}
}else{
x.pipe+=' -f image2pipe '+x.detector_vf+x.cust_detect+x.dratio+' pipe:3';
}
}
//Traditional Recording Buffer
if(e.details.detector=='1'&&e.details.detector_trigger=='1'&&e.details.detector_record_method==='sip'){
if(e.details.input_map_choices&&e.details.input_map_choices.detector_sip_buffer){
//add input feed map
x.pipe += s.createFFmpegMap(e,e.details.input_map_choices.detector_sip_buffer)
}
x.detector_buffer_filters=[]
if(!e.details.detector_buffer_vcodec||e.details.detector_buffer_vcodec===''||e.details.detector_buffer_vcodec==='auto'){
switch(e.type){
case'h264':case'hls':case'mp4':
e.details.detector_buffer_vcodec = 'copy'
break;
default:
if(e.details.accelerator === '1' && e.cudaEnabled){
e.details.detector_buffer_vcodec = 'h264_nvenc'
}else{
e.details.detector_buffer_vcodec = 'libx264'
}
break;
}
}
if(!e.details.detector_buffer_acodec||e.details.detector_buffer_acodec===''||e.details.detector_buffer_acodec==='auto'){
switch(e.type){
case'mjpeg':case'jpeg':case'socket':
e.details.detector_buffer_acodec = 'no'
break;
case'h264':case'hls':case'mp4':
e.details.detector_buffer_acodec = 'copy'
break;
default:
e.details.detector_buffer_acodec = 'aac'
break;
}
}
if(e.details.detector_buffer_acodec === 'no'){
x.detector_buffer_acodec = ' -an'
}else{
x.detector_buffer_acodec = ' -c:a '+e.details.detector_buffer_acodec
}
if(!e.details.detector_buffer_tune||e.details.detector_buffer_tune===''){e.details.detector_buffer_tune='zerolatency'}
if(!e.details.detector_buffer_g||e.details.detector_buffer_g===''){e.details.detector_buffer_g='1'}
if(!e.details.detector_buffer_hls_time||e.details.detector_buffer_hls_time===''){e.details.detector_buffer_hls_time='2'}
if(!e.details.detector_buffer_hls_list_size||e.details.detector_buffer_hls_list_size===''){e.details.detector_buffer_hls_list_size='4'}
if(!e.details.detector_buffer_start_number||e.details.detector_buffer_start_number===''){e.details.detector_buffer_start_number='0'}
if(!e.details.detector_buffer_live_start_index||e.details.detector_buffer_live_start_index===''){e.details.detector_buffer_live_start_index='-3'}
if(e.details.detector_buffer_vcodec.indexOf('_vaapi')>-1){
if(x.hwaccel.indexOf('-vaapi_device')>-1){
x.detector_buffer_filters.push('format=nv12')
x.detector_buffer_filters.push('hwupload')
}else{
e.details.detector_buffer_vcodec='libx264'
}
}
if(e.details.detector_buffer_vcodec!=='copy'){
if(e.details.detector_buffer_fps&&e.details.detector_buffer_fps!==''){
x.detector_buffer_fps=' -r '+e.details.detector_buffer_fps
}else{
x.detector_buffer_fps=' -r 30'
}
}else{
x.detector_buffer_fps=''
}
if(x.detector_buffer_filters.length>0){
x.pipe+=' -vf '+x.detector_buffer_filters.join(',')
}
x.pipe+=x.detector_buffer_fps+x.detector_buffer_acodec+' -c:v '+e.details.detector_buffer_vcodec+' -f hls -tune '+e.details.detector_buffer_tune+' -g '+e.details.detector_buffer_g+' -hls_time '+e.details.detector_buffer_hls_time+' -hls_list_size '+e.details.detector_buffer_hls_list_size+' -start_number '+e.details.detector_buffer_start_number+' -live_start_index '+e.details.detector_buffer_live_start_index+' -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "'+e.sdir+'detectorStream.m3u8"'
}
}
ffmpeg.assembleMainPieces = function(e,x){
//create executeable FFMPEG command
x.ffmpegCommandString = x.loglevel+x.input_fps;
//progress pipe
x.ffmpegCommandString += ' -progress pipe:5';
switch(e.type){
case'dashcam':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i -';
break;
case'socket':case'jpeg':case'pipe'://case'webpage':
x.ffmpegCommandString += ' -pattern_type glob -f image2pipe'+x.record_fps+' -vcodec mjpeg'+x.cust_input+x.hwaccel+' -i -';
break;
case'mjpeg':
x.ffmpegCommandString += ' -reconnect 1 -f mjpeg'+x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;
case'h264':case'hls':case'mp4':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.url+'"';
break;
case'local':
x.ffmpegCommandString += x.cust_input+x.hwaccel+' -i "'+e.path+'"';
break;
}
//add extra input maps
if(e.details.input_maps){
e.details.input_maps.forEach(function(v,n){
x.ffmpegCommandString += s.createInputMap(e,n+1,v)
})
}
//add recording and stream outputs
x.ffmpegCommandString += x.record_string+x.pipe
}
ffmpeg.createPipeArray = function(e,x){
//create additional pipes from ffmpeg
x.stdioPipes = [];
var times = config.pipeAddition;
if(e.details.stream_channels){
times+=e.details.stream_channels.length
}
for(var i=0; i < times; i++){
x.stdioPipes.push('pipe')
}
}
s.ffmpeg = function(e){
//set X for temporary values so we don't break our main monitor object.
var x = {tmp : ''}
//set some placeholding values to avoid "undefined" in ffmpeg string.
ffmpeg.buildMainInput(e,x)
ffmpeg.buildMainStream(e,x)
ffmpeg.buildMainRecording(e,x)
ffmpeg.buildMainDetector(e,x)
ffmpeg.assembleMainPieces(e,x)
ffmpeg.createPipeArray(e,x)
//hold ffmpeg command for log stream
s.group[e.ke].mon[e.mid].ffmpeg = x.ffmpegCommandString
//clean the string of spatial impurities and split for spawn()
x.ffmpegCommandString = s.splitForFFPMEG(x.ffmpegCommandString)
//launch that bad boy
return spawn(config.ffmpegDir,x.ffmpegCommandString,{detached: true,stdio:x.stdioPipes})
}
if(!config.ffmpegDir){
ffmpeg.checkForWindows(function(){
ffmpeg.checkForUnix(function(){
ffmpeg.checkForFfbinary(function(){
ffmpeg.checkForNpmStatic(function(){
console.log('No FFmpeg found.')
})
})
})
})
}
if(downloadingFfmpeg === false){
//not downloading ffmpeg
ffmpeg.completeCheck()
}
return ffmpeg
}

49
libs/folders.js Normal file
View file

@ -0,0 +1,49 @@
var fs = require('fs');
module.exports = function(s,config){
//directories
s.group={};
if(!config.windowsTempDir&&s.isWin===true){config.windowsTempDir='C:/Windows/Temp'}
if(!config.defaultMjpeg){config.defaultMjpeg=s.mainDirectory+'/web/libs/img/bg.jpg'}
//default stream folder check
if(!config.streamDir){
if(s.isWin===false){
config.streamDir='/dev/shm'
}else{
config.streamDir=config.windowsTempDir
}
if(!fs.existsSync(config.streamDir)){
config.streamDir=s.mainDirectory+'/streams/'
}else{
config.streamDir+='/streams/'
}
}
if(!config.videosDir){config.videosDir=s.mainDirectory+'/videos/'}
if(!config.binDir){config.binDir=s.mainDirectory+'/fileBin/'}
if(!config.addStorage){config.addStorage=[]}
s.dir={
videos:s.checkCorrectPathEnding(config.videosDir),
streams:s.checkCorrectPathEnding(config.streamDir),
fileBin:s.checkCorrectPathEnding(config.binDir),
addStorage:config.addStorage,
languages:s.location.languages+'/'
};
//streams dir
if(!fs.existsSync(s.dir.streams)){
fs.mkdirSync(s.dir.streams);
}
//videos dir
if(!fs.existsSync(s.dir.videos)){
fs.mkdirSync(s.dir.videos);
}
//fileBin dir
if(!fs.existsSync(s.dir.fileBin)){
fs.mkdirSync(s.dir.fileBin);
}
//additional storage areas
s.dir.addStorage.forEach(function(v,n){
v.path=s.checkCorrectPathEnding(v.path)
if(!fs.existsSync(v.path)){
fs.mkdirSync(v.path);
}
})
}

76
libs/health.js Normal file
View file

@ -0,0 +1,76 @@
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
module.exports = function(s,config,lang,io){
s.sendDiskUsedAmountToClients = function(e){
//send the amount used disk space to connected users
if(s.group[e.ke]&&s.group[e.ke].init){
s.tx({f:'diskUsed',size:s.group[e.ke].usedSpace,limit:s.group[e.ke].sizeLimit},'GRP_'+e.ke);
}
}
s.heartBeat = function(){
setTimeout(s.heartBeat, 8000);
io.sockets.emit('ping',{beat:1});
}
s.heartBeat()
s.cpuUsage = function(callback){
k={}
switch(s.platform){
case'win32':
k.cmd="@for /f \"skip=1\" %p in ('wmic cpu get loadpercentage') do @echo %p%"
break;
case'darwin':
k.cmd="ps -A -o %cpu | awk '{s+=$1} END {print s}'";
break;
case'linux':
k.cmd='LANG=C top -b -n 2 | grep "^'+config.cpuUsageMarker+'" | awk \'{print $2}\' | tail -n1';
break;
case'freebsd':
k.cmd='vmstat 1 2 | tail -1 | awk \'{print $17}\''
break;
}
if(config.customCpuCommand){
exec(config.customCpuCommand,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true) {
d = d.replace(/(\r\n|\n|\r)/gm, "").replace(/%/g, "")
}
callback(d)
});
} else if(k.cmd){
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true){
d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"")
}
callback(d)
});
} else {
callback(0)
}
}
s.ramUsage = function(callback){
k={}
switch(s.platform){
case'win32':
k.cmd = "wmic OS get FreePhysicalMemory /Value"
break;
case'darwin':
k.cmd = "vm_stat | awk '/^Pages free: /{f=substr($3,1,length($3)-1)} /^Pages active: /{a=substr($3,1,length($3-1))} /^Pages inactive: /{i=substr($3,1,length($3-1))} /^Pages speculative: /{s=substr($3,1,length($3-1))} /^Pages wired down: /{w=substr($4,1,length($4-1))} /^Pages occupied by compressor: /{c=substr($5,1,length($5-1)); print ((a+w)/(f+a+i+w+s+c))*100;}'"
break;
case'freebsd':
k.cmd = "echo \"scale=4; $(vmstat -H | tail -1 | awk '{print $5}')*1024*100/$(sysctl hw.physmem | awk '{print $2}')\" | bc"
break;
default:
k.cmd = "LANG=C free | grep Mem | awk '{print $7/$2 * 100.0}'";
break;
}
if(k.cmd){
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true){
d=(parseInt(d.split('=')[1])/(s.totalmem/1000))*100
}
callback(d)
});
}else{
callback(0)
}
}
}

61
libs/language.js Normal file
View file

@ -0,0 +1,61 @@
module.exports = function(s,config){
if(!config.language){
config.language='en_CA'
}
try{
var lang = require(s.location.languages+'/'+config.language+'.json');
}catch(er){
console.error(er)
console.log('There was an error loading your language file.')
var lang = require(s.location.languages+'/en_CA.json');
}
s.location.definitions = s.mainDirectory+'/definitions'
try{
var definitions = require(s.location.definitions+'/'+config.language+'.json');
}catch(er){
console.error(er)
console.log('There was an error loading your language file.')
var definitions = require(s.location.definitions+'/en_CA.json');
}
//load languages dynamically
s.loadedLanguages={}
s.loadedLanguages[config.language]=lang;
s.getLanguageFile = function(rule){
if(rule && rule !== ''){
var file = s.loadedLanguages[file]
if(!file){
try{
s.loadedLanguages[rule] = require(s.location.languages+'/'+rule+'.json')
s.loadedLanguages[rule] = Object.assign(lang,s.loadedLanguages[rule])
file = s.loadedLanguages[rule]
}catch(err){
file = lang
}
}
}else{
file = lang
}
return file
}
//load defintions dynamically
s.loadedDefinitons={}
s.loadedDefinitons[config.language]=definitions;
s.getDefinitonFile = function(rule){
if(rule && rule !== ''){
var file = s.loadedDefinitons[file]
if(!file){
try{
s.loadedDefinitons[rule] = require(s.location.definitions+'/'+rule+'.json')
s.loadedDefinitons[rule] = Object.assign(definitions,s.loadedDefinitons[rule])
file = s.loadedDefinitons[rule]
}catch(err){
file = definitions
}
}
}else{
file = definitions
}
return file
}
return lang
}

1321
libs/monitor.js Normal file

File diff suppressed because it is too large Load diff

318
libs/notification.js Normal file
View file

@ -0,0 +1,318 @@
var Discord = require("discord.js")
module.exports = function(s,config,lang){
//discord bot
if(config.discordBot === true){
try{
s.discordMsg = function(data,files,groupKey){
if(!data)data = {};
var bot = s.group[groupKey].discordBot
if(!bot){
s.userLog({ke:groupKey,mid:'$USER'},{type:lang.DiscordFailedText,msg:lang.DiscordNotEnabledText})
return
}
var sendBody = Object.assign({
color: 3447003,
title: 'Alert from Shinobi',
description: "",
fields: [],
timestamp: new Date(),
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},data)
bot.channels.get(s.group[groupKey].init.discordbot_channel).send({
embed: sendBody,
files: files
}).catch(err => {
if(err){
s.userLog({ke:groupKey,mid:'$USER'},{type:lang.DiscordErrorText,msg:err})
s.group[groupKey].discordBot = null
s.loadGroupApps({ke:groupKey})
}
})
}
var onEventTriggerBeforeFilterForDiscord = function(d,filter){
filter.discord = true
}
var onEventTriggerForDiscord = function(d,filter){
// d = event object
//discord bot
if(filter.discord && d.mon.details.detector_discordbot === '1' && !s.group[d.ke].mon[d.id].detector_discordbot){
var detector_discordbot_timeout
if(!d.mon.details.detector_discordbot_timeout||d.mon.details.detector_discordbot_timeout===''){
detector_discordbot_timeout = 1000*60*10;
}else{
detector_discordbot_timeout = parseFloat(d.mon.details.detector_discordbot_timeout)*1000*60;
}
//lock mailer so you don't get emailed on EVERY trigger event.
s.group[d.ke].mon[d.id].detector_discordbot=setTimeout(function(){
//unlock so you can mail again.
clearTimeout(s.group[d.ke].mon[d.id].detector_discordbot);
delete(s.group[d.ke].mon[d.id].detector_discordbot);
},detector_discordbot_timeout);
var files = []
var sendAlert = function(){
s.discordMsg({
author: {
name: s.group[d.ke].mon_conf[d.id].name,
icon_url: config.iconURL
},
title: lang.Event+' - '+d.screenshotName,
description: lang.EventText1+' '+d.currentTimestamp,
fields: [],
timestamp: d.currentTime,
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},files,d.ke)
}
if(d.mon.details.detector_discordbot_send_video === '1'){
s.mergeDetectorBufferChunks(d,function(mergedFilepath,filename){
s.discordMsg({
author: {
name: s.group[d.ke].mon_conf[d.id].name,
icon_url: config.iconURL
},
title: filename,
fields: [],
timestamp: d.currentTime,
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},[
{
attachment: mergedFilepath,
name: filename
}
],d.ke)
})
}
s.getRawSnapshotFromMonitor(d.mon,function(data){
if((data[data.length-2] === 0xFF && data[data.length-1] === 0xD9)){
d.screenshotBuffer = data
files.push({
attachment: d.screenshotBuffer,
name: d.screenshotName+'.jpg'
})
}
sendAlert()
})
}
}
var onTwoFactorAuthCodeNotificationForDiscord = function(r){
// r = user
if(r.details.factor_discord === '1'){
s.discordMsg({
author: {
name: r.lang['2-Factor Authentication'],
icon_url: config.iconURL
},
title: r.lang['Enter this code to proceed'],
description: '**'+s.factorAuth[r.ke][r.uid].key+'** '+r.lang.FactorAuthText1,
fields: [],
timestamp: new Date(),
footer: {
icon_url: config.iconURL,
text: "Shinobi Systems"
}
},[],r.ke)
}
}
var loadDiscordBotForUser = function(user){
ar=JSON.parse(user.details);
//discordbot
if(!s.group[user.ke].discordBot &&
config.discordBot === true &&
ar.discordbot === '1' &&
ar.discordbot_token !== ''
){
s.group[user.ke].discordBot = new Discord.Client()
s.group[user.ke].discordBot.on('ready', () => {
console.log(`${user.mail} : Discord Bot Logged in as ${s.group[user.ke].discordBot.user.tag}!`)
})
s.group[user.ke].discordBot.login(ar.discordbot_token)
}
}
var unloadDiscordBotForUser = function(user){
if(s.group[user.ke].discordBot && s.group[user.ke].discordBot.destroy){
s.group[user.ke].discordBot.destroy()
delete(s.group[user.ke].discordBot)
}
}
s.loadGroupAppExtender(loadDiscordBotForUser)
s.unloadGroupAppExtender(unloadDiscordBotForUser)
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForDiscord)
s.onEventTrigger(onEventTriggerForDiscord)
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForDiscord)
}catch(err){
console.log(err)
console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.')
s.discordMsg = function(){}
}
}
// mailing with nodemailer
try{
if(config.mail){
if(config.mail.from === undefined){config.mail.from = '"ShinobiCCTV" <no-reply@shinobi.video>'}
s.nodemailer = require('nodemailer').createTransport(config.mail);
}
var onDetectorNoTriggerTimeoutForEmail = function(e){
//e = monitor object
if(config.mail && e.details.detector_notrigger_mail === '1'){
s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(err,r){
r = r[0]
var mailOptions = {
from: config.mail.from, // sender address
to: r.mail, // list of receivers
subject: lang.NoMotionEmailText1+' '+e.name+' ('+e.id+')', // Subject line
html: '<i>'+lang.NoMotionEmailText2+' '+e.details.detector_notrigger_timeout+' '+lang.minutes+'.</i>',
}
mailOptions.html+='<div><b>'+lang['Monitor Name']+' </b> : '+e.name+'</div>'
mailOptions.html+='<div><b>'+lang['Monitor ID']+' </b> : '+e.id+'</div>'
s.nodemailer.sendMail(mailOptions, (error, info) => {
if (error) {
s.systemLog('detector:notrigger:sendMail',error)
s.tx({f:'error',ff:'detector_notrigger_mail',id:e.id,ke:e.ke,error:error},'GRP_'+e.ke);
return ;
}
s.tx({f:'detector_notrigger_mail',id:e.id,ke:e.ke,info:info},'GRP_'+e.ke);
})
})
}
}
var onTwoFactorAuthCodeNotificationForEmail = function(r){
// r = user object
if(r.details.factor_mail !== '0'){
var mailOptions = {
from: config.mail.from,
to: r.mail,
subject: r.lang['2-Factor Authentication'],
html: r.lang['Enter this code to proceed']+' <b>'+s.factorAuth[r.ke][r.uid].key+'</b>. '+r.lang.FactorAuthText1,
};
s.nodemailer.sendMail(mailOptions, (error, info) => {
if (error) {
s.systemLog(r.lang.MailError,error)
return
}
})
}
}
var onFilterEventForEmail = function(x,d){
// x = filter function
// d = filter event object
if(x === 'email'){
if(d.videos && d.videos.length > 0){
d.mailOptions = {
from: config.mail.from, // sender address
to: d.mail, // list of receivers
subject: lang['Filter Matches']+' : '+d.name, // Subject line
html: lang.FilterMatchesText1+' '+d.videos.length+' '+lang.FilterMatchesText2,
};
if(d.execute&&d.execute!==''){
d.mailOptions.html+='<div><b>'+lang.Executed+' :</b> '+d.execute+'</div>'
}
if(d.delete==='1'){
d.mailOptions.html+='<div><b>'+lang.Deleted+' :</b> '+lang.Yes+'</div>'
}
d.mailOptions.html+='<div><b>'+lang.Query+' :</b> '+d.query+'</div>'
d.mailOptions.html+='<div><b>'+lang['Filter ID']+' :</b> '+d.id+'</div>'
s.nodemailer.sendMail(d.mailOptions, (error, info) => {
if (error) {
s.tx({f:'error',ff:'filter_mail',ke:d.ke,error:error},'GRP_'+d.ke);
return ;
}
s.tx({f:'filter_mail',ke:d.ke,info:info},'GRP_'+d.ke);
})
}
}
}
var onEventTriggerBeforeFilterForEmail = function(d,filter){
filter.mail = true
}
var onEventTriggerForEmail = function(d,filter){
if(filter.mail && config.mail && !s.group[d.ke].mon[d.id].detector_mail && d.mon.details.detector_mail === '1'){
s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[d.ke,'%"sub"%'],function(err,r){
r=r[0];
var detector_mail_timeout
if(!d.mon.details.detector_mail_timeout||d.mon.details.detector_mail_timeout===''){
detector_mail_timeout = 1000*60*10;
}else{
detector_mail_timeout = parseFloat(d.mon.details.detector_mail_timeout)*1000*60;
}
//lock mailer so you don't get emailed on EVERY trigger event.
s.group[d.ke].mon[d.id].detector_mail=setTimeout(function(){
//unlock so you can mail again.
clearTimeout(s.group[d.ke].mon[d.id].detector_mail);
delete(s.group[d.ke].mon[d.id].detector_mail);
},detector_mail_timeout);
var files = []
var mailOptions = {
from: config.mail.from, // sender address
to: r.mail, // list of receivers
subject: lang.Event+' - '+d.screenshotName, // Subject line
html: '<i>'+lang.EventText1+' '+d.currentTimestamp+'.</i>',
attachments: files
}
var sendMail = function(){
Object.keys(d.details).forEach(function(v,n){
mailOptions.html+='<div><b>'+v+'</b> : '+d.details[v]+'</div>'
})
s.nodemailer.sendMail(mailOptions, (error, info) => {
if (error) {
s.systemLog(lang.MailError,error)
return false;
}
})
}
if(d.mon.details.detector_mail_send_video === '1'){
s.mergeDetectorBufferChunks(d,function(mergedFilepath,filename){
s.nodemailer.sendMail({
from: config.mail.from,
to: r.mail,
subject: filename,
html: '',
attachments: [
{
filename: filename,
content: fs.readFileSync(mergedFilepath)
}
]
}, (error, info) => {
if (error) {
s.systemLog(lang.MailError,error)
return false;
}
})
})
}
if(d.screenshotBuffer){
files.push({
filename: d.screenshotName+'.jpg',
content: d.screenshotBuffer
})
sendMail()
}else{
s.getRawSnapshotFromMonitor(d.mon,function(data){
d.screenshotBuffer = data
files.push({
filename: d.screenshotName+'.jpg',
content: data
})
sendMail()
})
}
})
}
}
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForEmail)
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForEmail)
s.onEventTrigger(onEventTriggerForEmail)
s.onFilterEvent(onFilterEventForEmail)
s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForEmail)
}catch(err){
console.log(err)
}
}

105
libs/plugins.js Normal file
View file

@ -0,0 +1,105 @@
var socketIOclient = require('socket.io-client');
module.exports = function(s,config,lang){
//function for receiving detector data
s.pluginEventController=function(d){
switch(d.f){
case'trigger':
s.triggerEvent(d)
break;
case's.tx':
s.tx(d.data,d.to)
break;
case's.sqlQuery':
s.sqlQuery(d.query,d.values)
break;
case'log':
s.systemLog('PLUGIN : '+d.plug+' : ',d)
break;
}
}
//multi plugin connections
s.connectedPlugins={}
s.pluginInitiatorSuccess=function(mode,d,cn){
s.systemLog('pluginInitiatorSuccess',d)
if(mode==='client'){
//is in client mode (camera.js is client)
cn.pluginEngine=d.plug
if(!s.connectedPlugins[d.plug]){
s.connectedPlugins[d.plug]={plug:d.plug}
}
s.systemLog('Connected to plugin : Detector - '+d.plug+' - '+d.type)
switch(d.type){
default:case'detector':
s.ocv={started:s.timeObject(),id:cn.id,plug:d.plug,notice:d.notice,isClientPlugin:true};
cn.ocv=1;
s.tx({f:'detector_plugged',plug:d.plug,notice:d.notice},'CPU')
break;
}
}else{
//is in host mode (camera.js is client)
switch(d.type){
default:case'detector':
s.ocv={started:s.timeObject(),id:"host",plug:d.plug,notice:d.notice,isHostPlugin:true};
break;
}
}
s.connectedPlugins[d.plug].plugged=true
s.tx({f:'readPlugins',ke:d.ke},'CPU')
s.ocvTx({f:'api_key',key:d.plug})
s.api[d.plug]={pluginEngine:d.plug,permissions:{},details:{},ip:'0.0.0.0'};
}
s.pluginInitiatorFail=function(mode,d,cn){
if(s.connectedPlugins[d.plug])s.connectedPlugins[d.plug].plugged=false
if(mode==='client'){
//is in client mode (camera.js is client)
cn.disconnect()
}else{
//is in host mode (camera.js is client)
}
}
if(config.plugins&&config.plugins.length>0){
config.plugins.forEach(function(v){
s.connectedPlugins[v.id]={plug:v.id}
if(v.enabled===false){return}
if(v.mode==='host'){
//is in host mode (camera.js is client)
if(v.https===true){
v.https='https://'
}else{
v.https='http://'
}
if(!v.port){
v.port=80
}
var socket = socketIOclient(v.https+v.host+':'+v.port)
s.connectedPlugins[v.id].tx = function(x){return socket.emit('f',x)}
socket.on('connect', function(cn){
s.systemLog('Connected to plugin (host) : '+v.id)
s.connectedPlugins[v.id].tx({f:'init_plugin_as_host',key:v.key})
});
socket.on('init',function(d){
s.systemLog('Initialize Plugin : Host',d)
if(d.ok===true){
s.pluginInitiatorSuccess("host",d)
}else{
s.pluginInitiatorFail("host",d)
}
});
socket.on('ocv',s.pluginEventController);
socket.on('disconnect', function(){
s.connectedPlugins[v.id].plugged=false
delete(s.api[v.id])
s.systemLog('Plugin Disconnected : '+v.id)
s.connectedPlugins[v.id].reconnector = setInterval(function(){
if(socket.connected===true){
clearInterval(s.connectedPlugins[v.id].reconnector)
}else{
socket.connect()
}
},1000*2)
});
s.connectedPlugins[v.id].ws = socket;
}
})
}
}

33
libs/process.js Normal file
View file

@ -0,0 +1,33 @@
var os = require('os')
module.exports = function(process,__dirname){
process.send = process.send || function () {};
process.on('uncaughtException', function (err) {
console.error('Uncaught Exception occured!');
console.error(err.stack);
});
// [CTRL] + [C] = exit
process.on('SIGINT', function() {
console.log('Shinobi is Exiting...')
process.exit();
});
// s = Shinobi
s = {
//Total Memory
coreCount : os.cpus().length,
//Total Memory
totalmem : os.totalmem(),
//Check Platform
platform : os.platform(),
//JSON stringify short-hand
s : JSON.stringify,
//Pretty Print JSON
prettyPrint : function(obj){return JSON.stringify(obj,null,3)},
//Check if Windows
isWin : (process.platform === 'win32' || process.platform === 'win64'),
//UTC Offset
utcOffset : require('moment')().utcOffset(),
//directory path for this file
mainDirectory : __dirname
}
return s
}

1433
libs/socketio.js Normal file

File diff suppressed because it is too large Load diff

88
libs/sql.js Normal file
View file

@ -0,0 +1,88 @@
module.exports = function(s,config){
//sql/database connection with knex
s.databaseOptions = {
client: config.databaseType,
connection: config.db,
}
if(s.databaseOptions.client.indexOf('sqlite')>-1){
s.databaseOptions.client = 'sqlite3';
s.databaseOptions.useNullAsDefault = true;
}
if(s.databaseOptions.client === 'sqlite3' && s.databaseOptions.connection.filename === undefined){
s.databaseOptions.connection.filename = s.mainDirectory+"/shinobi.sqlite"
}
s.mergeQueryValues = function(query,values){
if(!values){values=[]}
var valuesNotFunction = true;
if(typeof values === 'function'){
var values = [];
valuesNotFunction = false;
}
if(values&&valuesNotFunction){
var splitQuery = query.split('?')
var newQuery = ''
splitQuery.forEach(function(v,n){
newQuery += v
var value = values[n]
if(value){
if(isNaN(value) || value instanceof Date){
newQuery += "'"+value+"'"
}else{
newQuery += value
}
}
})
}else{
newQuery = query
}
return newQuery
}
s.stringToSqlTime = function(value){
newValue = new Date(value.replace('T',' '))
return newValue
}
s.sqlQuery = function(query,values,onMoveOn,hideLog){
if(!values){values=[]}
if(typeof values === 'function'){
var onMoveOn = values;
var values = [];
}
if(!onMoveOn){onMoveOn=function(){}}
var mergedQuery = s.mergeQueryValues(query,values)
s.debugLog('s.sqlQuery QUERY',mergedQuery)
return s.databaseEngine
.raw(query,values)
.asCallback(function(err,r){
if(err && !hideLog){
console.log('s.sqlQuery QUERY ERRORED',query)
console.log('s.sqlQuery ERROR',err)
}
if(onMoveOn && typeof onMoveOn === 'function'){
switch(s.databaseOptions.client){
case'sqlite3':
if(!r)r=[]
break;
default:
if(r)r=r[0]
break;
}
onMoveOn(err,r)
}
})
}
s.preQueries = function(){
//add Cloud Videos table, will remove in future
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Cloud Videos` (`mid` varchar(50) NOT NULL,`ke` varchar(50) DEFAULT NULL,`href` text NOT NULL,`size` float DEFAULT NULL,`time` timestamp NULL DEFAULT NULL,`end` timestamp NULL DEFAULT NULL,`status` int(1) DEFAULT \'0\' COMMENT \'0:Complete,1:Read,2:Archive\',`details` text) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
// if(err)console.log(err)
},true)
//create Files table
s.sqlQuery('CREATE TABLE IF NOT EXISTS `Files` (`ke` varchar(50) NOT NULL,`mid` varchar(50) NOT NULL,`name` tinytext NOT NULL,`size` float NOT NULL DEFAULT \'0\',`details` text NOT NULL,`status` int(1) NOT NULL DEFAULT \'0\') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;',[],function(err){
// if(err)console.log(err)
//add time column to Files table
s.sqlQuery('ALTER TABLE `Files` ADD COLUMN `time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;',[],function(err){
// if(err)console.log(err)
},true)
},true)
delete(s.preQueries)
}
}

165
libs/startup.js Normal file
View file

@ -0,0 +1,165 @@
var fs = require('fs');
var moment = require('moment');
var crypto = require('crypto');
var exec = require('child_process').exec;
var execSync = require('child_process').execSync;
module.exports = function(s,config,lang,io){
console.log('FFmpeg version : '+s.ffmpegVersion)
console.log('Node.js version : '+execSync("node -v"))
s.processReady = function(){
s.systemLog(lang.startUpText5)
process.send('ready')
}
var loadedAccounts = []
var loadMonitors = function(callback){
s.systemLog(lang.startUpText4)
//preliminary monitor start
s.sqlQuery('SELECT * FROM Monitors', function(err,monitors) {
if(err){s.systemLog(err)}
if(monitors && monitors[0]){
var loadCompleted = 0
var orphanedVideosForMonitors = {}
var loadMonitor = function(monitor){
if(!orphanedVideosForMonitors[monitor.ke])orphanedVideosForMonitors[monitor.ke] = {}
if(!orphanedVideosForMonitors[monitor.ke][monitor.mid])orphanedVideosForMonitors[monitor.ke][monitor.mid] = 0
s.initiateMonitorObject(monitor)
s.orphanedVideoCheck(monitor,2,function(orphanedFilesCount){
if(orphanedFilesCount){
orphanedVideosForMonitors[monitor.ke][monitor.mid] += orphanedFilesCount
}
s.group[monitor.ke].mon_conf[monitor.mid] = monitor
var monObj = Object.assign(monitor,{id : monitor.mid})
s.camera(monitor.mode,monObj)
++loadCompleted
if(monitors[loadCompleted]){
loadMonitor(monitors[loadCompleted])
}else{
s.systemLog(lang.startUpText6+' : '+s.s(orphanedVideosForMonitors))
callback()
}
})
}
loadMonitor(monitors[loadCompleted])
}else{
callback()
}
})
}
var loadDiskUseForUser = function(user,callback){
s.systemLog(user.mail+' : '+lang.startUpText0)
var userDetails = JSON.parse(user.details)
user.size = 0
user.limit = userDetails.size
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=?',[user.ke,0],function(err,videos){
if(videos && videos[0]){
videos.forEach(function(video){
user.size += video.size
})
}
s.systemLog(user.mail+' : '+lang.startUpText1+' : '+videos.length,user.size)
callback()
})
}
var loadCloudDiskUseForUser = function(user,callback){
var userDetails = JSON.parse(user.details)
user.cloudDiskUse = {}
user.size = 0
user.limit = userDetails.size
s.cloudDisksLoaded.forEach(function(storageType){
user.cloudDiskUse[storageType] = {
usedSpace : 0,
firstCount : 0
}
if(s.cloudDiskUseStartupExtensions[storageType])s.cloudDiskUseStartupExtensions[storageType](user,userDetails)
})
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE ke=? AND status!=?',[user.ke,0],function(err,videos){
if(videos && videos[0]){
videos.forEach(function(video){
var storageType = JSON.parse(video.details).type
if(!storageType)storageType = 's3'
user.cloudDiskUse[storageType].usedSpace += (video.size /1000000)
++user.cloudDiskUse[storageType].firstCount
})
s.cloudDisksLoaded.forEach(function(storageType){
var firstCount = user.cloudDiskUse[storageType].firstCount
s.systemLog(user.mail+' : '+lang.startUpText1+' : '+firstCount,storageType,user.cloudDiskUse[storageType].usedSpace)
delete(user.cloudDiskUse[storageType].firstCount)
})
}
s.group[user.ke].cloudDiskUse = user.cloudDiskUse
callback()
})
}
var loadAdminUsers = function(callback){
//get current disk used for each isolated account (admin user) on startup
s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,users){
if(users && users[0]){
var loadLocalDiskUse = function(callback){
var count = users.length
var countFinished = 0
users.forEach(function(user){
loadedAccounts.push(user.ke)
loadDiskUseForUser(user,function(){
s.loadGroup(user)
s.loadGroupApps(user)
++countFinished
if(countFinished === count){
callback()
}
})
})
}
var loadCloudDiskUse = function(callback){
var count = users.length
var countFinished = 0
users.forEach(function(user){
loadCloudDiskUseForUser(user,function(){
++countFinished
if(countFinished === count){
callback()
}
})
})
}
loadLocalDiskUse(function(){
loadCloudDiskUse(function(){
callback()
})
})
}else{
s.processReady()
}
})
}
//check disk space every 20 minutes
if(config.autoDropCache===true){
setInterval(function(){
exec('echo 3 > /proc/sys/vm/drop_caches',{detached: true})
},60000*20)
}
//master node - startup functions
setInterval(function(){
s.cpuUsage(function(cpu){
s.ramUsage(function(ram){
s.tx({f:'os',cpu:cpu,ram:ram},'CPU');
})
})
},10000)
//run prerequsite queries, load users and monitors
if(config.childNodes.mode !== 'child'){
//sql/database connection with knex
s.databaseEngine = require('knex')(s.databaseOptions)
//run prerequsite queries
s.preQueries()
setTimeout(function(){
//load administrators (groups)
loadAdminUsers(function(){
//load monitors (for groups)
loadMonitors(function(){
s.processReady()
})
})
},1500)
}
}

235
libs/user.js Normal file
View file

@ -0,0 +1,235 @@
var fs = require('fs');
var events = require('events');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
module.exports = function(s,config){
s.purgeDiskForGroup = function(e){
if(config.cron.deleteOverMax === true){
s.group[e.ke].sizePurgeQueue.push(1)
if(s.group[e.ke].sizePurging !== true){
s.group[e.ke].sizePurging = true
var finish = function(){
//remove value just used from queue
s.group[e.ke].sizePurgeQueue.shift()
//do next one
if(s.group[e.ke].sizePurgeQueue.length > 0){
checkQueue()
}else{
s.group[e.ke].sizePurging=false
s.sendDiskUsedAmountToClients(e)
}
}
var checkQueue = function(){
//get first in queue
var currentPurge = s.group[e.ke].sizePurgeQueue[0]
var deleteVideos = function(){
//run purge command
if(s.group[e.ke].usedSpace > (s.group[e.ke].sizeLimit*config.cron.deleteOverMaxOffset)){
s.sqlQuery('SELECT * FROM Videos WHERE status != 0 AND details NOT LIKE \'%"archived":"1"%\' AND ke=? ORDER BY `time` ASC LIMIT 3',[e.ke],function(err,videos){
var videosToDelete = []
var queryValues = [e.ke]
var completedCheck = 0
if(videos){
videos.forEach(function(video){
video.dir = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
videosToDelete.push('(mid=? AND `time`=?)')
queryValues.push(video.mid)
queryValues.push(video.time)
fs.chmod(video.dir,0o777,function(err){
fs.unlink(video.dir,function(err){
++completedCheck
if(err){
fs.stat(video.dir,function(err){
if(!err){
s.file('delete',video.dir)
}
})
}
if(videosToDelete.length === completedCheck){
videosToDelete = videosToDelete.join(' OR ')
s.sqlQuery('DELETE FROM Videos WHERE ke =? AND ('+videosToDelete+')',queryValues,function(){
deleteVideos()
})
}
})
})
s.setDiskUsedForGroup(e,-(video.size/1000000))
s.tx({
f: 'video_delete',
ff: 'over_max',
filename: s.formattedTime(video.time)+'.'+video.ext,
mid: video.mid,
ke: video.ke,
time: video.time,
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
},'GRP_'+e.ke)
})
}else{
console.log(err)
}
if(videosToDelete.length === 0){
finish()
}
})
}else{
finish()
}
}
deleteVideos()
}
checkQueue()
}
}else{
s.sendDiskUsedAmountToClients(e)
}
}
s.setDiskUsedForGroup = function(e,bytes){
//`bytes` will be used as the value to add or substract
if(s.group[e.ke] && s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter.emit('set',bytes)
}
}
s.purgeCloudDiskForGroup = function(e,storageType){
if(s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter.emit('purgeCloud',storageType)
}
}
s.setCloudDiskUsedForGroup = function(e,usage){
//`bytes` will be used as the value to add or substract
if(s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter.emit('setCloud',usage)
}
}
s.sendDiskUsedAmountToClients = function(e){
//send the amount used disk space to connected users
if(s.group[e.ke]&&s.group[e.ke].init){
s.tx({f:'diskUsed',size:s.group[e.ke].usedSpace,limit:s.group[e.ke].sizeLimit},'GRP_'+e.ke);
}
}
//user log
s.userLog = function(e,x){
if(e.id && !e.mid)e.mid = e.id
if(!x||!e.mid){return}
if((e.details&&e.details.sqllog==='1')||e.mid.indexOf('$')>-1){
s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',[e.ke,e.mid,s.s(x)]);
}
s.tx({f:'log',ke:e.ke,mid:e.mid,log:x,time:s.timeObject()},'GRPLOG_'+e.ke);
}
s.loadGroup = function(e){
if(!s.group[e.ke]){
s.group[e.ke]={}
}
if(!s.group[e.ke].init){
s.group[e.ke].init={}
}
if(!s.group[e.ke].fileBin){s.group[e.ke].fileBin={}};
if(!s.group[e.ke].users){s.group[e.ke].users={}}
if(!s.group[e.ke].dashcamUsers){s.group[e.ke].dashcamUsers={}}
if(!s.group[e.ke].sizePurgeQueue){s.group[e.ke].sizePurgeQueue=[]}
if(!e.limit||e.limit===''){e.limit=10000}else{e.limit=parseFloat(e.limit)}
//save global space limit for group key (mb)
s.group[e.ke].sizeLimit=e.limit;
//save global used space as megabyte value
s.group[e.ke].usedSpace=e.size/1000000;
//emit the changes to connected users
s.sendDiskUsedAmountToClients(e)
}
s.loadGroupApps = function(e){
// e = user
if(!s.group[e.ke].init){
s.group[e.ke].init={};
}
s.sqlQuery('SELECT * FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(ar,r){
if(r&&r[0]){
r=r[0];
ar=JSON.parse(r.details);
//load extenders
s.loadGroupAppExtensions.forEach(function(extender){
extender(r)
})
//disk Used Emitter
if(!s.group[e.ke].diskUsedEmitter){
s.group[e.ke].diskUsedEmitter = new events.EventEmitter()
s.group[e.ke].diskUsedEmitter.on('setCloud',function(currentChange){
var amount = currentChange.amount
var storageType = currentChange.storageType
var cloudDisk = s.group[e.ke].cloudDiskUse[storageType]
//validate current values
if(!cloudDisk.usedSpace){
cloudDisk.usedSpace = 0
}else{
cloudDisk.usedSpace = parseFloat(cloudDisk.usedSpace)
}
if(cloudDisk.usedSpace < 0 || isNaN(cloudDisk.usedSpace)){
cloudDisk.usedSpace = 0
}
//change global size value
cloudDisk.usedSpace = cloudDisk.usedSpace + amount
})
s.group[e.ke].diskUsedEmitter.on('purgeCloud',function(storageType){
if(config.cron.deleteOverMax === true){
//set queue processor
var finish=function(){
// s.sendDiskUsedAmountToClients(e)
}
var deleteVideos = function(){
//run purge command
var cloudDisk = s.group[e.ke].cloudDiskUse[storageType]
if(cloudDisk.sizeLimitCheck && cloudDisk.usedSpace > (cloudDisk.sizeLimit*config.cron.deleteOverMaxOffset)){
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE status != 0 AND ke=? AND details LIKE \'%"type":"'+storageType+'"%\' ORDER BY `time` ASC LIMIT 2',[e.ke],function(err,videos){
var videosToDelete = []
var queryValues = [e.ke]
if(!videos)return console.log(err)
videos.forEach(function(video){
video.dir = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
videosToDelete.push('(mid=? AND `time`=?)')
queryValues.push(video.mid)
queryValues.push(video.time)
s.setCloudDiskUsedForGroup(e,{
amount : -(video.size/1000000),
storageType : storageType
})
s.deleteVideoFromCloudExtensionsRunner(e,storageType,video)
})
if(videosToDelete.length > 0){
videosToDelete = videosToDelete.join(' OR ')
s.sqlQuery('DELETE FROM `Cloud Videos` WHERE ke =? AND ('+videosToDelete+')',queryValues,function(){
deleteVideos()
})
}else{
finish()
}
})
}else{
finish()
}
}
deleteVideos()
}else{
// s.sendDiskUsedAmountToClients(e)
}
})
//s.setDiskUsedForGroup
s.group[e.ke].diskUsedEmitter.on('set',function(currentChange){
//validate current values
if(!s.group[e.ke].usedSpace){
s.group[e.ke].usedSpace=0
}else{
s.group[e.ke].usedSpace=parseFloat(s.group[e.ke].usedSpace)
}
if(s.group[e.ke].usedSpace<0||isNaN(s.group[e.ke].usedSpace)){
s.group[e.ke].usedSpace=0
}
//change global size value
s.group[e.ke].usedSpace += currentChange
//remove value just used from queue
s.sendDiskUsedAmountToClients(e)
})
}
Object.keys(ar).forEach(function(v){
s.group[e.ke].init[v]=ar[v]
})
}
});
}
}

329
libs/videos.js Normal file
View file

@ -0,0 +1,329 @@
var fs = require('fs');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
module.exports = function(s,config,lang){
/**
* Gets the video directory of the supplied video or monitor database row.
* @constructor
* @param {object} e - Monitor object or Video object. Object is a database row.
*/
s.getVideoDirectory = function(e){
if(e.mid&&!e.id){e.id=e.mid};
s.checkDetails(e)
if(e.details&&e.details.dir&&e.details.dir!==''){
return s.checkCorrectPathEnding(e.details.dir)+e.ke+'/'+e.id+'/'
}else{
return s.dir.videos+e.ke+'/'+e.id+'/';
}
}
/**
* Creates available API based URLs for streaming
* @constructor
* @param {object} videos - Array of video objects
* @param {object} options - Contains middle parameter of URL and auth key
* @param [options.auth] {string} - API Key
* @param [options.videoParam] {string} - currently only `videos` and `cloudVideos` available.
*/
s.buildVideoLinks = function(videos,options){
videos.forEach(function(v){
var details = JSON.parse(v.details)
var queryString = []
if(details.isUTC === true){
queryString.push('isUTC=true')
}else{
v.time = s.utcToLocal(v.time)
v.end = s.utcToLocal(v.end)
}
if(queryString.length > 0){
queryString = '?'+queryString.join('&')
}else{
queryString = ''
}
if(!v.ext && v.href){
v.ext = v.href.split('.')
v.ext = v.ext[v.ext.length - 1]
}
v.filename = s.formattedTime(v.time)+'.'+v.ext;
if(!options.videoParam)options.videoParam = 'videos'
var href = '/'+options.auth+'/'+options.videoParam+'/'+v.ke+'/'+v.mid+'/'+v.filename;
v.actionUrl = href
v.links = {
deleteVideo : href+'/delete' + queryString,
changeToUnread : href+'/status/1' + queryString,
changeToRead : href+'/status/2' + queryString
}
if(!v.href || options.hideRemote === true)v.href = href + queryString
v.details = details
})
}
//extender for "s.insertCompletedVideo"
s.insertCompletedVideoExtensions = []
s.insertCompletedVideoExtender = function(callback){
s.insertCompletedVideoExtensions.push(callback)
}
s.insertDatabaseRow = function(e,k,callback){
s.checkDetails(e)
//save database row
k.details = {}
if(e.details&&e.details.dir&&e.details.dir!==''){
k.details.dir = e.details.dir
}
if(config.useUTC === true)k.details.isUTC = config.useUTC;
var save = [
e.mid,
e.ke,
k.startTime,
e.ext,
1,
s.s(k.details),
k.filesize,
k.endTime,
]
s.sqlQuery('INSERT INTO Videos (mid,ke,time,ext,status,details,size,end) VALUES (?,?,?,?,?,?,?,?)',save,function(err){
if(callback)callback(err)
fs.chmod(k.dir+k.file,0o777,function(err){
})
})
}
//on video completion
s.insertCompletedVideo = function(e,k,callback){
//e = monitor object
//k = video insertion object
s.checkDetails(e)
if(!k)k={};
e.dir = s.getVideoDirectory(e)
k.dir = e.dir.toString()
if(s.group[e.ke].mon[e.id].childNode){
s.cx({f:'insertCompleted',d:s.group[e.ke].mon_conf[e.id],k:k},s.group[e.ke].mon[e.id].childNodeId);
}else{
//get file directory
k.fileExists = fs.existsSync(k.dir+k.file)
if(k.fileExists!==true){
k.dir = s.dir.videos+'/'+e.ke+'/'+e.id+'/'
k.fileExists = fs.existsSync(k.dir+k.file)
if(k.fileExists !== true){
s.dir.addStorage.forEach(function(v){
if(k.fileExists !== true){
k.dir = s.checkCorrectPathEnding(v.path)+e.ke+'/'+e.id+'/'
k.fileExists = fs.existsSync(k.dir+k.file)
}
})
}
}
if(k.fileExists===true){
//close video row
k.stat = fs.statSync(k.dir+k.file)
k.filesize = k.stat.size
k.filesizeMB = parseFloat((k.filesize/1000000).toFixed(2))
k.startTime = new Date(s.nameToTime(k.file))
k.endTime = new Date(k.stat.mtime)
if(config.useUTC === true){
fs.rename(k.dir+k.file, k.dir+s.formattedTime(k.startTime)+'.'+e.ext, (err) => {
if (err) return console.error(err);
});
k.filename = s.formattedTime(k.startTime)+'.'+e.ext
}else{
k.filename = k.file
}
if(!e.ext){e.ext = k.filename.split('.')[1]}
//send event for completed recording
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
fs.createReadStream(k.dir+k.filename,{ highWaterMark: 500 })
.on('data',function(data){
s.cx({
f:'created_file_chunk',
mid:e.mid,
ke:e.ke,
chunk:data,
filename:k.filename,
d:s.cleanMonitorObject(e),
filesize:e.filesize,
time:s.timeObject(k.startTime).format(),
end:s.timeObject(k.endTime).format()
})
})
.on('close',function(){
clearTimeout(s.group[e.ke].mon[e.id].recordingChecker)
clearTimeout(s.group[e.ke].mon[e.id].streamChecker)
s.cx({
f:'created_file',
mid:e.id,
ke:e.ke,
filename:k.filename,
d:s.cleanMonitorObject(e),
filesize:k.filesize,
time:s.timeObject(k.startTime).format(),
end:s.timeObject(k.endTime).format()
})
})
}else{
var href = '/videos/'+e.ke+'/'+e.mid+'/'+k.filename
if(config.useUTC === true)href += '?isUTC=true';
s.txWithSubPermissions({
f:'video_build_success',
hrefNoAuth:href,
filename:k.filename,
mid:e.mid,
ke:e.ke,
time:k.startTime,
size:k.filesize,
end:k.endTime
},'GRP_'+e.ke,'video_view')
//purge over max
s.purgeDiskForGroup(e)
//send new diskUsage values
s.setDiskUsedForGroup(e,k.filesizeMB)
s.insertDatabaseRow(e,k,callback)
s.insertCompletedVideoExtensions.forEach(function(extender){
extender(e,k)
})
}
}
}
}
s.deleteVideo = function(e){
//e = video object
s.checkDetails(e)
e.dir = s.getVideoDirectory(e)
if(!e.filename && e.time){
e.filename = s.formattedTime(e.time)
}
var filename,
time
if(e.filename.indexOf('.')>-1){
filename = e.filename
}else{
filename = e.filename+'.'+e.ext
}
if(e.filename && !e.time){
time = s.nameToTime(filename)
}else{
time = e.time
}
time = new Date(time)
var queryValues = [e.id,e.ke,time];
s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',queryValues,function(err,r){
if(r && r[0]){
r = r[0]
fs.chmod(e.dir+filename,0o777,function(err){
s.tx({
f: 'video_delete',
filename: filename,
mid: e.id,
ke: e.ke,
time: s.nameToTime(filename),
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
},'GRP_'+e.ke);
s.setDiskUsedForGroup(e,-(r.size / 1000000))
s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',queryValues,function(err){
if(err){
s.systemLog(lang['File Delete Error'] + ' : '+e.ke+' : '+' : '+e.id,err)
}
})
fs.unlink(e.dir+filename,function(err){
fs.stat(e.dir+filename,function(err){
if(!err){
s.file('delete',e.dir+filename)
}
})
})
})
var videoSnap = s.dir.videoSnaps + e.ke + '/' + e.mid + '/' + filename.split('.')[0] + '.jpg'
fs.chmod(videoSnap,0o777,function(err){
if(!err){
fs.unlink(videoSnap,function(err){})
}
})
}else{
console.log(lang['Database row does not exist'],queryValues)
}
})
}
s.deleteVideoFromCloudExtensions = {}
s.deleteVideoFromCloudExtensionsRunner = function(e,storageType,video){
// e = user
if(!storageType){
var videoDetails = JSON.parse(r.details)
videoDetails.type = videoDetails.type || 's3'
}
if(s.deleteVideoFromCloudExtensions[storageType]){
s.deleteVideoFromCloudExtensions[storageType](e,video,function(){
s.tx({
f: 'video_delete_cloud',
mid: e.mid,
ke: e.ke,
time: e.time,
end: e.end
},'GRP_'+e.ke);
})
}
}
s.deleteVideoFromCloud = function(e){
// e = video object
s.checkDetails(e)
var videoSelector = [e.id,e.ke,new Date(e.time)]
s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE `mid`=? AND `ke`=? AND `time`=?',videoSelector,function(err,r){
if(r&&r[0]){
r = r[0]
s.sqlQuery('DELETE FROM `Cloud Videos` WHERE `mid`=? AND `ke`=? AND `time`=?',videoSelector,function(){
s.deleteVideoFromCloudExtensionsRunner(e,r)
})
}else{
// console.log('Delete Failed',e)
// console.error(err)
}
})
}
s.orphanedVideoCheck = function(monitor,checkMax,callback,forceCheck){
var finish = function(orphanedFilesCount){
if(callback)callback(orphanedFilesCount)
}
if(forceCheck === true || config.insertOrphans === true){
if(!checkMax){
checkMax = config.orphanedVideoCheckMax
}
var videosDirectory = s.getVideoDirectory(monitor)// + s.formattedTime(video.time) + '.' + video.ext
fs.readdir(videosDirectory,function(err,files){
if(files && files.length > 0){
var fiveRecentFiles = files.slice(files.length - checkMax,files.length)
var completedFile = 0
var orphanedFilesCount = 0
var fileComplete = function(){
++completedFile
if(fiveRecentFiles.length === completedFile){
finish(orphanedFilesCount)
}
}
fiveRecentFiles.forEach(function(filename){
if(/T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(filename)){
var queryValues = [
monitor.ke,
monitor.mid,
s.nameToTime(filename)
]
s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND time=? LIMIT 1',queryValues,function(err,rows){
if(!err && (!rows || !rows[0])){
++orphanedFilesCount
var video = rows[0]
s.insertCompletedVideo(monitor,{
file : filename
},function(){
fileComplete()
})
}else{
fileComplete()
}
})
}
})
}else{
finish()
}
})
}else{
finish()
}
}
}

35
libs/webServer.js Normal file
View file

@ -0,0 +1,35 @@
var fs = require('fs');
var http = require('http');
var https = require('https');
var express = require('express');
var app = express()
module.exports = function(s,config,lang,io){
var server = http.createServer(app);
//SSL options
if(config.ssl&&config.ssl.key&&config.ssl.cert){
config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8')
config.ssl.cert=fs.readFileSync(s.checkRelativePath(config.ssl.cert),'utf8')
if(config.ssl.port === undefined){
config.ssl.port=443
}
if(config.ssl.bindip === undefined){
config.ssl.bindip=config.bindip
}
if(config.ssl.ca&&config.ssl.ca instanceof Array){
config.ssl.ca.forEach(function(v,n){
config.ssl.ca[n]=fs.readFileSync(s.checkRelativePath(v),'utf8')
})
}
var serverHTTPS = https.createServer(config.ssl,app);
serverHTTPS.listen(config.ssl.port,config.bindip,function(){
console.log('SSL '+lang.Shinobi+' : SSL Web Server Listening on '+config.ssl.port);
});
io.attach(serverHTTPS);
}
//start HTTP
server.listen(config.port,config.bindip,function(){
console.log(lang.Shinobi+' : Web Server Listening on '+config.port);
});
io.attach(server);
return app
}

428
libs/webServerAdminPaths.js Normal file
View file

@ -0,0 +1,428 @@
var fs = require('fs');
var os = require('os');
var moment = require('moment')
var request = require('request')
var jsonfile = require("jsonfile")
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var execSync = require('child_process').execSync;
module.exports = function(s,config,lang,app){
var closeResponse = function(res,endData){
res.setHeader('Content-Type', 'application/json')
res.end(s.prettyPrint(endData))
}
/**
* API : Administrator : Edit Sub-Account (Account to share cameras with)
*/
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/edit', function (req,res){
s.auth(req.params,function(user){
var endData = {
ok : false
}
if(user.details.sub){
endData.msg = user.lang['Not Permitted']
closeResponse(res,endData)
return
}
var form = s.getPostData(req)
var uid = s.getPostData(req,'uid',false)
var mail = s.getPostData(req,'mail',false)
if(form){
var keys = Object.keys(form)
var condition = []
var value = []
keys.forEach(function(v){
condition.push(v+'=?')
if(form[v] instanceof Object)form[v] = JSON.stringify(form[v])
value.push(form[v])
})
value = value.concat([req.params.ke,uid])
s.sqlQuery("UPDATE Users SET "+condition.join(',')+" WHERE ke=? AND uid=?",value)
s.tx({
f: 'edit_sub_account',
ke: req.params.ke,
uid: uid,
mail: mail,
form: form
},'ADM_'+req.params.ke)
endData.ok = true
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[req.params.ke,uid],function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
delete(s.api[row.code])
})
}
})
}else{
endData.msg = lang.postDataBroken
}
closeResponse(res,endData)
},res,req)
})
/**
* API : Administrator : Delete Sub-Account (Account to share cameras with)
*/
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/delete', function (req,res){
s.auth(req.params,function(user){
var endData = {
ok : false
}
if(user.details.sub){
endData.msg = user.lang['Not Permitted']
closeResponse(res,endData)
return
}
var uid = s.getPostData(req,'uid',false)
var mail = s.getPostData(req,'mail',false)
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[uid,req.params.ke,mail])
s.sqlQuery("SELECT * FROM API WHERE ke=? AND uid=?",[req.params.ke,uid],function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
delete(s.api[row.code])
})
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[uid,req.params.ke])
}
})
s.tx({
f: 'delete_sub_account',
ke: req.params.ke,
uid: uid,
mail: mail
},'ADM_'+req.params.ke)
endData.ok = true
closeResponse(res,endData)
},res,req)
})
/**
* API : Administrator : Add Sub-Account (Account to share cameras with)
*/
app.post([
config.webPaths.adminApiPrefix+':auth/accounts/:ke/register',
//these two routes are for backwards compatibility
config.webPaths.adminApiPrefix+':auth/register/:ke/:uid',
config.webPaths.apiPrefix+':auth/register/:ke/:uid'
],function (req,res){
endData = {
ok : false
}
res.setHeader('Content-Type', 'application/json');
s.auth(req.params,function(user){
if(user.details.sub){
endData.msg = user.lang['Not an Administrator Account']
closeResponse(res,endData)
return
}
var form = s.getPostData(req)
if(form.mail !== '' && form.pass !== ''){
if(form.pass === form.password_again || form.pass === form.pass_again){
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[form.mail],function(err,r) {
if(r&&r[0]){
//found one exist
endData.msg = 'Email address is in use.'
}else{
//create new
endData.msg = 'New Account Created'
endData.ok = true
var newId = s.gid()
var details = s.s({
sub: "1",
allmonitors: "1"
})
s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[req.params.ke,newId,form.mail,s.createHash(form.pass),details])
s.tx({
f: 'add_sub_account',
details: details,
ke: req.params.ke,
uid: newId,
mail: form.mail
},'ADM_'+req.params.ke)
}
res.end(s.prettyPrint(endData))
})
}else{
endData.msg = user.lang["Passwords Don't Match"]
}
}else{
endData.msg = user.lang['Fields cannot be empty']
}
if(endData.msg){
res.end(s.prettyPrint(endData))
}
},res,req)
})
/**
* API : Administrator : Monitor : Add, Edit, and Delete
*/
app.all([
config.webPaths.apiPrefix+':auth/configureMonitor/:ke/:id',
config.webPaths.apiPrefix+':auth/configureMonitor/:ke/:id/:f',
config.webPaths.adminApiPrefix+':auth/configureMonitor/:ke/:id',
config.webPaths.adminApiPrefix+':auth/configureMonitor/:ke/:id/:f'
], function (req,res){
req.ret={ok:false};
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
if(req.params.f !== 'delete'){
var form = s.getPostData(req)
if(!form){
req.ret.msg = user.lang.monitorEditText1;
res.end(s.prettyPrint(req.ret))
return
}
if(!user.details.sub ||
user.details.allmonitors === '1' ||
hasRestrictions && user.details.monitor_edit.indexOf(form.mid) >- 1 ||
hasRestrictions && user.details.monitor_create === '1'){
if(form&&form.mid&&form.name){
req.set=[],req.ar=[];
form.mid=req.params.id.replace(/[^\w\s]/gi,'').replace(/ /g,'');
try{
JSON.parse(form.details)
}catch(er){
if(!form.details||!form.details.stream_type){
req.ret.msg=user.lang.monitorEditText2;
res.end(s.prettyPrint(req.ret))
return
}else{
form.details=JSON.stringify(form.details)
}
}
form.ke=req.params.ke
req.logObject={details:JSON.parse(form.details),ke:req.params.ke,mid:req.params.id}
s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[form.ke,form.mid],function(er,r){
req.tx={f:'monitor_edit',mid:form.mid,ke:form.ke,mon:form};
if(r&&r[0]){
req.tx.new=false;
Object.keys(form).forEach(function(v){
if(form[v]&&form[v]!==''){
req.set.push(v+'=?'),req.ar.push(form[v]);
}
})
req.set=req.set.join(',');
req.ar.push(form.ke),req.ar.push(form.mid);
s.userLog(form,{type:'Monitor Updated',msg:'by user : '+user.uid});
req.ret.msg=user.lang['Monitor Updated by user']+' : '+user.uid;
s.sqlQuery('UPDATE Monitors SET '+req.set+' WHERE ke=? AND mid=?',req.ar)
req.finish=1;
}else{
if(!s.group[form.ke].init.max_camera||s.group[form.ke].init.max_camera==''||Object.keys(s.group[form.ke].mon).length <= parseInt(s.group[form.ke].init.max_camera)){
req.tx.new=true;
req.st=[];
Object.keys(form).forEach(function(v){
if(form[v]&&form[v]!==''){
req.set.push(v),req.st.push('?'),req.ar.push(form[v]);
}
})
// req.set.push('ke'),req.st.push('?'),req.ar.push(form.ke);
req.set=req.set.join(','),req.st=req.st.join(',');
s.userLog(form,{type:'Monitor Added',msg:'by user : '+user.uid});
req.ret.msg=user.lang['Monitor Added by user']+' : '+user.uid;
s.sqlQuery('INSERT INTO Monitors ('+req.set+') VALUES ('+req.st+')',req.ar)
req.finish=1;
}else{
req.tx.f='monitor_edit_failed';
req.tx.ff='max_reached';
req.ret.msg=user.lang.monitorEditFailedMaxReached;
}
}
if(req.finish===1){
form.details=JSON.parse(form.details)
req.ret.ok=true;
s.initiateMonitorObject({mid:form.mid,ke:form.ke});
s.group[form.ke].mon_conf[form.mid]=s.cleanMonitorObject(form);
if(form.mode==='stop'){
s.camera('stop',form);
}else{
s.camera('stop',form);setTimeout(function(){s.camera(form.mode,form);},5000)
};
s.tx(req.tx,'STR_'+form.ke);
};
s.tx(req.tx,'GRP_'+form.ke);
res.end(s.prettyPrint(req.ret))
})
}else{
req.ret.msg=user.lang.monitorEditText1;
res.end(s.prettyPrint(req.ret))
}
}else{
req.ret.msg=user.lang['Not Permitted'];
res.end(s.prettyPrint(req.ret))
}
}else{
if(!user.details.sub || user.details.allmonitors === '1' || user.details.monitor_edit.indexOf(req.params.id) > -1 || hasRestrictions && user.details.monitor_create === '1'){
s.userLog(s.group[req.params.ke].mon_conf[req.params.id],{type:'Monitor Deleted',msg:'by user : '+user.uid});
req.params.delete=1;s.camera('stop',req.params);
s.tx({f:'monitor_delete',uid:user.uid,mid:req.params.id,ke:req.params.ke},'GRP_'+req.params.ke);
s.sqlQuery('DELETE FROM Monitors WHERE ke=? AND mid=?',[req.params.ke,req.params.id])
// s.sqlQuery('DELETE FROM Files WHERE ke=? AND mid=?',[req.params.ke,req.params.id])
if(req.query.deleteFiles === 'true'){
//videos
s.dir.addStorage.forEach(function(v,n){
var videosDir = v.path+req.params.ke+'/'+req.params.id+'/'
fs.stat(videosDir,function(err,stat){
if(!err){
s.file('deleteFolder',videosDir)
}
})
})
var videosDir = s.dir.videos+req.params.ke+'/'+req.params.id+'/'
fs.stat(videosDir,function(err,stat){
if(!err){
s.file('deleteFolder',videosDir)
}
})
//fileBin
var binDir = s.dir.fileBin+req.params.ke+'/'+req.params.id+'/'
fs.stat(binDir,function(err,stat){
if(!err){
s.file('deleteFolder',binDir)
}
})
}
req.ret.ok=true;
req.ret.msg='Monitor Deleted by user : '+user.uid
res.end(s.prettyPrint(req.ret))
}else{
req.ret.msg=user.lang['Not Permitted'];
res.end(s.prettyPrint(req.ret))
}
}
},res,req)
})
/**
* API : Add API Key, binded to the user who created it
*/
app.all([
config.webPaths.adminApiPrefix+':auth/api/:ke/add',
config.webPaths.apiPrefix+':auth/api/:ke/add',
],function (req,res){
var endData = {ok:false}
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var endData = {
ok : false
}
var form = s.getPostData(req)
if(form){
var insert = {
ke : req.params.ke,
uid : user.uid,
code : s.gid(30),
ip : form.ip,
details : s.stringJSON(form.details)
}
var escapes = []
Object.keys(insert).forEach(function(column){
escapes.push('?')
});
s.sqlQuery('INSERT INTO API ('+Object.keys(insert).join(',')+') VALUES ('+escapes.join(',')+')',Object.values(insert),function(err,r){
insert.time = s.formattedTime(new Date,'YYYY-DD-MM HH:mm:ss');
if(!err){
s.tx({
f: 'api_key_added',
uid: user.uid,
form: insert
},'GRP_' + req.params.ke)
endData.ok = true
}
closeResponse(res,endData)
})
}else{
endData.msg = lang.postDataBroken
closeResponse(res,endData)
}
},res,req)
})
/**
* API : Delete API Key
*/
app.all([
config.webPaths.adminApiPrefix+':auth/api/:ke/delete',
config.webPaths.apiPrefix+':auth/api/:ke/delete',
],function (req,res){
var endData = {ok:false}
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var endData = {
ok : false
}
var form = s.getPostData(req)
if(form){
if(!form.code){
s.tx({
f:'form_incomplete',
uid: user.uid,
form:'APIs'
},'GRP_' + req.params.ke)
endData.msg = lang.postDataBroken
closeResponse(res,endData)
return
}
var row = {
ke : req.params.ke,
uid : user.uid,
code : form.code
}
var where = []
Object.keys(row).forEach(function(column){
where.push(column+'=?')
})
s.sqlQuery('DELETE FROM API WHERE '+where.join(' AND '),Object.values(row),function(err,r){
if(!err){
s.tx({
f: 'api_key_deleted',
uid: user.uid,
form: row
},'GRP_' + req.params.ke)
endData.ok = true
delete(s.api[row.code])
}
closeResponse(res,endData)
})
}else{
endData.msg = lang.postDataBroken
closeResponse(res,endData)
}
},res,req)
})
/**
* API : List API Keys for Authenticated user
*/
app.get([
config.webPaths.adminApiPrefix+':auth/api/:ke/list',
config.webPaths.apiPrefix+':auth/api/:ke/list',
],function (req,res){
var endData = {ok:false}
res.setHeader('Content-Type', 'application/json');
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
var endData = {
ok : false
}
var row = {
ke : req.params.ke,
uid : user.uid
}
var where = []
Object.keys(row).forEach(function(column){
where.push(column+'=?')
})
s.sqlQuery('SELECT * FROM API WHERE '+where.join(' AND '),Object.values(row),function(err,rows){
if(rows && rows[0]){
rows.forEach(function(row){
row.details = JSON.parse(row.details)
})
endData.ok = true
endData.uid = user.uid
endData.ke = user.ke
endData.keys = rows
}
closeResponse(res,endData)
})
},res,req)
})
}

1843
libs/webServerPaths.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,358 @@
var express = require('express');
var fs = require('fs');
var bodyParser = require('body-parser');
var os = require('os');
var moment = require('moment');
var request = require('request');
var execSync = require('child_process').execSync;
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({})
var ejs = require('ejs');
var CircularJSON = require('circular-json');
module.exports = function(s,config,lang,app){
/**
* Page : Get Embed Stream
*/
app.get([config.webPaths.apiPrefix+':auth/embed/:ke/:id',config.webPaths.apiPrefix+':auth/embed/:ke/:id/:addon'], function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.params.protocol=req.protocol;
s.auth(req.params,function(user){
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){
if(s.group[req.params.ke].mon[req.params.id].isStarted === true){
req.params.uid=user.uid;
res.render(config.renderPaths.embed,{data:req.params,baseUrl:req.protocol+'://'+req.hostname,config:config,lang:user.lang,mon:CircularJSON.parse(CircularJSON.stringify(s.group[req.params.ke].mon_conf[req.params.id])),originalURL:s.getOriginalUrl(req)});
res.end()
}else{
res.end(user.lang['Cannot watch a monitor that isn\'t running.'])
}
}else{
res.end(user.lang['No Monitor Exists with this ID.'])
}
},res,req);
});
/**
* API : Get Poseidon MP4 Stream
*/
app.get([config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.ts',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/s.ts'], function (req, res) {
s.auth(req.params,function(user){
if(!s.group[req.params.ke] || !s.group[req.params.ke].mon[req.params.id]){
res.status(404);
res.end('404 : Monitor not found');
return
}
s.checkChildProxy(req.params,function(){
var Channel = 'MAIN'
if(req.params.channel){
Channel = parseInt(req.params.channel)+config.pipeAddition
}
var mp4frag = s.group[req.params.ke].mon[req.params.id].mp4frag[Channel];
var errorMessage = 'MP4 Stream is not enabled'
if(!mp4frag){
res.status(503);
res.end('503 : initialization : '+errorMessage);
}else{
var init = mp4frag.initialization;
if (!init) {
res.status(503);
res.end('404 : Not Found : '+errorMessage);
} else {
res.locals.mp4frag = mp4frag
res.set('Access-Control-Allow-Origin', '*')
res.set('Connection', 'close')
res.set('Cache-Control', 'private, no-cache, no-store, must-revalidate')
res.set('Expires', '-1')
res.set('Pragma', 'no-cache')
res.set('Content-Type', 'video/mp4')
res.status(200);
res.write(init);
mp4frag.pipe(res);
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
res.on('close', () => {
try{
mp4frag.unpipe(res)
}catch(err){}
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
}
}
},res,req);
},res,req);
});
/**
* API and Page : Get MJPEG Stream or Page
* @param {string} full - if `true` page will load the MJPEG iframe page
*/
app.get([config.webPaths.apiPrefix+':auth/mjpeg/:ke/:id',config.webPaths.apiPrefix+':auth/mjpeg/:ke/:id/:channel'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
if(req.query.full=='true'){
res.render(config.renderPaths.mjpeg,{url:'/'+req.params.auth+'/mjpeg/'+req.params.ke+'/'+req.params.id,originalURL:s.getOriginalUrl(req)});
res.end()
}else{
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
var Emitter
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
}
res.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace; boundary=shinobi',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Pragma': 'no-cache'
});
var contentWriter,content = fs.readFileSync(config.defaultMjpeg,'binary');
res.write("--shinobi\r\n");
res.write("Content-Type: image/jpeg\r\n");
res.write("Content-Length: " + content.length + "\r\n");
res.write("\r\n");
res.write(content,'binary');
res.write("\r\n");
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
Emitter.on('data',contentWriter=function(d){
content = d;
res.write(content,'binary');
})
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
});
}else{
res.end();
}
},res,req);
},res,req);
}
});
/**
* API : Get HLS Stream
*/
app.get([config.webPaths.apiPrefix+':auth/hls/:ke/:id/:file',config.webPaths.apiPrefix+':auth/hls/:ke/:id/:channel/:file'], function (req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
req.fn=function(user){
s.checkChildProxy(req.params,function(){
req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/'
if(req.params.channel){
req.dir+='channel'+(parseInt(req.params.channel)+config.pipeAddition)+'/'+req.params.file;
}else{
req.dir+=req.params.file;
}
res.on('finish',function(){res.end();});
if (fs.existsSync(req.dir)){
fs.createReadStream(req.dir).pipe(res);
}else{
res.end(lang['File Not Found'])
}
},res,req)
}
s.auth(req.params,req.fn,res,req);
})
/**
* API : Get JPEG Snapshot
*/
app.get(config.webPaths.apiPrefix+':auth/jpeg/:ke/:id/s.jpg', function(req,res){
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors&&user.details.monitors.indexOf(req.params.id)===-1){
res.end(user.lang['Not Permitted'])
return
}
req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/s.jpg';
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
});
res.on('finish',function(){res.end();});
if (fs.existsSync(req.dir)){
fs.createReadStream(req.dir).pipe(res);
}else{
fs.createReadStream(config.defaultMjpeg).pipe(res);
}
},res,req);
},res,req);
});
/**
* API : Get FLV Stream
*/
app.get([config.webPaths.apiPrefix+':auth/flv/:ke/:id/s.flv',config.webPaths.apiPrefix+':auth/flv/:ke/:id/:channel/s.flv'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
var Emitter,chunkChannel
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
chunkChannel = parseInt(req.params.channel)+config.pipeAddition
}
if(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel]){
//variable name of contentWriter
var contentWriter
//set headers
res.setHeader('Content-Type', 'video/x-flv');
res.setHeader('Access-Control-Allow-Origin','*');
//write first frame on stream
res.write(s.group[req.params.ke].mon[req.params.id].firstStreamChunk[chunkChannel])
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
//write new frames as they happen
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
//remove contentWriter when client leaves
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
}else{
res.setHeader('Content-Type', 'application/json');
res.end(s.prettyPrint({ok:false,msg:'FLV not started or not ready'}))
}
},res,req)
},res,req)
})
/**
* API : Get H.265/h265 HEVC stream
*/
app.get([config.webPaths.apiPrefix+':auth/h265/:ke/:id/s.hevc',config.webPaths.apiPrefix+':auth/h265/:ke/:id/:channel/s.hevc'], function(req,res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
var Emitter,chunkChannel
if(!req.params.channel){
Emitter = s.group[req.params.ke].mon[req.params.id].emitter
chunkChannel = 'MAIN'
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.channel)+config.pipeAddition]
chunkChannel = parseInt(req.params.channel)+config.pipeAddition
}
//variable name of contentWriter
var contentWriter
//set headers
res.setHeader('Content-Type', 'video/mp4');
res.setHeader('Access-Control-Allow-Origin','*');
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
//write new frames as they happen
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
//remove contentWriter when client leaves
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
},res,req)
},res,req)
})
/**
* API : Get H.264 over HTTP
*/
app.get([
config.webPaths.apiPrefix+':auth/mpegts/:ke/:id/:feed/:file',
config.webPaths.apiPrefix+':auth/mpegts/:ke/:id/:feed/',
config.webPaths.apiPrefix+':auth/h264/:ke/:id/:feed/:file',
config.webPaths.apiPrefix+':auth/h264/:ke/:id/:feed',
config.webPaths.apiPrefix+':auth/h264/:ke/:id'
], function (req, res) {
res.header("Access-Control-Allow-Origin",req.headers.origin);
s.auth(req.params,function(user){
s.checkChildProxy(req.params,function(){
if(!req.query.feed){req.query.feed='1'}
var Emitter
if(!req.params.feed){
Emitter = s.group[req.params.ke].mon[req.params.id].streamIn[req.query.feed]
}else{
Emitter = s.group[req.params.ke].mon[req.params.id].emitterChannel[parseInt(req.params.feed)+config.pipeAddition]
}
var contentWriter
var date = new Date();
res.writeHead(200, {
'Date': date.toUTCString(),
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Content-Type': 'video/mp4',
'Server': 'Shinobi H.264 Test Stream',
})
var ip = s.getClientIp(req)
s.camera('watch_on',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
Emitter.on('data',contentWriter=function(buffer){
res.write(buffer)
})
res.on('close', function () {
Emitter.removeListener('data',contentWriter)
s.camera('watch_off',{
id : req.params.id,
ke : req.params.ke
},{
id : req.params.auth + ip + req.headers['user-agent']
})
})
},res,req);
},res,req);
});
}

411
libs/webServerSuperPaths.js Normal file
View file

@ -0,0 +1,411 @@
var fs = require('fs');
var os = require('os');
var moment = require('moment')
var request = require('request')
var jsonfile = require("jsonfile")
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var execSync = require('child_process').execSync;
module.exports = function(s,config,lang,app){
/**
* API : Superuser : Get Logs
*/
app.all([config.webPaths.supersuperApiPrefix+':auth/logs'], function (req,res){
req.ret={ok:false};
s.superAuth(req.params,function(resp){
req.sql='SELECT * FROM Logs WHERE ke=?';req.ar=['$'];
if(!req.params.id){
if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
req.or=[];
user.details.monitors.forEach(function(v,n){
req.or.push('mid=?');req.ar.push(v)
})
req.sql+=' AND ('+req.or.join(' OR ')+')'
}
}else{
if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1||req.params.id.indexOf('$')>-1){
req.sql+=' and mid=?';req.ar.push(req.params.id)
}else{
res.end('[]');
return;
}
}
if(req.query.start||req.query.end){
if(!req.query.startOperator||req.query.startOperator==''){
req.query.startOperator='>='
}
if(!req.query.endOperator||req.query.endOperator==''){
req.query.endOperator='<='
}
if(req.query.start && req.query.start !== '' && req.query.end && req.query.end !== ''){
req.query.start = s.stringToSqlTime(req.query.start)
req.query.end = s.stringToSqlTime(req.query.end)
req.sql+=' AND `time` '+req.query.startOperator+' ? AND `time` '+req.query.endOperator+' ?';
req.ar.push(req.query.start)
req.ar.push(req.query.end)
}else if(req.query.start && req.query.start !== ''){
req.query.start = s.stringToSqlTime(req.query.start)
req.sql+=' AND `time` '+req.query.startOperator+' ?';
req.ar.push(req.query.start)
}
}
if(!req.query.limit||req.query.limit==''){req.query.limit=50}
req.sql+=' ORDER BY `time` DESC LIMIT '+req.query.limit+'';
s.sqlQuery(req.sql,req.ar,function(err,r){
if(err){
err.sql=req.sql;
res.end(s.prettyPrint(err));
return
}
if(!r){r=[]}
r.forEach(function(v,n){
r[n].info=JSON.parse(v.info)
})
res.end(s.prettyPrint(r))
})
},res,req)
})
/**
* API : Superuser : Log delete.
*/
app.all(config.webPaths.superApiPrefix+':auth/logs/delete', function (req,res){
s.superAuth(req.params,function(resp){
s.sqlQuery('DELETE FROM Logs WHERE ke=?',['$'],function(){
var endData = {
ok : true
}
res.end(s.prettyPrint(endData))
})
},res,req)
})
/**
* API : Superuser : Update Shinobi
*/
app.all(config.webPaths.superApiPrefix+':auth/system/update', function (req,res){
s.superAuth(req.params,function(resp){
s.ffmpegKill()
s.systemLog('Shinobi ordered to update',{
by: resp.$user.mail,
ip: resp.ip
})
var updateProcess = spawn('sh',(s.mainDirectory+'/UPDATE.sh').split(' '),{detached: true})
updateProcess.stderr.on('data',function(data){
s.systemLog('Update Info',data.toString())
})
updateProcess.stdout.on('data',function(data){
s.systemLog('Update Info',data.toString())
})
var endData = {
ok : true
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Restart Shinobi
*/
app.all(config.webPaths.superApiPrefix+':auth/system/restart/:script', function (req,res){
s.superAuth(req.params,function(resp){
var check = function(x){return req.params.script.indexOf(x)>-1}
var endData = {
ok : true
}
if(check('system')){
s.systemLog('Shinobi ordered to restart',{by:resp.$user.mail,ip:resp.ip})
s.ffmpegKill()
endData.systemOuput = execSync('pm2 restart '+s.mainDirectory+'/camera.js')
}
if(check('cron')){
s.systemLog('Shinobi CRON ordered to restart',{by:resp.$user.mail,ip:resp.ip})
endData.cronOuput = execSync('pm2 restart '+s.mainDirectory+'/cron.js')
}
if(check('logs')){
s.systemLog('Flush PM2 Logs',{by:resp.$user.mail,ip:resp.ip})
endData.logsOuput = execSync('pm2 flush')
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Modify Configuration (conf.json)
*/
app.all(config.webPaths.superApiPrefix+':auth/system/configure', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
var postBody = s.getPostData(req)
if(!postBody){
endData.ok = false
endData.msg = lang.postDataBroken
}else{
s.systemLog('conf.json Modified',{
by: resp.$user.mail,
ip: resp.ip,
old:jsonfile.readFileSync(s.location.config)
})
jsonfile.writeFile(s.location.config,postBody,{spaces: 2},function(){
s.tx({f:'save_configuration'},'$')
})
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Get users in system
*/
app.all([
config.webPaths.superApiPrefix+':auth/accounts/list',
config.webPaths.superApiPrefix+':auth/accounts/list/:type',
], function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
searchQuery = 'SELECT ke,uid,auth,mail,details FROM Users'
queryVals = []
switch(req.params.type){
case'admin':case'administrator':
searchQuery += ' WHERE details NOT LIKE ?'
queryVals.push('%"sub"%')
break;
case'sub':case'subaccount':
searchQuery += ' WHERE details LIKE ?'
queryVals.push('%"sub"%')
break;
}
// ' WHERE details NOT LIKE ?'
s.sqlQuery(searchQuery,queryVals,function(err,users) {
endData.users = users
res.end(s.prettyPrint(endData))
})
},res,req)
})
/**
* API : Superuser : Save Superuser Preferences
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/saveSettings', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
var form = s.getPostData(req)
if(form){
var currentSuperUserList = jsonfile.readFileSync(s.location.super)
var currentSuperUser = {}
var currentSuperUserPosition = -1
//find this user in current list
currentSuperUserList.forEach(function(user,pos){
if(user.mail === resp.$user.mail){
currentSuperUser = user
currentSuperUserPosition = pos
}
})
var logDetails = {
by : resp.$user.mail,
ip : resp.ip
}
//check if pass and pass_again match, if not remove password
if(form.pass !== '' && form.pass === form.pass_again){
form.pass = s.createHash(form.pass)
}else{
delete(form.pass)
}
//delete pass_again from object
delete(form.pass_again)
//set new values
currentSuperUser = Object.assign(currentSuperUser,form)
//reset email and log change of email
if(form.mail !== resp.$user.mail){
logDetails.newEmail = form.mail
logDetails.oldEmail = resp.$user.mail
}
//log this change
s.systemLog('super.json Modified',logDetails)
//modify or add account in temporary master list
if(currentSuperUserList[currentSuperUserPosition]){
currentSuperUserList[currentSuperUserPosition] = currentSuperUser
}else{
currentSuperUserList.push(currentSuperUser)
}
//update master list in system
jsonfile.writeFile(s.location.super,currentSuperUserList,{spaces: 2},function(){
s.tx({f:'save_preferences'},'$')
})
}else{
endData.ok = false
endData.msg = lang.postDataBroken
}
res.end(s.prettyPrint(endData))
},res,req)
})
/**
* API : Superuser : Create Admin account (Account to manage cameras)
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/registerAdmin', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : false
}
var close = function(){
res.end(s.prettyPrint(endData))
}
var isCallbacking = false
var form = s.getPostData(req)
if(form){
if(form.mail !== '' && form.pass !== ''){
if(form.pass === form.password_again || form.pass === form.pass_again){
isCallbacking = true
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[form.mail],function(err,r) {
if(r&&r[0]){
//found address already exists
endData.msg = lang['Email address is in use.'];
}else{
endData.ok = true
//create new
//user id
form.uid = s.gid()
//check to see if custom key set
if(!form.ke||form.ke===''){
form.ke=s.gid()
}else{
form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
}
//check if "details" is object
if(form.details instanceof Object){
form.details = JSON.stringify(form.details)
}
//write user to db
s.sqlQuery(
'INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',
[
form.ke,
form.uid,
form.mail,
s.createHash(form.pass),
form.details
]
)
s.tx({f:'add_account',details:form.details,ke:form.ke,uid:form.uid,mail:form.mail},'$')
//init user
s.loadGroup(form)
}
close()
})
}else{
endData.msg = lang["Passwords Don't Match"]
}
}else{
endData.msg = lang['Email and Password fields cannot be empty']
}
}else{
endData.msg = lang.postDataBroken
}
if(isCallbacking === false)close()
},res,req)
})
/**
* API : Superuser : Edit Admin account (Account to manage cameras)
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/editAdmin', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : false
}
var close = function(){
res.end(s.prettyPrint(endData))
}
var form = s.getPostData(req)
if(form){
var account = s.getPostData(req,'account')
s.sqlQuery('SELECT * FROM Users WHERE mail=?',[account.mail],function(err,r) {
if(r && r[0]){
r = r[0]
var details = JSON.parse(r.details)
if(form.pass && form.pass !== ''){
if(form.pass === form.password_again){
form.pass = s.createHash(form.pass);
}else{
endData.msg = lang["Passwords Don't Match"]
close()
return
}
}else{
delete(form.pass);
}
delete(form.password_again);
var keys = Object.keys(form)
var set = []
var values = []
keys.forEach(function(v,n){
if(set==='ke'||set==='password_again'||!form[v]){return}
set.push(v+'=?')
if(v === 'details'){
form[v] = s.stringJSON(Object.assign(details,s.parseJSON(form[v])))
}
values.push(form[v])
})
values.push(account.mail)
s.sqlQuery('UPDATE Users SET '+set.join(',')+' WHERE mail=?',values,function(err,r) {
if(err){
console.log(err)
endData.error = err
endData.msg = lang.AccountEditText1
}else{
endData.ok = true
s.tx({f:'edit_account',form:form,ke:account.ke,uid:account.uid},'$')
delete(s.group[account.ke].init);
s.loadGroupApps(account)
}
close()
})
}
})
}else{
endData.msg = lang.postDataBroken
close()
}
},res,req)
})
/**
* API : Superuser : Delete Admin account (Account to manage cameras)
*/
app.all(config.webPaths.superApiPrefix+':auth/accounts/deleteAdmin', function (req,res){
s.superAuth(req.params,function(resp){
var endData = {
ok : true
}
var close = function(){
res.end(s.prettyPrint(endData))
}
var account = s.getPostData(req,'account')
s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[account.uid,account.ke,account.mail])
s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[account.uid,account.ke])
if(s.getPostData(req,'deleteSubAccounts',false) === '1'){
s.sqlQuery('DELETE FROM Users WHERE ke=?',[account.ke])
}
if(s.getPostData(req,'deleteMonitors',false) == '1'){
s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[account.ke],function(err,monitors){
if(monitors && monitors[0]){
monitors.forEach(function(monitor){
s.camera('stop',monitor)
})
s.sqlQuery('DELETE FROM Monitors WHERE ke=?',[account.ke])
}
})
}
if(s.getPostData(req,'deleteVideos',false) == '1'){
s.sqlQuery('DELETE FROM Videos WHERE ke=?',[account.ke])
fs.chmod(s.dir.videos+account.ke,0o777,function(err){
fs.unlink(s.dir.videos+account.ke,function(err){})
})
}
if(s.getPostData(req,'deleteEvents',false) == '1'){
s.sqlQuery('DELETE FROM Events WHERE ke=?',[account.ke])
}
s.tx({f:'delete_account',ke:account.ke,uid:account.uid,mail:account.mail},'$')
close()
},res,req)
})
}