UNPKG

221 kBJavaScriptView Raw
1//
2// Shinobi
3// Copyright (C) 2016 Moe Alam, moeiscool
4//
5//
6// # Donate
7//
8// If you like what I am doing here and want me to continue please consider donating :)
9// PayPal : paypal@m03.ca
10//
11process.on('uncaughtException', function (err) {
12 console.error('Uncaught Exception occured!');
13 console.error(err.stack);
14});
15var fs = require('fs');
16var os = require('os');
17var URL = require('url');
18var path = require('path');
19var mysql = require('mysql');
20var moment = require('moment');
21var request = require("request");
22var express = require('express');
23var app = express();
24var appHTTPS = express();
25var http = require('http');
26var https = require('https');
27var server = http.createServer(app);
28var bodyParser = require('body-parser');
29var CircularJSON = require('circular-json');
30var ejs = require('ejs');
31var io = new (require('socket.io'))();
32var execSync = require('child_process').execSync;
33var exec = require('child_process').exec;
34var spawn = require('child_process').spawn;
35var crypto = require('crypto');
36var webdav = require("webdav");
37var jsonfile = require("jsonfile");
38var connectionTester = require('connection-tester');
39var events = require('events');
40var Cam = require('onvif').Cam;
41var location = {}
42location.super = __dirname+'/super.json'
43location.config = __dirname+'/conf.json'
44location.languages = __dirname+'/languages'
45location.definitions = __dirname+'/definitions'
46var config = require(location.config);
47if(!config.productType){
48 config.productType='CE'
49}
50if(config.productType==='Pro'){
51 var LdapAuth = require('ldapauth-fork');
52}
53if(!config.language){
54 config.language='en_CA'
55}
56try{
57 var lang = require(location.languages+'/'+config.language+'.json');
58}catch(er){
59 console.error(er)
60 console.log('There was an error loading your language file.')
61 var lang = require(location.languages+'/en_CA.json');
62}
63try{
64 var definitions = require(location.definitions+'/'+config.language+'.json');
65}catch(er){
66 console.error(er)
67 console.log('There was an error loading your language file.')
68 var definitions = require(location.definitions+'/en_CA.json');
69}
70process.send = process.send || function () {};
71if(config.mail){
72 var nodemailer = require('nodemailer').createTransport(config.mail);
73}
74//config defaults
75if(config.cpuUsageMarker===undefined){config.cpuUsageMarker='%Cpu'}
76if(config.autoDropCache===undefined){config.autoDropCache=true}
77if(config.doSnapshot===undefined){config.doSnapshot=true}
78if(config.restart===undefined){config.restart={}}
79if(config.systemLog===undefined){config.systemLog=true}
80if(config.deleteCorruptFiles===undefined){config.deleteCorruptFiles=true}
81if(config.restart.onVideoNotExist===undefined){config.restart.onVideoNotExist=true}
82if(config.ip===undefined||config.ip===''||config.ip.indexOf('0.0.0.0')>-1){config.ip='localhost'}else{config.bindip=config.ip};
83if(config.cron===undefined)config.cron={};
84if(config.cron.deleteOverMax===undefined)config.cron.deleteOverMax=true;
85if(config.cron.deleteOverMaxOffset===undefined)config.cron.deleteOverMaxOffset=0.9;
86if(config.pluginKeys===undefined)config.pluginKeys={};
87s={factorAuth:{},child_help:false,totalmem:os.totalmem(),platform:os.platform(),s:JSON.stringify,isWin:(process.platform==='win32')};
88//load languages dynamically
89s.loadedLanguages={}
90s.loadedLanguages[config.language]=lang;
91s.getLanguageFile=function(rule){
92 if(rule&&rule!==''){
93 var file=s.loadedLanguages[file]
94 if(!file){
95 try{
96 s.loadedLanguages[rule]=require(location.languages+'/'+rule+'.json')
97 file=s.loadedLanguages[rule]
98 }catch(err){
99 file=lang
100 }
101 }
102 }else{
103 file=lang
104 }
105 return file
106}
107//load defintions dynamically
108s.loadedDefinitons={}
109s.loadedDefinitons[config.language]=definitions;
110s.getDefinitonFile=function(rule){
111 if(rule&&rule!==''){
112 var file=s.loadedDefinitons[file]
113 if(!file){
114 try{
115 s.loadedDefinitons[rule]=require(location.definitions+'/'+rule+'.json')
116 file=s.loadedDefinitons[rule]
117 }catch(err){
118 file=definitions
119 }
120 }
121 }else{
122 file=definitions
123 }
124 return file
125}
126s.connectSQL=function(){
127 sql = mysql.createConnection(config.db);
128 sql.connect(function(err){if(err){s.systemLog(lang['Error Connecting']+' : DB',err);setTimeout(s.connectSQL, 2000);}});
129 sql.on('error',function(err) {s.systemLog(lang['DB Lost.. Retrying..']);s.systemLog(err);s.connectSQL();return;});
130 sql.on('connect',function() {
131 s.sqlQuery = function(query,values,callback){
132 if(!values){values=[]}
133 return sql.query(query,values,function(err,r){
134 if(err)
135 s.systemLog('s.sqlQuery',err)
136 if(callback)
137 callback(err,r)
138 })
139 }
140 s.sqlQuery('ALTER TABLE `Videos` ADD COLUMN `details` TEXT NULL DEFAULT NULL AFTER `status`;',function(err){
141 if(err){
142 s.systemLog("Critical update 1/2 already applied");
143 }
144 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){
145 if(err){
146 s.systemLog("Critical update 2/2 NOT applied, this could be bad");
147 }else{
148 s.systemLog("Critical update 2/2 already applied");
149 }
150 });
151 });
152 });
153}
154s.connectSQL();
155//kill any ffmpeg running
156s.ffmpegKill=function(){
157 var cmd=''
158 if(s.isWin===true){
159 cmd="Taskkill /IM ffmpeg.exe /F"
160 }else{
161 cmd="ps aux | grep -ie ffmpeg | awk '{print $2}' | xargs kill -9"
162 }
163 exec(cmd,{detached: true})
164};
165process.on('exit',s.ffmpegKill.bind(null,{cleanup:true}));
166process.on('SIGINT',s.ffmpegKill.bind(null, {exit:true}));
167//key for child servers
168s.child_nodes={};
169s.child_key='3123asdasdf1dtj1hjk23sdfaasd12asdasddfdbtnkkfgvesra3asdsd3123afdsfqw345';
170s.checkRelativePath=function(x){
171 if(x.charAt(0)!=='/'){
172 x=__dirname+'/'+x
173 }
174 return x
175}
176s.checkCorrectPathEnding=function(x){
177 var length=x.length
178 if(x.charAt(length-1)!=='/'){
179 x=x+'/'
180 }
181 return x.replace('__DIR__',__dirname)
182}
183s.md5=function(x){return crypto.createHash('md5').update(x).digest("hex");}
184s.tx=function(z,y,x){if(x){return x.broadcast.to(y).emit('f',z)};io.to(y).emit('f',z);}
185s.cx=function(z,y,x){if(x){return x.broadcast.to(y).emit('c',z)};io.to(y).emit('c',z);}
186s.txWithSubPermissions=function(z,y,permissionChoices){
187 if(typeof permissionChoices==='string'){
188 permissionChoices=[permissionChoices]
189 }
190 if(s.group[z.ke]){
191 Object.keys(s.group[z.ke].users).forEach(function(v){
192 var user = s.group[z.ke].users[v]
193 if(user.details.sub){
194 if(user.details.allmonitors!=='1'){
195 var valid=0
196 var checked=permissionChoices.length
197 permissionChoices.forEach(function(b){
198 if(user.details[b].indexOf(z.mid)!==-1){
199 ++valid
200 }
201 })
202 if(valid===checked){
203 s.tx(z,user.cnid)
204 }
205 }else{
206 s.tx(z,user.cnid)
207 }
208 }else{
209 s.tx(z,user.cnid)
210 }
211 })
212 }
213}
214//load camera controller vars
215s.nameToTime=function(x){x=x.split('.')[0].split('T'),x[1]=x[1].replace(/-/g,':');x=x.join(' ');return x;}
216s.ratio=function(width,height,ratio){ratio = width / height;return ( Math.abs( ratio - 4 / 3 ) < Math.abs( ratio - 16 / 9 ) ) ? '4:3' : '16:9';}
217s.randomNumber=function(x){
218 if(!x){x=10};
219 return Math.floor((Math.random() * x) + 1);
220};
221s.gid=function(x){
222 if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
223 for( var i=0; i < x; i++ )
224 t += p.charAt(Math.floor(Math.random() * p.length));
225 return t;
226};
227s.nid=function(x){
228 if(!x){x=6};var t = "";var p = "0123456789";
229 for( var i=0; i < x; i++ )
230 t += p.charAt(Math.floor(Math.random() * p.length));
231 return t;
232};
233s.moment_withOffset=function(e,x){
234 if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
235 e=moment(e);if(config.utcOffset){e=e.utcOffset(config.utcOffset)}
236 return e.format(x);
237}
238s.moment=function(e,x){
239 if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
240 return moment(e).format(x);
241}
242s.ipRange=function(start_ip, end_ip) {
243 var start_long = s.toLong(start_ip);
244 var end_long = s.toLong(end_ip);
245 if (start_long > end_long) {
246 var tmp=start_long;
247 start_long=end_long
248 end_long=tmp;
249 }
250 var range_array = [];
251 var i;
252 for (i=start_long; i<=end_long;i++) {
253 range_array.push(s.fromLong(i));
254 }
255 return range_array;
256}
257s.portRange=function(lowEnd,highEnd){
258 var list = [];
259 for (var i = lowEnd; i <= highEnd; i++) {
260 list.push(i);
261 }
262 return list;
263}
264//toLong taken from NPM package 'ip'
265s.toLong=function(ip) {
266 var ipl = 0;
267 ip.split('.').forEach(function(octet) {
268 ipl <<= 8;
269 ipl += parseInt(octet);
270 });
271 return(ipl >>> 0);
272};
273
274//fromLong taken from NPM package 'ip'
275s.fromLong=function(ipl) {
276 return ((ipl >>> 24) + '.' +
277 (ipl >> 16 & 255) + '.' +
278 (ipl >> 8 & 255) + '.' +
279 (ipl & 255) );
280};
281s.kill=function(x,e,p){
282 if(s.group[e.ke]&&s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].spawn !== undefined){
283 if(s.group[e.ke].mon[e.id].spawn){
284 try{
285 s.group[e.ke].mon[e.id].spawn.removeListener('end',s.group[e.ke].mon[e.id].spawn_exit);
286 s.group[e.ke].mon[e.id].spawn.removeListener('exit',s.group[e.ke].mon[e.id].spawn_exit);
287 delete(s.group[e.ke].mon[e.id].spawn_exit);
288 }catch(er){}
289 }
290 clearTimeout(s.group[e.ke].mon[e.id].checker);
291 delete(s.group[e.ke].mon[e.id].checker);
292 clearTimeout(s.group[e.ke].mon[e.id].checkStream);
293 delete(s.group[e.ke].mon[e.id].checkStream);
294 clearTimeout(s.group[e.ke].mon[e.id].watchdog_stop);
295 delete(s.group[e.ke].mon[e.id].watchdog_stop);
296 if(e&&s.group[e.ke].mon[e.id].record){
297 clearTimeout(s.group[e.ke].mon[e.id].record.capturing);
298// if(s.group[e.ke].mon[e.id].record.request){s.group[e.ke].mon[e.id].record.request.abort();delete(s.group[e.ke].mon[e.id].record.request);}
299 };
300 if(s.group[e.ke].mon[e.id].child_node){
301 s.cx({f:'kill',d:s.init('noReference',e)},s.group[e.ke].mon[e.id].child_node_id)
302 }else{
303 if(!x||x===1){return};
304 p=x.pid;
305 if(s.group[e.ke].mon_conf[e.id].type===('dashcam'||'socket'||'jpeg'||'pipe')){
306 x.stdin.pause();setTimeout(function(){x.kill('SIGTERM');delete(x);},500)
307 }else{
308 try{
309 x.stdin.setEncoding('utf8');x.stdin.write('q');
310 }catch(er){}
311 }
312 setTimeout(function(){exec('kill -9 '+p,{detached: true})},1000)
313 }
314 }
315}
316//user log
317s.log=function(e,x){
318 if(!x||!e.mid){return}
319 if((e.details&&e.details.sqllog==='1')||e.mid.indexOf('$')>-1){
320 s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',[e.ke,e.mid,s.s(x)]);
321 }
322 s.tx({f:'log',ke:e.ke,mid:e.mid,log:x,time:moment()},'GRPLOG_'+e.ke);
323// s.systemLog('s.log : ',{f:'log',ke:e.ke,mid:e.mid,log:x,time:moment()},'GRP_'+e.ke)
324}
325//system log
326s.systemLog=function(q,w,e){
327 if(!w){w=''}
328 if(!e){e=''}
329 if(config.systemLog===true){
330 if(typeof q==='string'&&sql){
331 s.sqlQuery('INSERT INTO Logs (ke,mid,info) VALUES (?,?,?)',['$','$SYSTEM',s.s({type:q,msg:w})]);
332 s.tx({f:'log',log:{time:moment(),ke:'$',mid:'$SYSTEM',time:moment(),info:s.s({type:q,msg:w})}},'$');
333 }
334 return console.log(moment().format(),q,w,e)
335 }
336}
337//SSL options
338if(config.ssl&&config.ssl.key&&config.ssl.cert){
339 config.ssl.key=fs.readFileSync(s.checkRelativePath(config.ssl.key),'utf8')
340 config.ssl.cert=fs.readFileSync(s.checkRelativePath(config.ssl.cert),'utf8')
341 if(config.ssl.port===undefined){
342 config.ssl.port=443
343 }
344 if(config.ssl.bindip===undefined){
345 config.ssl.bindip=config.bindip
346 }
347 if(config.ssl.ca&&config.ssl.ca instanceof Array){
348 config.ssl.ca.forEach(function(v,n){
349 config.ssl.ca[n]=fs.readFileSync(s.checkRelativePath(v),'utf8')
350 })
351 }
352 var serverHTTPS = https.createServer(config.ssl,app);
353 serverHTTPS.listen(config.ssl.port,config.bindip,function(){
354 console.log('SSL '+lang.Shinobi+' - SSL PORT : '+config.ssl.port);
355 });
356 io.attach(serverHTTPS);
357}
358//start HTTP
359server.listen(config.port,config.bindip,function(){
360 console.log(lang.Shinobi+' - PORT : '+config.port);
361});
362io.attach(server);
363console.log('NODE.JS version : '+execSync("node -v"))
364//ffmpeg location
365if(!config.ffmpegDir){
366 if(s.isWin===true){
367 config.ffmpegDir=__dirname+'/ffmpeg/ffmpeg.exe'
368 }else{
369 config.ffmpegDir='ffmpeg'
370 }
371}
372s.ffmpegVersion=execSync(config.ffmpegDir+" -version").toString().split('Copyright')[0].replace('ffmpeg version','').trim()
373console.log('FFMPEG version : '+s.ffmpegVersion)
374if(s.ffmpegVersion.indexOf(': 2.')>-1){
375 s.systemLog('FFMPEG is too old : '+s.ffmpegVersion+', Needed : 3.2+',err)
376 return
377}
378//directories
379s.group={};
380if(!config.windowsTempDir&&s.isWin===true){config.windowsTempDir='C:/Windows/Temp'}
381if(!config.defaultMjpeg){config.defaultMjpeg=__dirname+'/web/libs/img/bg.jpg'}
382//default stream folder check
383if(!config.streamDir){
384 if(s.isWin===false){
385 config.streamDir='/dev/shm'
386 }else{
387 config.streamDir=config.windowsTempDir
388 }
389 if(!fs.existsSync(config.streamDir)){
390 config.streamDir=__dirname+'/streams/'
391 }else{
392 config.streamDir+='/streams/'
393 }
394}
395if(!config.videosDir){config.videosDir=__dirname+'/videos/'}
396if(!config.binDir){config.binDir=__dirname+'/fileBin/'}
397if(!config.addStorage){config.addStorage=[]}
398s.dir={
399 videos:s.checkCorrectPathEnding(config.videosDir),
400 streams:s.checkCorrectPathEnding(config.streamDir),
401 fileBin:s.checkCorrectPathEnding(config.binDir),
402 addStorage:config.addStorage,
403 languages:location.languages+'/'
404};
405//streams dir
406if(!fs.existsSync(s.dir.streams)){
407 fs.mkdirSync(s.dir.streams);
408}
409//videos dir
410if(!fs.existsSync(s.dir.videos)){
411 fs.mkdirSync(s.dir.videos);
412}
413//fileBin dir
414if(!fs.existsSync(s.dir.fileBin)){
415 fs.mkdirSync(s.dir.fileBin);
416}
417//additional storage areas
418s.dir.addStorage.forEach(function(v,n){
419 v.path=s.checkCorrectPathEnding(v.path)
420 if(!fs.existsSync(v.path)){
421 fs.mkdirSync(v.path);
422 }
423})
424////Camera Controller
425s.init=function(x,e,k,fn){
426 if(!e){e={}}
427 if(!k){k={}}
428 switch(x){
429 case 0://init camera
430 if(!s.group[e.ke]){s.group[e.ke]={}};
431 if(!s.group[e.ke].fileBin){s.group[e.ke].fileBin={}};
432 if(!s.group[e.ke].mon){s.group[e.ke].mon={}}
433 if(!s.group[e.ke].sizeChangeQueue){s.group[e.ke].sizeChangeQueue=[]}
434 if(!s.group[e.ke].sizePurgeQueue){s.group[e.ke].sizePurgeQueue=[]}
435 if(!s.group[e.ke].users){s.group[e.ke].users={}}
436 if(!s.group[e.ke].mon[e.mid]){s.group[e.ke].mon[e.mid]={}}
437 if(!s.group[e.ke].mon[e.mid].streamIn){s.group[e.ke].mon[e.mid].streamIn={}};
438 if(!s.group[e.ke].mon[e.mid].watch){s.group[e.ke].mon[e.mid].watch={}};
439 if(!s.group[e.ke].mon[e.mid].fixingVideos){s.group[e.ke].mon[e.mid].fixingVideos={}};
440 if(!s.group[e.ke].mon[e.mid].record){s.group[e.ke].mon[e.mid].record={yes:e.record}};
441 if(!s.group[e.ke].mon[e.mid].started){s.group[e.ke].mon[e.mid].started=0};
442 if(s.group[e.ke].mon[e.mid].delete){clearTimeout(s.group[e.ke].mon[e.mid].delete)}
443 if(!s.group[e.ke].mon_conf){s.group[e.ke].mon_conf={}}
444 s.init('apps',e)
445 break;
446 case'apps':
447 if(!s.group[e.ke].init){
448 s.group[e.ke].init={};
449 }
450 if(!s.group[e.ke].webdav||!s.group[e.ke].sizeLimit){
451 s.sqlQuery('SELECT * FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(ar,r){
452 if(r&&r[0]){
453 r=r[0];
454 ar=JSON.parse(r.details);
455 //owncloud/webdav
456 if(ar.webdav_user&&
457 ar.webdav_user!==''&&
458 ar.webdav_pass&&
459 ar.webdav_pass!==''&&
460 ar.webdav_url&&
461 ar.webdav_url!==''
462 ){
463 if(!ar.webdav_dir||ar.webdav_dir===''){
464 ar.webdav_dir='/';
465 if(ar.webdav_dir.slice(-1)!=='/'){ar.webdav_dir+='/';}
466 }
467 s.group[e.ke].webdav = webdav(
468 ar.webdav_url,
469 ar.webdav_user,
470 ar.webdav_pass
471 );
472 }
473 Object.keys(ar).forEach(function(v){
474 s.group[e.ke].init[v]=ar[v]
475 })
476 }
477 });
478 }
479 break;
480 case'sync':
481 e.cn=Object.keys(s.child_nodes);
482 e.cn.forEach(function(v){
483 if(s.group[e.ke]){
484 s.cx({f:'sync',sync:s.init('noReference',s.group[e.ke].mon[e.mid]),ke:e.ke,mid:e.mid},s.child_nodes[v].cnid);
485 }
486 });
487 break;
488 case'noReference':
489 x={keys:Object.keys(e),ar:{}};
490 x.keys.forEach(function(v){
491 if(v!=='last_frame'&&v!=='record'&&v!=='spawn'&&v!=='running'&&(v!=='time'&&typeof e[v]!=='function')){x.ar[v]=e[v];}
492 });
493 return x.ar;
494 break;
495 case'url':
496 //build a complete url from pieces
497 e.authd='';
498 if(e.details.muser&&e.details.muser!==''&&e.host.indexOf('@')===-1) {
499 e.authd=e.details.muser+':'+e.details.mpass+'@';
500 }
501 if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port}
502 e.url=e.protocol+'://'+e.authd+e.host+e.porty+e.path;return e.url;
503 break;
504 case'url_no_path':
505 e.authd='';
506 if(!e.details.muser){e.details.muser=''}
507 if(!e.details.mpass){e.details.mpass=''}
508 if(e.details.muser!==''&&e.host.indexOf('@')===-1) {
509 e.authd=e.details.muser+':'+e.details.mpass+'@';
510 }
511 if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port}
512 e.url=e.protocol+'://'+e.authd+e.host+e.porty;return e.url;
513 break;
514 case'diskUsedEmit':
515 //send the amount used disk space to connected users
516 if(s.group[e.ke]&&s.group[e.ke].init){
517 s.tx({f:'diskUsed',size:s.group[e.ke].usedSpace,limit:s.group[e.ke].sizeLimit},'GRP_'+e.ke);
518 }
519 break;
520 case'diskUsedSet':
521 //`k` will be used as the value to add or substract
522 s.group[e.ke].sizeChangeQueue.push(k)
523 if(s.group[e.ke].sizeChanging!==true){
524 //lock this function
525 s.group[e.ke].sizeChanging=true
526 //validate current values
527 if(!s.group[e.ke].usedSpace){s.group[e.ke].usedSpace=0}else{s.group[e.ke].usedSpace=parseFloat(s.group[e.ke].usedSpace)}
528 if(s.group[e.ke].usedSpace<0){s.group[e.ke].usedSpace=0}
529 //set queue processor
530 var checkQueue=function(){
531 //get first in queue
532 var currentChange = s.group[e.ke].sizeChangeQueue[0]
533 //change global size value
534 s.group[e.ke].usedSpace=s.group[e.ke].usedSpace+currentChange
535 //remove value just used from queue
536 s.group[e.ke].sizeChangeQueue = s.group[e.ke].sizeChangeQueue.splice(1,s.group[e.ke].sizeChangeQueue.length+10)
537 //do next one
538 if(s.group[e.ke].sizeChangeQueue.length>0){
539 checkQueue()
540 }else{
541 s.group[e.ke].sizeChanging=false
542 s.init('diskUsedEmit',e)
543 }
544 }
545 checkQueue()
546 }
547 break;
548 }
549 if(typeof e.callback==='function'){setTimeout(function(){e.callback()},500);}
550}
551s.filter=function(x,d){
552 switch(x){
553 case'archive':
554 d.videos.forEach(function(v,n){
555 s.video('archive',v)
556 })
557 break;
558 case'email':
559 if(d.videos&&d.videos.length>0){
560 d.videos.forEach(function(v,n){
561
562 })
563 d.mailOptions = {
564 from: '"ShinobiCCTV" <no-reply@shinobi.video>', // sender address
565 to: d.mail, // list of receivers
566 subject: lang['Filter Matches']+' : '+d.name, // Subject line
567 html: lang.FilterMatchesText1+' '+d.videos.length+' '+lang.FilterMatchesText2,
568 };
569 if(d.execute&&d.execute!==''){
570 d.mailOptions.html+='<div><b>'+lang.Executed+' :</b> '+d.execute+'</div>'
571 }
572 if(d.delete==='1'){
573 d.mailOptions.html+='<div><b>'+lang.Deleted+' :</b> '+lang.Yes+'</div>'
574 }
575 d.mailOptions.html+='<div><b>'+lang.Query+' :</b> '+d.query+'</div>'
576 d.mailOptions.html+='<div><b>'+lang['Filter ID']+' :</b> '+d.id+'</div>'
577 nodemailer.sendMail(d.mailOptions, (error, info) => {
578 if (error) {
579 s.tx({f:'error',ff:'filter_mail',ke:d.ke,error:error},'GRP_'+d.ke);
580 return ;
581 }
582 s.tx({f:'filter_mail',ke:d.ke,info:info},'GRP_'+d.ke);
583 });
584 }
585 break;
586 case'delete':
587 d.videos.forEach(function(v,n){
588 s.video('delete',v)
589 })
590 break;
591 case'execute':
592 exec(d.execute,{detached: true})
593 break;
594 }
595}
596s.video=function(x,e){
597 if(!e){e={}};
598 switch(x){
599 case'getDir':
600 if(e.mid&&!e.id){e.id=e.mid};
601 if(e.details&&(e.details instanceof Object)===false){
602 try{e.details=JSON.parse(e.details)}catch(err){}
603 }
604 if(e.details&&e.details.dir&&e.details.dir!==''){
605 return s.checkCorrectPathEnding(e.details.dir)+e.ke+'/'+e.id+'/'
606 }else{
607 return s.dir.videos+e.ke+'/'+e.id+'/';
608 }
609 break;
610 }
611 var k={}
612 if(x!=='getDir'){e.dir=s.video('getDir',e)}
613 switch(x){
614 case'fix':
615 e.sdir=s.dir.streams+e.ke+'/'+e.id+'/';
616 if(!e.filename&&e.time){e.filename=s.moment(e.time)}
617 if(e.filename.indexOf('.')===-1){
618 e.filename=e.filename+'.'+e.ext
619 }
620 s.tx({f:'video_fix_start',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke)
621 s.group[e.ke].mon[e.id].fixingVideos[e.filename]={}
622 switch(e.ext){
623 case'mp4':
624 e.fixFlags='-vcodec libx264 -acodec aac -strict -2';
625 break;
626 case'webm':
627 e.fixFlags='-vcodec libvpx -acodec libvorbis';
628 break;
629 }
630 e.spawn=spawn(config.ffmpegDir,('-i '+e.dir+e.filename+' '+e.fixFlags+' '+e.sdir+e.filename).split(' '),{detached: true})
631 e.spawn.stdout.on('data',function(data){
632 s.tx({f:'video_fix_data',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke)
633 });
634 e.spawn.on('close',function(data){
635 exec('mv '+e.dir+e.filename+' '+e.sdir+e.filename,{detached: true}).on('exit',function(){
636 s.tx({f:'video_fix_success',mid:e.mid,ke:e.ke,filename:e.filename},'GRP_'+e.ke)
637 delete(s.group[e.ke].mon[e.id].fixingVideos[e.filename]);
638 })
639 });
640 break;
641 case'archive':
642 if(!e.filename&&e.time){e.filename=s.moment(e.time)}
643 if(!e.status){e.status=0}
644 e.details.archived="1"
645 e.save=[JSON.stringify(e.details),e.id,e.ke,s.nameToTime(e.filename)];
646 s.sqlQuery('UPDATE Videos SET details=? WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(err,r){
647 s.tx({f:'video_edit',status:3,filename:e.filename+'.'+e.ext,mid:e.mid,ke:e.ke,time:s.nameToTime(e.filename)},'GRP_'+e.ke);
648 });
649 break;
650 case'delete':
651 if(!e.filename&&e.time){e.filename=s.moment(e.time)}
652 if(!e.status){e.status=0}
653 e.save=[e.id,e.ke,s.nameToTime(e.filename)];
654 s.sqlQuery('SELECT * FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(err,r){
655 if(r&&r[0]){
656 r=r[0]
657 e.dir=s.video('getDir',r)
658 s.sqlQuery('DELETE FROM Videos WHERE `mid`=? AND `ke`=? AND `time`=?',e.save,function(){
659 fs.stat(e.dir+e.filename+'.'+e.ext,function(err,file){
660 if(err){
661 s.systemLog('File Delete Error : '+e.ke+' : '+' : '+e.mid,err)
662 }
663 s.init('diskUsedSet',e,-(r.size/1000000))
664 })
665 s.tx({f:'video_delete',filename:e.filename+'.'+e.ext,mid:e.mid,ke:e.ke,time:s.nameToTime(e.filename),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
666 s.file('delete',e.dir+e.filename+'.'+e.ext)
667 })
668 }
669 })
670 break;
671 case'open':
672 //on video open
673 e.save=[e.id,e.ke,s.nameToTime(e.filename),e.ext];
674 if(!e.status){e.save.push(0)}else{e.save.push(e.status)}
675 k.details={}
676 if(e.details&&e.details.dir&&e.details.dir!==''){
677 k.details.dir=e.details.dir
678 }
679 e.save.push(s.s(k.details))
680 s.sqlQuery('INSERT INTO Videos (mid,ke,time,ext,status,details) VALUES (?,?,?,?,?,?)',e.save)
681 s.tx({f:'video_build_start',filename:e.filename+'.'+e.ext,mid:e.id,ke:e.ke,time:s.nameToTime(e.filename),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
682 break;
683 case'close':
684 //video function : close
685 if(s.group[e.ke]&&s.group[e.ke].mon[e.id]){
686 if(s.group[e.ke].mon[e.id].open&&!e.filename){e.filename=s.group[e.ke].mon[e.id].open;e.ext=s.group[e.ke].mon[e.id].open_ext}
687 if(s.group[e.ke].mon[e.id].child_node){
688 s.cx({f:'close',d:s.init('noReference',e)},s.group[e.ke].mon[e.id].child_node_id);
689 }else{
690 k.file=e.filename+'.'+e.ext
691 k.dir=e.dir.toString()
692 k.fileExists=fs.existsSync(k.dir+k.file)
693 if(k.fileExists!==true){
694 k.dir=s.dir.videos+'/'+e.ke+'/'+e.id+'/'
695 k.fileExists=fs.existsSync(k.dir+k.file)
696 if(k.fileExists!==true){
697 s.dir.addStorage.forEach(function(v){
698 if(k.fileExists!==true){
699 k.dir=s.checkCorrectPathEnding(v.path)+e.ke+'/'+e.id+'/'
700 k.fileExists=fs.existsSync(k.dir+k.file)
701 }
702 })
703 }
704 }
705 if(k.fileExists===true){
706 k.stat=fs.statSync(k.dir+k.file);
707 e.filesize=k.stat.size;
708 e.filesizeMB=parseFloat((e.filesize/1000000).toFixed(2));
709 e.end_time=s.moment(k.stat.mtime,'YYYY-MM-DD HH:mm:ss');
710 e.save=[e.filesize,1,e.end_time,e.id,e.ke,s.nameToTime(e.filename)];
711 if(!e.status){e.save.push(0)}else{e.save.push(e.status)}
712 s.sqlQuery('UPDATE Videos SET `size`=?,`status`=?,`end`=? WHERE `mid`=? AND `ke`=? AND `time`=? AND `status`=?',e.save)
713 s.txWithSubPermissions({f:'video_build_success',hrefNoAuth:'/videos/'+e.ke+'/'+e.mid+'/'+k.file,filename:k.file,mid:e.id,ke:e.ke,time:moment(s.nameToTime(e.filename)).format(),size:e.filesize,end:moment(e.end_time).format()},'GRP_'+e.ke,'video_view');
714
715 //cloud auto savers
716 //webdav
717 if(s.group[e.ke].webdav&&s.group[e.ke].init.use_webdav!=='0'&&s.group[e.ke].init.webdav_save=="1"){
718 fs.readFile(k.dir+k.file,function(err,data){
719 s.group[e.ke].webdav.putFileContents(s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'/'+k.file,"binary",data)
720 .catch(function(err) {
721 s.log(e,{type:lang['Webdav Error'],msg:{msg:lang.WebdavErrorText+' <b>/'+e.ke+'/'+e.id+'</b>',info:err},ffmpeg:s.group[e.ke].mon[e.id].ffmpeg})
722 console.error(err);
723 });
724 });
725 }
726 if(s.group[e.ke].init){
727 s.init('diskUsedSet',e,e.filesizeMB)
728 if(config.cron.deleteOverMax===true){
729 //check space
730 s.group[e.ke].sizePurgeQueue.push(1)
731 if(s.group[e.ke].sizePurging!==true){
732 //lock this function
733 s.group[e.ke].sizePurging=true
734 //set queue processor
735 var finish=function(){
736// console.log('checkQueueOne',s.group[e.ke].sizePurgeQueue.length)
737 //remove value just used from queue
738 s.group[e.ke].sizePurgeQueue = s.group[e.ke].sizePurgeQueue.splice(1,s.group[e.ke].sizePurgeQueue.length+10)
739 //do next one
740 if(s.group[e.ke].sizePurgeQueue.length>0){
741 checkQueue()
742 }else{
743// console.log('checkQueueFinished',s.group[e.ke].sizePurgeQueue.length)
744 s.group[e.ke].sizePurging=false
745 s.init('diskUsedEmit',e)
746 }
747 }
748 var checkQueue=function(){
749// console.log('checkQueue',config.cron.deleteOverMaxOffset)
750 //get first in queue
751 var currentPurge = s.group[e.ke].sizePurgeQueue[0]
752 var deleteVideos = function(){
753// console.log(s.group[e.ke].usedSpace>(s.group[e.ke].sizeLimit*config.cron.deleteOverMaxOffset))
754 //run purge command
755 if(s.group[e.ke].usedSpace>(s.group[e.ke].sizeLimit*config.cron.deleteOverMaxOffset)){
756 s.sqlQuery('SELECT * FROM Videos WHERE status != 0 AND details NOT LIKE \'%"archived":"1"%\' AND ke=? ORDER BY `time` ASC LIMIT 2',[e.ke],function(err,evs){
757 k.del=[];k.ar=[e.ke];
758 evs.forEach(function(ev){
759 ev.dir=s.video('getDir',ev)+s.moment(ev.time)+'.'+ev.ext;
760 k.del.push('(mid=? AND time=?)');
761 k.ar.push(ev.mid),k.ar.push(ev.time);
762 s.file('delete',ev.dir);
763 s.init('diskUsedSet',e,-(ev.size/1000000))
764 s.tx({f:'video_delete',ff:'over_max',filename:s.moment(ev.time)+'.'+ev.ext,mid:ev.mid,ke:ev.ke,time:ev.time,end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+e.ke);
765 });
766 if(k.del.length>0){
767 k.qu=k.del.join(' OR ');
768 s.sqlQuery('DELETE FROM Videos WHERE ke =? AND ('+k.qu+')',k.ar,function(){
769 deleteVideos()
770 })
771 }else{
772 finish()
773 }
774 })
775 }else{
776 finish()
777 }
778 }
779 deleteVideos()
780 }
781 checkQueue()
782 }
783 }else{
784 s.init('diskUsedEmit',e)
785 }
786 }
787 }else{
788 s.video('delete',e);
789 s.log(e,{type:lang['File Not Exist'],msg:lang.FileNotExistText,ffmpeg:s.group[e.ke].mon[e.id].ffmpeg})
790 if(e.mode&&config.restart.onVideoNotExist===true&&e.fn){
791 delete(s.group[e.ke].mon[e.id].open);
792 s.log(e,{type:lang['Camera is not recording'],msg:{msg:lang.CameraNotRecordingText}});
793 if(s.group[e.ke].mon[e.id].started===1){
794 s.camera('restart',e)
795 }
796 }
797 }
798 }
799 }
800 delete(s.group[e.ke].mon[e.id].open);
801 break;
802 }
803}
804s.ffmpeg=function(e,x){
805 //set X for temporary values so we don't break our main monitor object.
806 if(!x){x={tmp:''}}
807 //set some placeholding values to avoid "undefined" in ffmpeg string.
808 x.record_string=''
809 x.cust_input=''
810 x.cust_detect=' '
811 x.record_video_filters=[]
812 x.stream_video_filters=[]
813 x.hwaccel=''
814 //input - analyze duration
815 if(e.details.aduration&&e.details.aduration!==''){x.cust_input+=' -analyzeduration '+e.details.aduration};
816 //input - probe size
817 if(e.details.probesize&&e.details.probesize!==''){x.cust_input+=' -probesize '+e.details.probesize};
818 //input - check protocol
819 //input
820 switch(e.type){
821 case'h264':
822 switch(e.protocol){
823 case'rtsp':
824 if(e.details.rtsp_transport&&e.details.rtsp_transport!==''&&e.details.rtsp_transport!=='no'){x.cust_input+=' -rtsp_transport '+e.details.rtsp_transport;}
825 break;
826 }
827 break;
828 }
829 //record - resolution
830 switch(s.ratio(e.width,e.height)){
831 case'16:9':
832 x.ratio='640x360';
833 break;
834 default:
835 x.ratio='640x480';
836 break;
837 }
838 if(e.width!==''&&e.height!==''&&!isNaN(e.width)&&!isNaN(e.height)){
839 x.record_dimensions=' -s '+e.width+'x'+e.height
840 }else{
841 x.record_dimensions=''
842 }
843 if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
844 x.ratio=e.details.stream_scale_x+'x'+e.details.stream_scale_y;
845 }
846 //record - segmenting
847 x.segment=' -f segment -segment_atclocktime 1 -reset_timestamps 1 -strftime 1 -segment_list pipe:2 -segment_time '+(60*e.cutoff)+' ';
848 //record - check for double quotes
849 if(e.details.dqf=='1'){
850 x.segment+='"'+e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext+'"';
851 }else{
852 x.segment+=e.dir+'%Y-%m-%dT%H-%M-%S.'+e.ext;
853 }
854 //record - set defaults for extension, video quality
855 switch(e.ext){
856 case'mp4':
857 x.vcodec='libx264';x.acodec='aac';
858 if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -crf '+e.details.crf}
859 break;
860 case'webm':
861 x.acodec='libvorbis',x.vcodec='libvpx';
862 if(e.details.crf&&e.details.crf!==''){x.vcodec+=' -q:v '+e.details.crf}else{x.vcodec+=' -q:v 1';}
863 break;
864 }
865 //record - use custom video codec
866 if(e.details.vcodec&&e.details.vcodec!==''&&e.details.vcodec!=='default'){x.vcodec=e.details.vcodec}
867 //record - use custom audio codec
868 if(e.details.acodec&&e.details.acodec!==''&&e.details.acodec!=='default'){x.acodec=e.details.acodec}
869 if(e.details.cust_record){
870 if(x.acodec=='aac'&&e.details.cust_record.indexOf('-strict -2')===-1){e.details.cust_record+=' -strict -2';}
871 if(e.details.cust_record.indexOf('-threads')===-1){e.details.cust_record+=' -threads 1';}
872 }
873// 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';}
874 //record - ready or reset codecs
875 if(x.acodec!=='no'){
876 if(x.acodec.indexOf('none')>-1){x.acodec=''}else{x.acodec=' -acodec '+x.acodec}
877 }else{
878 x.acodec=' -an'
879 }
880 if(x.vcodec.indexOf('none')>-1){x.vcodec=''}else{x.vcodec=' -vcodec '+x.vcodec}
881 //stream - frames per second
882 if(!e.details.sfps||e.details.sfps===''){
883 e.details.sfps=parseFloat(e.details.sfps);
884 if(isNaN(e.details.sfps)){e.details.sfps=1}
885 }
886 if(e.fps&&e.fps!==''){x.framerate=' -r '+e.fps}else{x.framerate=''}
887 if(e.details.stream_fps&&e.details.stream_fps!==''){x.stream_fps=' -r '+e.details.stream_fps}else{x.stream_fps=''}
888 //record - timestamp options for -vf
889 if(e.details.timestamp&&e.details.timestamp=="1"&&e.details.vcodec!=='copy'){
890 //font
891 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'}
892 //position x
893 if(e.details.timestamp_x&&e.details.timestamp_x!==''){x.timex=e.details.timestamp_x}else{x.timex='(w-tw)/2'}
894 //position y
895 if(e.details.timestamp_y&&e.details.timestamp_y!==''){x.timey=e.details.timestamp_y}else{x.timey='0'}
896 //text color
897 if(e.details.timestamp_color&&e.details.timestamp_color!==''){x.time_color=e.details.timestamp_color}else{x.time_color='white'}
898 //box color
899 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'}
900 //text size
901 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'}
902
903 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);
904 }
905 //record - watermark for -vf
906 if(e.details.watermark&&e.details.watermark=="1"&&e.details.watermark_location&&e.details.watermark_location!==''){
907 switch(e.details.watermark_position){
908 case'tl'://top left
909 x.watermark_position='10:10'
910 break;
911 case'tr'://top right
912 x.watermark_position='main_w-overlay_w-10:10'
913 break;
914 case'bl'://bottom left
915 x.watermark_position='10:main_h-overlay_h-10'
916 break;
917 default://bottom right
918 x.watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
919 break;
920 }
921 x.record_video_filters.push('movie='+e.details.watermark_location+'[watermark],[in][watermark]overlay='+x.watermark_position+'[out]');
922 }
923 //record - rotation
924 if(e.details.rotate_record&&e.details.rotate_record!==""&&e.details.rotate_record!=="no"&&e.details.stream_vcodec!=="copy"){
925 x.record_video_filters.push('transpose='+e.details.rotate_record);
926 }
927 //check custom record filters for -vf
928 if(e.details.vf&&e.details.vf!==''){
929 x.record_video_filters.push(e.details.vf)
930 }
931 //compile filter string for -vf
932 if(x.record_video_filters.length>0){
933 x.record_video_filters=' -vf '+x.record_video_filters.join(',')
934 }else{
935 x.record_video_filters=''
936 }
937 //stream - timestamp
938 if(e.details.stream_timestamp&&e.details.stream_timestamp=="1"&&e.details.vcodec!=='copy'){
939 //font
940 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'}
941 //position x
942 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'}
943 //position y
944 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'}
945 //text color
946 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'}
947 //box color
948 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'}
949 //text size
950 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'}
951
952 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);
953 }
954 //stream - watermark for -vf
955 if(e.details.stream_watermark&&e.details.stream_watermark=="1"&&e.details.stream_watermark_location&&e.details.stream_watermark_location!==''){
956 switch(e.details.stream_watermark_position){
957 case'tl'://top left
958 x.stream_watermark_position='10:10'
959 break;
960 case'tr'://top right
961 x.stream_watermark_position='main_w-overlay_w-10:10'
962 break;
963 case'bl'://bottom left
964 x.stream_watermark_position='10:main_h-overlay_h-10'
965 break;
966 default://bottom right
967 x.stream_watermark_position='(main_w-overlay_w-10)/2:(main_h-overlay_h-10)/2'
968 break;
969 }
970 x.stream_video_filters.push('movie='+e.details.stream_watermark_location+'[watermark],[in][watermark]overlay='+x.stream_watermark_position+'[out]');
971 }
972 //stream - rotation
973 if(e.details.rotate_stream&&e.details.rotate_stream!==""&&e.details.rotate_stream!=="no"){
974 x.stream_video_filters.push('transpose='+e.details.rotate_stream);
975 }
976 //stream - hls vcodec
977 if(e.details.stream_vcodec&&e.details.stream_vcodec!=='no'){
978 if(e.details.stream_vcodec!==''){x.stream_vcodec=' -c:v '+e.details.stream_vcodec}else{x.stream_vcodec=' -c:v libx264'}
979 }else{
980 x.stream_vcodec='';
981 }
982 //stream - hls acodec
983 if(e.details.stream_acodec!=='no'){
984 if(e.details.stream_acodec&&e.details.stream_acodec!==''){x.stream_acodec=' -c:a '+e.details.stream_acodec}else{x.stream_acodec=''}
985 }else{
986 x.stream_acodec=' -an';
987 }
988 //stream - hls segment time
989 if(e.details.hls_time&&e.details.hls_time!==''){x.hls_time=e.details.hls_time}else{x.hls_time="2"} //hls list size
990 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}
991 //stream - custom flags
992 if(e.details.cust_stream&&e.details.cust_stream!==''){x.cust_stream=' '+e.details.cust_stream}else{x.cust_stream=''}
993 //stream - preset
994 if(e.details.preset_stream&&e.details.preset_stream!==''){x.preset_stream=' -preset '+e.details.preset_stream;}else{x.preset_stream=''}
995 //stream - quality
996 if(e.details.stream_quality&&e.details.stream_quality!==''){x.stream_quality=e.details.stream_quality}else{x.stream_quality=''}
997 //hardware acceleration
998 if(e.details.accelerator&&e.details.accelerator==='1'){
999 if(e.details.hwaccel&&e.details.hwaccel!==''){
1000 x.hwaccel+=' -hwaccel '+e.details.hwaccel;
1001 }
1002 if(e.details.hwaccel_vcodec&&e.details.hwaccel_vcodec!==''){
1003 x.hwaccel+=' -c:v '+e.details.hwaccel_vcodec;
1004 }
1005 if(e.details.hwaccel_device&&e.details.hwaccel_device!==''){
1006 switch(e.details.hwaccel){
1007 case'vaapi':
1008 x.hwaccel+=' -vaapi_device '+e.details.hwaccel_device;
1009 break;
1010 default:
1011 x.hwaccel+=' -hwaccel_device '+e.details.hwaccel_device;
1012 break;
1013 }
1014 }
1015// else{
1016// if(e.details.hwaccel==='vaapi'){
1017// x.hwaccel+=' -hwaccel_device 0';
1018// }
1019// }
1020 }
1021 if(e.details.stream_vcodec==='h264_vaapi'){
1022 x.stream_video_filters=[]
1023 x.stream_video_filters.push('format=nv12|vaapi');
1024 if(e.details.stream_scale_x&&e.details.stream_scale_x!==''&&e.details.stream_scale_y&&e.details.stream_scale_y!==''){
1025 x.stream_video_filters.push('scale_vaapi=w='+e.details.stream_scale_x+':h='+e.details.stream_scale_y)
1026 }
1027 }
1028 //stream - video filter
1029 if(e.details.svf&&e.details.svf!==''){
1030 x.stream_video_filters.push(e.details.svf)
1031 }
1032 if(x.stream_video_filters.length>0){
1033 x.stream_video_filters=' -vf '+x.stream_video_filters.join(',')
1034 }else{
1035 x.stream_video_filters=''
1036 }
1037 //stream - pipe build
1038 switch(e.details.stream_type){
1039 case'flv':
1040 if(e.details.stream_vcodec!=='copy'){
1041 if(x.cust_stream.indexOf('-s ')===-1){x.cust_stream+=' -s '+x.ratio}
1042 x.cust_stream+=x.stream_fps
1043 if(x.stream_quality)x.stream_quality=' -crf '+x.stream_quality;
1044 x.cust_stream+=x.stream_quality
1045 x.cust_stream+=x.preset_stream
1046 }
1047 x.pipe=' -f flv'+x.stream_acodec+x.stream_vcodec+x.stream_video_filters+x.cust_stream+' pipe:1';
1048 break;
1049 case'hls':
1050 if(e.details.stream_vcodec!=='h264_vaapi'){
1051 if(x.stream_quality)x.stream_quality=' -crf '+x.stream_quality;
1052 if(x.cust_stream.indexOf('-tune')===-1){x.cust_stream+=' -tune zerolatency'}
1053 if(x.cust_stream.indexOf('-g ')===-1){x.cust_stream+=' -g 1'}
1054 }
1055 x.pipe=x.preset_stream+x.stream_quality+x.stream_acodec+x.stream_vcodec+x.stream_fps+' -f hls -s '+x.ratio+x.stream_video_filters+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';
1056 break;
1057 case'mjpeg':
1058 if(x.stream_quality)x.stream_quality=' -q:v '+x.stream_quality;
1059 x.pipe=' -c:v mjpeg -f mpjpeg -boundary_tag shinobi'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1';
1060 break;
1061 case'b64':case'':case undefined:case null://base64
1062 if(x.stream_quality)x.stream_quality=' -q:v '+x.stream_quality;
1063 x.pipe=' -c:v mjpeg -f image2pipe'+x.cust_stream+x.stream_video_filters+x.stream_quality+x.stream_fps+' -s '+x.ratio+' pipe:1';
1064 break;
1065 default:
1066 x.pipe=''
1067 break;
1068 }
1069 //detector - plugins, motion
1070 if(e.details.detector==='1'&&e.details.detector_send_frames==='1'){
1071 if(!e.details.detector_fps||e.details.detector_fps===''){e.details.detector_fps=2}
1072 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'}
1073 if(e.details.cust_detect&&e.details.cust_detect!==''){x.cust_detect+=e.details.cust_detect;}
1074 x.pipe+=' -f singlejpeg -vf fps='+e.details.detector_fps+x.cust_detect+x.dratio+' pipe:0';
1075 }
1076 //api - snapshot bin/ cgi.bin (JPEG Mode)
1077 if(e.details.snap==='1'||e.details.stream_type==='jpeg'){
1078 if(!e.details.snap_fps||e.details.snap_fps===''){e.details.snap_fps=1}
1079 if(e.details.snap_vf&&e.details.snap_vf!==''){x.snap_vf=' -vf '+e.details.snap_vf}else{x.snap_vf=''}
1080 if(e.details.snap_scale_x&&e.details.snap_scale_x!==''&&e.details.snap_scale_y&&e.details.snap_scale_y!==''){x.sratio=' -s '+e.details.snap_scale_x+'x'+e.details.snap_scale_y}else{x.sratio=''}
1081 if(e.details.cust_snap&&e.details.cust_snap!==''){x.cust_snap=' '+e.details.cust_snap;}else{x.cust_snap=''}
1082 x.pipe+=' -update 1 -r '+e.details.snap_fps+x.cust_snap+x.sratio+x.snap_vf+' '+e.sdir+'s.jpg -y';
1083 }
1084 //Raw H.264 stream over HTTP (RTSP simulation)
1085 if(e.details.rawh264==='1'){
1086 if(e.details.rawh264_vcodec&&e.details.rawh264_vcodec!==''){x.rawh264_vcodec=' -c:v '+e.details.rawh264_vcodec}else{x.rawh264_vcodec=' -c:v copy'}
1087 if(e.details.rawh264_acodec==='no'){
1088 x.rawh264_acodec=''
1089 }else{
1090 if(e.details.rawh264_acodec&&e.details.rawh264_acodec!==''){x.rawh264_acodec=' -c:a '+e.details.rawh264_acodec}else{x.rawh264_acodec=' -c:a aac'}
1091 }
1092 x.rawh264_fps=''
1093 x.cust_rawh264=''
1094 x.rawh264_ratio=''
1095 x.rawh264_vf=''
1096 x.rawh264_crf=''
1097 if(x.rawh264_vcodec!=='copy'){
1098 if(e.details.rawh264_crf&&e.details.rawh264_crf!==''){x.rawh264_crf=' -crf '+e.details.rawh264_crf}else{x.rawh264_crf=''}
1099 if(e.details.rawh264_fps&&e.details.rawh264_fps!==''){x.rawh264_fps=e.details.rawh264_fps}else{x.rawh264_fps=''}
1100 if(e.details.rawh264_vf&&e.details.rawh264_vf!==''){x.rawh264_vf=' -vf '+e.details.rawh264_vf}else{x.rawh264_vf=''}
1101 if(e.details.rawh264_scale_x&&e.details.rawh264_scale_x!==''&&e.details.rawh264_scale_y&&e.details.rawh264_scale_y!==''){x.rawh264_ratio=' -s '+e.details.rawh264_scale_x+'x'+e.details.rawh264_scale_y}else{x.rawh264_ratio=''}
1102 }
1103 if(e.details.cust_rawh264&&e.details.cust_rawh264!==''){x.cust_rawh264=' '+e.details.cust_rawh264;}else{x.cust_rawh264=''}
1104 x.pipe+=' -f mpegts '+x.rawh264_vcodec+x.rawh264_acodec+x.rawh264_fps+x.cust_rawh264+x.rawh264_ratio+x.rawh264_vf+' http://127.0.0.1:'+config.port+'/streamIn/'+e.ke+'/'+e.mid+'/1';
1105 }
1106// //Stream to YouTube (Stream out to server)
1107// if(e.details.stream_server==='1'){
1108// if(!e.details.stream_server_vbr||e.details.stream_server_vbr===''){e.details.stream_server_vbr='256k'}
1109// x.stream_server_vbr=' -b:v '+e.details.stream_server_vbr;
1110// if(e.details.stream_server_fps&&e.details.stream_server_fps!==''){
1111// x.stream_server_fps=' -r '+e.details.stream_server_fps
1112// e.details.stream_server_fps=parseFloat(e.details.stream_server_fps)
1113// x.stream_server_fps+=' -g '+e.details.stream_server_fps
1114// }else{x.stream_server_fps=''}
1115// if(e.details.stream_server_crf&&e.details.stream_server_crf!==''){x.stream_server_crf=' -crf '+e.details.stream_server_crf}else{x.stream_server_crf=''}
1116// if(e.details.stream_server_vf&&e.details.stream_server_vf!==''){x.stream_server_vf=' -vf '+e.details.stream_server_vf}else{x.stream_server_vf=''}
1117// if(e.details.stream_server_preset&&e.details.stream_server_preset!==''){x.stream_server_preset=' -preset '+e.details.stream_server_preset}else{x.stream_server_preset=''}
1118// if(e.details.stream_server_scale_x&&e.details.stream_server_scale_x!==''&&e.details.stream_server_scale_y&&e.details.stream_server_scale_y!==''){x.stream_server_ratio=' -s '+e.details.stream_server_scale_x+'x'+e.details.stream_server_scale_y}else{x.stream_server_ratio=''}
1119// if(e.details.cust_stream_server&&e.details.cust_stream_server!==''){x.cust_stream_server=' '+e.details.cust_stream_server;}else{x.cust_stream_server=''}
1120// x.pipe+=' -vcodec libx264 -pix_fmt yuv420p'+x.stream_server_preset+x.stream_server_crf+x.stream_server_fps+x.stream_server_vbr+x.stream_server_ratio+x.stream_server_vf+' -acodec aac -strict 2 -ar 44100 -q:a 3 -b:a 712000'+x.cust_stream_server+' -f flv '+e.details.stream_server_url;
1121// }
1122 //custom - output
1123 if(e.details.custom_output&&e.details.custom_output!==''){x.pipe+=' '+e.details.custom_output;}
1124 //custom - input flags
1125 if(e.details.cust_input&&e.details.cust_input!==''){x.cust_input+=' '+e.details.cust_input;}
1126 //logging - level
1127 if(e.details.loglevel&&e.details.loglevel!==''){x.loglevel='-loglevel '+e.details.loglevel;}else{x.loglevel='-loglevel error'}
1128 if(e.mode=='record'){
1129 //custom - record flags
1130 if(e.details.cust_record&&e.details.cust_record!==''){x.record_string+=' '+e.details.cust_record;}
1131 //record - preset
1132 if(e.details.preset_record&&e.details.preset_record!==''){x.record_string+=' -preset '+e.details.preset_record;}
1133 }
1134 //build final string based on the input type.
1135 switch(e.type){
1136 case'dashcam':
1137 if(e.mode==='record'){x.record_string+=x.vcodec+x.framerate+x.record_video_filters+x.record_dimensions+x.segment;}
1138 x.tmp=x.loglevel+' -i -'+x.record_string+x.pipe;
1139 break;
1140 case'socket':case'jpeg':case'pipe':
1141 if(e.mode==='record'){x.record_string+=x.vcodec+x.framerate+x.record_video_filters+x.record_dimensions+x.segment;}
1142 x.tmp=x.loglevel+' -pattern_type glob -f image2pipe'+x.framerate+' -vcodec mjpeg'+x.cust_input+' -i -'+x.record_string+x.pipe;
1143 break;
1144 case'mjpeg':
1145 if(e.mode=='record'){
1146 x.record_string+=x.vcodec+x.record_video_filters+x.framerate+x.record_dimensions+x.segment;
1147 }
1148 x.tmp=x.loglevel+' -reconnect 1 -r '+e.details.sfps+' -f mjpeg'+x.cust_input+' -i '+e.url+''+x.record_string+x.pipe;
1149 break;
1150 case'h264':case'hls':case'mp4':
1151 if(e.mode=='record'){
1152 x.record_string+=x.vcodec+x.framerate+x.acodec+x.record_dimensions+x.record_video_filters+' '+x.segment;
1153 }
1154 x.tmp=x.loglevel+x.cust_input+x.hwaccel+' -i '+e.url+x.record_string+x.pipe;
1155 break;
1156 case'local':
1157 if(e.mode=='record'){
1158 x.record_string+=x.vcodec+x.framerate+x.acodec+x.record_dimensions+x.record_video_filters+' '+x.segment;
1159 }
1160 x.tmp=x.loglevel+x.cust_input+' -i '+e.path+''+x.record_string+x.pipe;
1161 break;
1162 }
1163 s.group[e.ke].mon[e.mid].ffmpeg=x.tmp;
1164 return spawn(config.ffmpegDir,x.tmp.replace(/\s+/g,' ').trim().split(' '),{detached: true});
1165}
1166s.file=function(x,e){
1167 if(!e){e={}};
1168 switch(x){
1169 case'size':
1170 return fs.statSync(e.filename)["size"];
1171 break;
1172 case'delete':
1173 if(!e){return false;}
1174 return exec('rm -f '+e,{detached: true});
1175 break;
1176 case'delete_files':
1177 if(!e.age_type){e.age_type='min'};if(!e.age){e.age='1'};
1178 exec('find '+e.path+' -type f -c'+e.age_type+' +'+e.age+' -exec rm -f {} +',{detached: true});
1179 break;
1180 }
1181}
1182s.camera=function(x,e,cn,tx){
1183 if(x!=='motion'){
1184 var ee=s.init('noReference',e);
1185 if(!e){e={}};if(cn&&cn.ke&&!e.ke){e.ke=cn.ke};
1186 if(!e.mode){e.mode=x;}
1187 if(!e.id&&e.mid){e.id=e.mid}
1188 }
1189 if(e.details&&(e.details instanceof Object)===false){
1190 try{e.details=JSON.parse(e.details)}catch(err){}
1191 }
1192 ['detector_cascades','cords'].forEach(function(v){
1193 if(e.details&&e.details[v]&&(e.details[v] instanceof Object)===false){
1194 try{
1195 e.details[v]=JSON.parse(e.details[v]);
1196 if(!e.details[v])e.details[v]={};
1197 }catch(err){
1198 e.details[v]={};
1199 }
1200 }
1201 })
1202 switch(x){
1203 case'snapshot'://get snapshot from monitor URL
1204 if(config.doSnapshot===true){
1205 if(e.mon.mode!=='stop'){
1206 try{e.mon.details=JSON.parse(e.mon.details)}catch(er){}
1207 if(e.mon.details.snap==='1'){
1208 fs.readFile(s.dir.streams+e.ke+'/'+e.mid+'/s.jpg',function(err,data){
1209 if(err){s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke);return};
1210 s.tx({f:'monitor_snapshot',snapshot:data,snapshot_format:'ab',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
1211 })
1212 }else{
1213 e.url=s.init('url',e.mon);
1214 switch(e.mon.type){
1215 case'mjpeg':case'h264':case'local':
1216 if(e.mon.type==='local'){e.url=e.mon.path;}
1217 e.spawn=spawn(config.ffmpegDir,('-loglevel quiet -i '+e.url+' -s 400x400 -r 25 -ss 1.8 -frames:v 1 -f singlejpeg pipe:1').split(' '),{detached: true})
1218 e.spawn.stdout.on('data',function(data){
1219 e.snapshot_sent=true; s.tx({f:'monitor_snapshot',snapshot:data.toString('base64'),snapshot_format:'b64',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
1220 e.spawn.kill();
1221 });
1222 e.spawn.on('close',function(data){
1223 if(!e.snapshot_sent){
1224 s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
1225 }
1226 delete(e.snapshot_sent);
1227 });
1228 break;
1229 case'jpeg':
1230 request({url:e.url,method:'GET',encoding:null},function(err,data){
1231 if(err){s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke);return};
1232 s.tx({f:'monitor_snapshot',snapshot:data.body,snapshot_format:'ab',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
1233 })
1234 break;
1235 default:
1236 s.tx({f:'monitor_snapshot',snapshot:'...',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
1237 break;
1238 }
1239 }
1240 }else{
1241 s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
1242 }
1243 }else{
1244 s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
1245 }
1246 break;
1247 case'record_off'://stop recording and start
1248 if(!s.group[e.ke].mon[e.id].record){s.group[e.ke].mon[e.id].record={}}
1249 s.group[e.ke].mon[e.id].record.yes=0;
1250 s.camera('start',e);
1251 break;
1252 case'watch_on'://live streamers - join
1253// if(s.group[e.ke].mon[e.id].watch[cn.id]){s.camera('watch_off',e,cn,tx);return}
1254 s.init(0,{ke:e.ke,mid:e.id})
1255 if(!cn.monitor_watching){cn.monitor_watching={}}
1256 if(!cn.monitor_watching[e.id]){cn.monitor_watching[e.id]={ke:e.ke}}
1257 s.group[e.ke].mon[e.id].watch[cn.id]={};
1258// if(Object.keys(s.group[e.ke].mon[e.id].watch).length>0){
1259// s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[e.ke,e.id],function(err,r) {
1260// if(r&&r[0]){
1261// r=r[0];
1262// r.url=s.init('url',r);
1263// s.group[e.ke].mon.type=r.type;
1264// }
1265// })
1266// }
1267 break;
1268 case'watch_off'://live streamers - leave
1269 if(cn.monitor_watching){delete(cn.monitor_watching[e.id])}
1270 if(s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].watch){
1271 delete(s.group[e.ke].mon[e.id].watch[cn.id]),e.ob=Object.keys(s.group[e.ke].mon[e.id].watch).length
1272 if(e.ob===0){
1273 delete(s.group[e.ke].mon[e.id].watch)
1274 }
1275 }else{
1276 e.ob=0;
1277 }
1278 if(tx){tx({f:'monitor_watch_off',ke:e.ke,id:e.id,cnid:cn.id})};
1279 s.tx({viewers:e.ob,ke:e.ke,id:e.id},'MON_'+e.id);
1280 break;
1281 case'restart'://restart monitor
1282 s.camera('stop',e)
1283 setTimeout(function(){
1284 s.camera(e.mode,e)
1285 },1300)
1286 break;
1287 case'idle':case'stop'://stop monitor
1288 if(!s.group[e.ke]||!s.group[e.ke].mon[e.id]){return}
1289 if(s.group[e.ke].mon[e.id].fswatch){s.group[e.ke].mon[e.id].fswatch.close();delete(s.group[e.ke].mon[e.id].fswatch)}
1290 if(s.group[e.ke].mon[e.id].fswatchStream){s.group[e.ke].mon[e.id].fswatchStream.close();delete(s.group[e.ke].mon[e.id].fswatchStream)}
1291 if(s.group[e.ke].mon[e.id].open){ee.filename=s.group[e.ke].mon[e.id].open,ee.ext=s.group[e.ke].mon[e.id].open_ext;s.video('close',ee)}
1292 if(s.group[e.ke].mon[e.id].last_frame){delete(s.group[e.ke].mon[e.id].last_frame)}
1293 if(s.group[e.ke].mon[e.id].started!==1){return}
1294 s.kill(s.group[e.ke].mon[e.id].spawn,e);
1295 if(e.neglectTriggerTimer===1){
1296 delete(e.neglectTriggerTimer);
1297 }else{
1298 clearTimeout(s.group[e.ke].mon[e.id].trigger_timer)
1299 delete(s.group[e.ke].mon[e.id].trigger_timer)
1300 }
1301 clearInterval(s.group[e.ke].mon[e.id].running);
1302 clearInterval(s.group[e.ke].mon[e.id].detector_notrigger_timeout)
1303 clearTimeout(s.group[e.ke].mon[e.id].err_fatal_timeout);
1304 s.group[e.ke].mon[e.id].started=0;
1305 if(s.group[e.ke].mon[e.id].record){s.group[e.ke].mon[e.id].record.yes=0}
1306 s.tx({f:'monitor_stopping',mid:e.id,ke:e.ke,time:s.moment()},'GRP_'+e.ke);
1307 s.camera('snapshot',{mid:e.id,ke:e.ke,mon:e})
1308 if(x==='stop'){
1309 s.log(e,{type:lang['Monitor Stopped'],msg:lang.MonitorStoppedText});
1310 clearTimeout(s.group[e.ke].mon[e.id].delete)
1311 if(e.delete===1){
1312 s.group[e.ke].mon[e.id].delete=setTimeout(function(){
1313 delete(s.group[e.ke].mon[e.id]);
1314 delete(s.group[e.ke].mon_conf[e.id]);
1315 },1000*60);
1316 }
1317 }else{
1318 s.tx({f:'monitor_idle',mid:e.id,ke:e.ke,time:s.moment()},'GRP_'+e.ke);
1319 s.log(e,{type:lang['Monitor Idling'],msg:lang.MonitorIdlingText});
1320 }
1321 break;
1322 case'start':case'record'://watch or record monitor url
1323 s.init(0,{ke:e.ke,mid:e.id})
1324 if(!s.group[e.ke].mon_conf[e.id]){s.group[e.ke].mon_conf[e.id]=s.init('noReference',e);}
1325 e.url=s.init('url',e);
1326 if(s.group[e.ke].mon[e.id].started===1){return}
1327 if(x==='start'&&e.details.detector_trigger=='1'){
1328 s.group[e.ke].mon[e.id].motion_lock=setTimeout(function(){
1329 clearTimeout(s.group[e.ke].mon[e.id].motion_lock);
1330 delete(s.group[e.ke].mon[e.id].motion_lock);
1331 },30000)
1332 }
1333 s.group[e.ke].mon[e.id].started=1;
1334 if(x==='record'){
1335 s.group[e.ke].mon[e.id].record.yes=1;
1336 }else{
1337 s.group[e.ke].mon[e.mid].record.yes=0;
1338 }
1339 if(e.details&&e.details.dir&&e.details.dir!==''){
1340 //addStorage choice
1341 e.dir=s.checkCorrectPathEnding(e.details.dir)+e.ke+'/';
1342 if (!fs.existsSync(e.dir)){
1343 fs.mkdirSync(e.dir);
1344 }
1345 e.dir=e.dir+e.id+'/';
1346 if (!fs.existsSync(e.dir)){
1347 fs.mkdirSync(e.dir);
1348 }
1349 }else{
1350 //MAIN videos dir
1351 e.dir=s.dir.videos+e.ke+'/';
1352 if (!fs.existsSync(e.dir)){
1353 fs.mkdirSync(e.dir);
1354 }
1355 e.dir=s.dir.videos+e.ke+'/'+e.id+'/';
1356 if (!fs.existsSync(e.dir)){
1357 fs.mkdirSync(e.dir);
1358 }
1359 }
1360 //stream dir
1361 e.sdir=s.dir.streams+e.ke+'/';
1362 if (!fs.existsSync(e.sdir)){
1363 fs.mkdirSync(e.sdir);
1364 }
1365 e.sdir=s.dir.streams+e.ke+'/'+e.id+'/';
1366 if (!fs.existsSync(e.sdir)){
1367 fs.mkdirSync(e.sdir);
1368 }else{
1369 s.file('delete',e.sdir+'*')
1370 }
1371 //start "no motion" checker
1372 if(e.details.detector=='1'&&e.details.detector_notrigger=='1'){
1373 if(!e.details.detector_notrigger_timeout||e.details.detector_notrigger_timeout===''){
1374 e.details.detector_notrigger_timeout=10
1375 }
1376 e.detector_notrigger_timeout=parseFloat(e.details.detector_notrigger_timeout)*1000*60;
1377 s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[e.ke,'%"sub"%'],function(err,r){
1378 r=r[0];
1379 s.group[e.ke].mon[e.id].detector_notrigger_timeout_function=function(){
1380 if(config.mail&&e.details.detector_notrigger_mail=='1'){
1381 e.mailOptions = {
1382 from: '"ShinobiCCTV" <no-reply@shinobi.video>', // sender address
1383 to: r.mail, // list of receivers
1384 subject: lang.NoMotionEmailText1+' '+e.name+' ('+e.id+')', // Subject line
1385 html: '<i>'+lang.NoMotionEmailText2+' '+e.details.detector_notrigger_timeout+' '+lang.minutes+'.</i>',
1386 };
1387 e.mailOptions.html+='<div><b>'+lang['Monitor Name']+' </b> : '+e.name+'</div>'
1388 e.mailOptions.html+='<div><b>'+lang['Monitor ID']+' </b> : '+e.id+'</div>'
1389 nodemailer.sendMail(e.mailOptions, (error, info) => {
1390 if (error) {
1391 s.systemLog('detector:notrigger:sendMail',error)
1392 s.tx({f:'error',ff:'detector_notrigger_mail',id:e.id,ke:e.ke,error:error},'GRP_'+e.ke);
1393 return ;
1394 }
1395 s.tx({f:'detector_notrigger_mail',id:e.id,ke:e.ke,info:info},'GRP_'+e.ke);
1396 });
1397 }
1398 }
1399 clearInterval(s.group[e.ke].mon[e.id].detector_notrigger_timeout)
1400 s.group[e.ke].mon[e.id].detector_notrigger_timeout=setInterval(s.group[e.ke].mon[e.id].detector_notrigger_timeout_function,e.detector_notrigger_timeout)
1401 })
1402 }
1403 //cutoff time and recording check interval
1404 if(!e.details.cutoff||e.details.cutoff===''){e.cutoff=15}else{e.cutoff=parseFloat(e.details.cutoff)};
1405 if(isNaN(e.cutoff)===true){e.cutoff=15}
1406 e.resetStreamCheck=function(){
1407 clearTimeout(s.group[e.ke].mon[e.id].checkStream)
1408 s.group[e.ke].mon[e.id].checkStream=setTimeout(function(){
1409 if(s.group[e.ke].mon[e.id].started===1){
1410 e.fn();
1411 s.log(e,{type:lang['Camera is not streaming'],msg:{msg:lang['Restarting Process']}});
1412 }
1413 },60000*1);
1414 }
1415 switch(x){
1416 case'record':
1417 s.group[e.ke].mon[e.id].fswatch=fs.watch(e.dir,{encoding:'utf8'},function(eventType,filename){
1418 if(s.group[e.ke].mon[e.id].fixingVideos[filename]){return}
1419 switch(eventType){
1420 case'change':
1421 clearTimeout(s.group[e.ke].mon[e.id].checker)
1422 clearTimeout(s.group[e.ke].mon[e.id].checkStream)
1423 s.group[e.ke].mon[e.id].checker=setTimeout(function(){
1424 if(s.group[e.ke].mon[e.id].started===1){
1425 e.fn();
1426 s.log(e,{type:lang['Camera is not recording'],msg:{msg:lang['Restarting Process']}});
1427 }
1428 },60000*2);
1429 break;
1430 case'rename':
1431 fs.exists(e.dir+filename,function(exists){
1432 if(exists){
1433 if(s.group[e.ke].mon[e.id].open){
1434 s.video('close',e);
1435 if(e.details.detector==='1'&&s.ocv&&s.group[e.ke].mon[e.id].started===1&&e.details&&e.details.detector_record_method==='del'&&e.details.detector_delete_motionless_videos==='1'&&s.group[e.ke].mon[e.id].detector_motion_count===0){
1436 if(e.details.loglevel!=='quiet'){
1437 s.log(e,{type:lang['Delete Motionless Video'],msg:e.filename+'.'+e.ext});
1438 }
1439 s.video('delete',s.init('noReference',e))
1440 }
1441 }
1442 setTimeout(function(){
1443 e.filename=filename.split('.')[0];
1444 s.video('open',e);
1445 s.group[e.ke].mon[e.id].open=e.filename;
1446 s.group[e.ke].mon[e.id].open_ext=e.ext;
1447 s.group[e.ke].mon[e.id].detector_motion_count=0;
1448 },2000)
1449 }
1450 });
1451 break;
1452 }
1453 })
1454 break;
1455 case'start':
1456 switch(e.details.stream_type){
1457 case'jpeg':case'hls':
1458 s.group[e.ke].mon[e.id].fswatchStream=fs.watch(e.sdir,{encoding:'utf8'},function(eventType,filename){
1459 switch(eventType){
1460 case'change':
1461 e.resetStreamCheck()
1462 break;
1463 }
1464 })
1465 break;
1466 }
1467 break;
1468 }
1469 s.camera('snapshot',{mid:e.id,ke:e.ke,mon:e})
1470 //check host to see if has password and user in it
1471 e.hosty=e.host.split('@');if(e.hosty[1]){e.hosty=e.hosty[1];}else{e.hosty=e.hosty[0];};
1472
1473 e.error_fatal=function(x){
1474 clearTimeout(s.group[e.ke].mon[e.id].err_fatal_timeout);
1475 ++e.error_fatal_count;
1476 if(s.group[e.ke].mon[e.id].started===1){
1477 s.group[e.ke].mon[e.id].err_fatal_timeout=setTimeout(function(){
1478 if(e.details.fatal_max!==0&&e.error_fatal_count>e.details.fatal_max){
1479 s.camera('stop',{id:e.id,ke:e.ke})
1480 }else{
1481 e.fn()
1482 };
1483 },5000);
1484 }else{
1485 s.kill(s.group[e.ke].mon[e.id].spawn,e);
1486 }
1487 }
1488 e.error_fatal_count=0;
1489 e.fn=function(){//this function loops to create new files
1490 clearTimeout(s.group[e.ke].mon[e.id].checker)
1491 if(s.group[e.ke].mon[e.id].started===1){
1492 e.error_count=0;
1493 s.group[e.ke].mon[e.id].error_socket_timeout_count=0;
1494 if(e.details.fatal_max===''){e.details.fatal_max=10}else{e.details.fatal_max=parseFloat(e.details.fatal_max)}
1495 s.kill(s.group[e.ke].mon[e.id].spawn,e);
1496 e.draw=function(err,o){
1497 if(o.success===true){
1498 e.frames=0;
1499 if(!s.group[e.ke].mon[e.id].record){s.group[e.ke].mon[e.id].record={yes:1}};
1500 //launch ffmpeg
1501 s.group[e.ke].mon[e.id].spawn = s.ffmpeg(e);
1502 //on unexpected exit restart
1503 s.group[e.ke].mon[e.id].spawn_exit=function(){
1504 if(s.group[e.ke].mon[e.id].started===1){
1505 if(e.details.loglevel!=='quiet'){
1506 s.log(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang['Process Crashed for Monitor']+' : '+e.id,cmd:s.group[e.ke].mon[e.id].ffmpeg}});
1507 }
1508 e.error_fatal();
1509 }
1510 }
1511 s.group[e.ke].mon[e.id].spawn.on('end',s.group[e.ke].mon[e.id].spawn_exit)
1512 s.group[e.ke].mon[e.id].spawn.on('exit',s.group[e.ke].mon[e.id].spawn_exit)
1513 //emitter for mjpeg
1514 if(!e.details.stream_mjpeg_clients||e.details.stream_mjpeg_clients===''||isNaN(e.details.stream_mjpeg_clients)===false){e.details.stream_mjpeg_clients=20;}else{e.details.stream_mjpeg_clients=parseInt(e.details.stream_mjpeg_clients)}
1515 s.group[e.ke].mon[e.id].emitter = new events.EventEmitter().setMaxListeners(e.details.stream_mjpeg_clients);
1516 s.log(e,{type:'FFMPEG Process Started',msg:{cmd:s.group[e.ke].mon[e.id].ffmpeg}});
1517 s.tx({f:'monitor_starting',mode:x,mid:e.id,time:s.moment()},'GRP_'+e.ke);
1518 //start workers
1519 if(e.type==='jpeg'){
1520 if(!e.details.sfps||e.details.sfps===''){
1521 e.details.sfps=parseFloat(e.details.sfps);
1522 if(isNaN(e.details.sfps)){e.details.sfps=1}
1523 }
1524 if(s.group[e.ke].mon[e.id].spawn){
1525 s.group[e.ke].mon[e.id].spawn.stdin.on('error',function(err){
1526 if(err&&e.details.loglevel!=='quiet'){
1527 s.log(e,{type:'STDIN ERROR',msg:err});
1528 }
1529 })
1530 }else{
1531 if(x==='record'){
1532 s.log(e,{type:lang.FFmpegCantStart,msg:lang.FFmpegCantStartText});
1533 return
1534 }
1535 }
1536 e.captureOne=function(f){
1537 s.group[e.ke].mon[e.id].record.request=request({url:e.url,method:'GET',encoding: null,timeout:15000},function(err,data){
1538 if(err){
1539 return;
1540 }
1541 }).on('data',function(d){
1542 if(!e.buffer0){
1543 e.buffer0=[d]
1544 }else{
1545 e.buffer0.push(d);
1546 }
1547 if((d[d.length-2] === 0xFF && d[d.length-1] === 0xD9)){
1548 e.buffer0=Buffer.concat(e.buffer0);
1549 ++e.frames;
1550 if(s.group[e.ke].mon[e.id].spawn&&s.group[e.ke].mon[e.id].spawn.stdin){
1551 s.group[e.ke].mon[e.id].spawn.stdin.write(e.buffer0);
1552 }
1553 if(s.group[e.ke].mon[e.id].started===1){
1554 s.group[e.ke].mon[e.id].record.capturing=setTimeout(function(){
1555 e.captureOne()
1556 },1000/e.details.sfps);
1557 }
1558 e.buffer0=null;
1559 }
1560 if(!e.timeOut){
1561 e.timeOut=setTimeout(function(){e.error_count=0;delete(e.timeOut);},3000);
1562 }
1563
1564 }).on('error', function(err){
1565 ++e.error_count;
1566 clearTimeout(e.timeOut);delete(e.timeOut);
1567 if(e.details.loglevel!=='quiet'){
1568 s.log(e,{type:lang['JPEG Error'],msg:{msg:lang.JPEGErrorText,info:err}});
1569 switch(err.code){
1570 case'ESOCKETTIMEDOUT':
1571 case'ETIMEDOUT':
1572 ++s.group[e.ke].mon[e.id].error_socket_timeout_count
1573 if(e.details.fatal_max!==0&&s.group[e.ke].mon[e.id].error_socket_timeout_count>e.details.fatal_max){
1574 s.log(e,{type:lang['Fatal Maximum Reached'],msg:{code:'ESOCKETTIMEDOUT',msg:lang.FatalMaximumReachedText}});
1575 s.camera('stop',e)
1576 }else{
1577 s.log(e,{type:lang['Restarting Process'],msg:{code:'ESOCKETTIMEDOUT',msg:lang.FatalMaximumReachedText}});
1578 s.camera('restart',e)
1579 }
1580 return;
1581 break;
1582 }
1583 }
1584 if(e.details.fatal_max!==0&&e.error_count>e.details.fatal_max){
1585 clearTimeout(s.group[e.ke].mon[e.id].record.capturing);
1586 e.fn();
1587 }
1588 });
1589 }
1590 e.captureOne()
1591 }
1592 if(!s.group[e.ke]||!s.group[e.ke].mon[e.id]){s.init(0,e)}
1593 s.group[e.ke].mon[e.id].spawn.on('error',function(er){
1594 s.log(e,{type:'Spawn Error',msg:er});e.error_fatal()
1595 });
1596 if(s.ocv&&e.details.detector==='1'){
1597 s.tx({f:'init_monitor',id:e.id,ke:e.ke},s.ocv.id)
1598 }
1599 //frames from motion detect
1600 s.group[e.ke].mon[e.id].spawn.stdin.on('data',function(d){
1601 if(s.ocv&&e.details.detector==='1'&&e.details.detector_send_frames==='1'){
1602 s.tx({f:'frame',mon:s.group[e.ke].mon_conf[e.id].details,ke:e.ke,id:e.id,time:s.moment(),frame:d},s.ocv.id);
1603 };
1604 })
1605 //frames to stream
1606 ++e.frames;
1607 switch(e.details.stream_type){
1608 case'flv':
1609 e.frame_to_stream=function(d){
1610 if(!s.group[e.ke].mon[e.id].firstFLVchunk)s.group[e.ke].mon[e.id].firstFLVchunk = d;
1611 e.resetStreamCheck()
1612 s.group[e.ke].mon[e.id].emitter.emit('data',d);
1613 }
1614 break;
1615 case'mjpeg':
1616 e.frame_to_stream=function(d){
1617 e.resetStreamCheck()
1618 s.group[e.ke].mon[e.id].emitter.emit('data',d);
1619 }
1620 break;
1621 case'b64':case undefined:case null:
1622 e.frame_to_stream=function(d){
1623 e.resetStreamCheck()
1624 if(s.group[e.ke]&&s.group[e.ke].mon[e.id]&&s.group[e.ke].mon[e.id].watch&&Object.keys(s.group[e.ke].mon[e.id].watch).length>0){
1625 if(!e.buffer){
1626 e.buffer=[d]
1627 }else{
1628 e.buffer.push(d);
1629 }
1630 if((d[d.length-2] === 0xFF && d[d.length-1] === 0xD9)){
1631 e.buffer=Buffer.concat(e.buffer);
1632 s.tx({f:'monitor_frame',ke:e.ke,id:e.id,time:s.moment(),frame:e.buffer.toString('base64'),frame_format:'b64'},'MON_STREAM_'+e.id);
1633 e.buffer=null;
1634 }
1635 }
1636 }
1637 break;
1638 }
1639 if(e.frame_to_stream){
1640 s.group[e.ke].mon[e.id].spawn.stdout.on('data',e.frame_to_stream);
1641 }
1642 if(x==='record'||e.type==='mjpeg'||e.type==='h264'||e.type==='local'){
1643 s.group[e.ke].mon[e.id].spawn.stderr.on('data',function(d){
1644 d=d.toString();
1645 e.chk=function(x){return d.indexOf(x)>-1;}
1646 switch(true){
1647 //mp4 output with webm encoder chosen
1648 case e.chk('Could not find tag for vp8'):
1649 case e.chk('Only VP8 or VP9 Video'):
1650 case e.chk('Could not write header'):
1651// switch(e.ext){
1652// case'mp4':
1653// e.details.vcodec='libx264'
1654// e.details.acodec='none'
1655// break;
1656// case'webm':
1657// e.details.vcodec='libvpx'
1658// e.details.acodec='none'
1659// break;
1660// }
1661// if(e.details.stream_type==='hls'){
1662// e.details.stream_vcodec='libx264'
1663// e.details.stream_acodec='no'
1664// }
1665// s.camera('restart',e)
1666 return s.log(e,{type:lang['Incorrect Settings Chosen'],msg:{msg:d}})
1667 break;
1668 case e.chk('NULL @'):
1669 case e.chk('RTP: missed'):
1670 case e.chk('deprecated pixel format used, make sure you did set range correctly'):
1671 return
1672 break;
1673// case e.chk('av_interleaved_write_frame'):
1674 case e.chk('Connection refused'):
1675 case e.chk('Connection timed out'):
1676 //restart
1677 setTimeout(function(){s.log(e,{type:lang["Can't Connect"],msg:lang['Retrying...']});e.error_fatal();},1000)
1678 break;
1679// case e.chk('No such file or directory'):
1680// case e.chk('Unable to open RTSP for listening'):
1681// case e.chk('timed out'):
1682// case e.chk('Invalid data found when processing input'):
1683// case e.chk('Immediate exit requested'):
1684// case e.chk('reset by peer'):
1685// if(e.frames===0&&x==='record'){s.video('delete',e)};
1686// setTimeout(function(){
1687// if(!s.group[e.ke].mon[e.id].spawn){e.fn()}
1688// },2000)
1689// break;
1690 case e.chk('mjpeg_decode_dc'):
1691 case e.chk('bad vlc'):
1692 case e.chk('error dc'):
1693 e.fn()
1694 break;
1695 case /T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(d):
1696 return s.log(e,{type:lang['Video Finished'],msg:{filename:d}})
1697 break;
1698 }
1699 s.log(e,{type:"FFMPEG STDERR",msg:d})
1700 });
1701 }
1702 }else{
1703 s.log(e,{type:lang["Can't Connect"],msg:lang['Retrying...']});e.error_fatal();return;
1704 }
1705 }
1706 if(e.type!=='socket'&&e.type!=='dashcam'&&e.protocol!=='udp'&&e.type!=='local'){
1707 connectionTester.test(e.hosty,e.port,2000,e.draw);
1708 }else{
1709 e.draw(null,{success:true})
1710 }
1711 }else{
1712 s.kill(s.group[e.ke].mon[e.id].spawn,e);
1713 }
1714 }
1715 //start drawing files
1716 if(s.child_help===true){
1717 e.ch=Object.keys(s.child_nodes);
1718 if(e.ch.length>0){
1719 e.ch_stop=0;
1720 e.fn=function(n){
1721 connectionTester.test(e.hosty,e.port,2000,function(err,o){
1722 if(o.success===true){
1723 s.video('open',e);
1724 e.frames=0;
1725 s.group[e.ke].mon[e.id].spawn={};
1726 s.group[e.ke].mon[e.id].child_node=n;
1727 s.cx({f:'spawn',d:s.init('noReference',e),mon:s.init('noReference',s.group[e.ke].mon[e.mid])},s.group[e.ke].mon[e.mid].child_node_id)
1728 }else{
1729// s.systemLog('Cannot Connect, Retrying...',e.id);
1730 e.error_fatal();return;
1731 }
1732 })
1733 }
1734 e.ch.forEach(function(n){
1735 if(e.ch_stop===0&&s.child_nodes[n].cpu<80){
1736 e.ch_stop=1;
1737 s.group[e.ke].mon[e.mid].child_node=n;
1738 s.group[e.ke].mon[e.mid].child_node_id=s.child_nodes[n].cnid;
1739 e.fn(n);
1740 }
1741 })
1742 }else{
1743 e.fn();
1744 }
1745 }else{
1746 e.fn();
1747 }
1748 break;
1749 case'motion':
1750 var d=e;
1751 if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]){
1752 return s.systemLog(lang['No Monitor Found, Ignoring Request'])
1753 }
1754 d.mon=s.group[d.ke].mon_conf[d.id];
1755 if(!s.group[d.ke].mon[d.id].detector_motion_count){
1756 s.group[d.ke].mon[d.id].detector_motion_count=0
1757 }
1758 s.group[d.ke].mon[d.id].detector_motion_count+=1
1759 if(s.group[d.ke].mon[d.id].motion_lock){
1760 return
1761 }
1762 if(!d.mon.details.detector_lock_timeout||d.mon.details.detector_lock_timeout===''){
1763 d.mon.details.detector_lock_timeout=2000
1764 }
1765 d.mon.details.detector_lock_timeout=parseFloat(d.mon.details.detector_lock_timeout);
1766 if(!s.group[d.ke].mon[d.id].detector_lock_timeout){
1767 s.group[d.ke].mon[d.id].detector_lock_timeout=setTimeout(function(){
1768 clearTimeout(s.group[d.ke].mon[d.id].detector_lock_timeout)
1769 delete(s.group[d.ke].mon[d.id].detector_lock_timeout)
1770 },d.mon.details.detector_lock_timeout)
1771 }else{
1772 return
1773 }
1774 d.cx={f:'detector_trigger',id:d.id,ke:d.ke,details:d.details};
1775 s.tx(d.cx,'GRP_'+d.ke);
1776 if(d.mon.details.detector_notrigger=='1'){
1777 if(!d.mon.details.detector_notrigger_timeout||d.mon.details.detector_notrigger_timeout===''){
1778 d.mon.details.detector_notrigger_timeout=10
1779 }
1780 d.mon.detector_notrigger_timeout=parseFloat(d.mon.details.detector_notrigger_timeout)*1000*60;
1781 clearInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout)
1782 s.group[d.ke].mon[d.id].detector_notrigger_timeout=setInterval(s.group[d.ke].mon[d.id].detector_notrigger_timeout_function,d.mon.detector_notrigger_timeout)
1783 }
1784 if(d.mon.details.detector_webhook=='1'){
1785 d.mon.details.detector_webhook_url=d.mon.details.detector_webhook_url
1786 .replace(/{{TIME}}/g,moment(new Date).format())
1787 .replace(/{{MONITOR_ID}}/g,d.id)
1788 .replace(/{{GROUP_KEY}}/g,d.ke);
1789 http.get(d.mon.details.detector_webhook_url, function(data) {
1790 data.setEncoding('utf8');
1791 var chunks='';
1792 data.on('data', (chunk) => {
1793 chunks+=chunk;
1794 });
1795 data.on('end', () => {
1796
1797 });
1798
1799 }).on('error', function(e) {
1800
1801 }).end();
1802 }
1803 if(d.mon.mode!=='stop'&&d.mon.details.detector_trigger=='1'&&d.mon.details.detector_record_method==='hot'){
1804 if(!d.mon.details.detector_timeout||d.mon.details.detector_timeout===''){
1805 d.mon.details.detector_timeout=10
1806 }else{
1807 d.mon.details.detector_timeout=parseFloat(d.mon.details.detector_timeout)
1808 }
1809 if(!d.auth){
1810 d.auth=s.gid();
1811 }
1812 if(!s.group[d.ke].users[d.auth]){
1813 s.group[d.ke].users[d.auth]={system:1,details:{},lang:lang}
1814 }
1815 d.urlQuery=[]
1816 d.url='http://'+config.ip+':'+config.port+'/'+d.auth+'/monitor/'+d.ke+'/'+d.id+'/record/'+d.mon.details.detector_timeout+'/min';
1817 if(d.mon.details.watchdog_reset!=='0'){
1818 d.urlQuery.push('reset=1')
1819 }
1820 if(d.mon.details.detector_trigger_record_fps&&d.mon.details.detector_trigger_record_fps!==''&&d.mon.details.detector_trigger_record_fps!=='0'){
1821 d.urlQuery.push('fps='+d.mon.details.detector_trigger_record_fps)
1822 }
1823 if(d.urlQuery.length>0){
1824 d.url+='?'+d.urlQuery.join('&')
1825 }
1826 http.get(d.url, function(data) {
1827 data.setEncoding('utf8');
1828 var chunks='';
1829 data.on('data', (chunk) => {
1830 chunks+=chunk;
1831 });
1832 data.on('end', () => {
1833 delete(s.group[d.ke].users[d.auth])
1834 d.cx.f='detector_record_engaged';
1835 d.cx.msg=JSON.parse(chunks);
1836 s.tx(d.cx,'GRP_'+d.ke);
1837 });
1838
1839 }).on('error', function(e) {
1840
1841 }).end();
1842 }
1843 //mailer
1844 if(config.mail&&!s.group[d.ke].mon[d.id].detector_mail&&d.mon.details.detector_mail==='1'){
1845 s.sqlQuery('SELECT mail FROM Users WHERE ke=? AND details NOT LIKE ?',[d.ke,'%"sub"%'],function(err,r){
1846 r=r[0];
1847 if(!d.mon.details.detector_mail_timeout||d.mon.details.detector_mail_timeout===''){
1848 d.mon.details.detector_mail_timeout=1000*60*10;
1849 }else{
1850 d.mon.details.detector_mail_timeout=parseFloat(d.mon.details.detector_mail_timeout)*1000*60;
1851 }
1852 //lock mailer so you don't get emailed on EVERY trigger event.
1853 s.group[d.ke].mon[d.id].detector_mail=setTimeout(function(){
1854 //unlock so you can mail again.
1855 clearTimeout(s.group[d.ke].mon[d.id].detector_mail);
1856 delete(s.group[d.ke].mon[d.id].detector_mail);
1857 },d.mon.details.detector_mail_timeout);
1858 d.frame_filename='Motion_'+(d.mon.name.replace(/[^\w\s]/gi, ''))+'_'+d.id+'_'+d.ke+'_'+s.moment()+'.jpg';
1859 fs.readFile(s.dir.streams+'/'+d.ke+'/'+d.id+'/s.jpg',function(err, frame){
1860 d.mailOptions = {
1861 from: '"ShinobiCCTV" <no-reply@shinobi.video>', // sender address
1862 to: r.mail, // list of receivers
1863 subject: lang.Event+' - '+d.frame_filename, // Subject line
1864 html: '<i>'+lang.EventText1+' '+moment(new Date).format()+'.</i>',
1865 };
1866 if(err){
1867 s.systemLog(lang.EventText2+' '+d.ke+' '+d.id,err)
1868 }else{
1869 d.mailOptions.attachments=[
1870 {
1871 filename: d.frame_filename,
1872 content: frame
1873 }
1874 ]
1875 d.mailOptions.html='<i>'+lang.EventText3+'</i>'
1876 }
1877 Object.keys(d.details).forEach(function(v,n){
1878 d.mailOptions.html+='<div><b>'+v+'</b> : '+d.details[v]+'</div>'
1879 })
1880 nodemailer.sendMail(d.mailOptions, (error, info) => {
1881 if (error) {
1882 s.systemLog(lang.MailError,error)
1883 return ;
1884 }
1885 });
1886 })
1887 });
1888 }
1889 //save this detection result in SQL, only coords. not image.
1890 if(d.mon.details.detector_save==='1'){
1891 s.sqlQuery('INSERT INTO Events (ke,mid,details) VALUES (?,?,?)',[d.ke,d.id,JSON.stringify(d.details)])
1892 }
1893 if(d.mon.details.detector_command_enable==='1'&&!s.group[d.ke].mon[d.id].detector_command){
1894 if(!d.mon.details.detector_command_timeout||d.mon.details.detector_command_timeout===''){
1895 d.mon.details.detector_command_timeout=1000*60*10;
1896 }else{
1897 d.mon.details.detector_command_timeout=parseFloat(d.mon.details.detector_command_timeout)*1000*60;
1898 }
1899 s.group[d.ke].mon[d.id].detector_command=setTimeout(function(){
1900 clearTimeout(s.group[d.ke].mon[d.id].detector_command);
1901 delete(s.group[d.ke].mon[d.id].detector_command);
1902
1903 },d.mon.details.detector_command_timeout);
1904 d.mon.details.detector_command=d.mon.details.detector_command
1905 .replace(/{{TIME}}/g,moment(new Date).format())
1906 .replace(/{{MONITOR_ID}}/g,d.id)
1907 .replace(/{{GROUP_KEY}}/g,d.ke)
1908 if(d.details.confidence){
1909 d.mon.details.detector_command=d.mon.details.detector_command
1910 .replace(/{{CONFIDENCE}}/g,d.details.confidence)
1911 }
1912 exec(d.mon.details.detector_command,{detached: true})
1913 }
1914 break;
1915 }
1916 if(typeof cn==='function'){setTimeout(function(){cn()},1000);}
1917}
1918
1919////socket controller
1920s.cn=function(cn){return{id:cn.id,ke:cn.ke,uid:cn.uid}}
1921io.on('connection', function (cn) {
1922var tx;
1923 cn.on('f',function(d){
1924 if(!cn.ke&&d.f==='init'){//socket login
1925 cn.ip=cn.request.connection.remoteAddress;
1926 tx=function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);}
1927 d.failed=function(){tx({ok:false,msg:'Not Authorized',token_used:d.auth,ke:d.ke});cn.disconnect();}
1928 d.success=function(r){
1929 r=r[0];cn.join('GRP_'+d.ke);cn.join('CPU');
1930 cn.ke=d.ke,
1931 cn.uid=d.uid,
1932 cn.auth=d.auth;
1933 if(!s.group[d.ke])s.group[d.ke]={};
1934// if(!s.group[d.ke].vid)s.group[d.ke].vid={};
1935 if(!s.group[d.ke].users)s.group[d.ke].users={};
1936// s.group[d.ke].vid[cn.id]={uid:d.uid};
1937 s.group[d.ke].users[d.auth]={cnid:cn.id,uid:r.uid,mail:r.mail,details:JSON.parse(r.details),logged_in_at:moment(new Date).format(),login_type:'Dashboard'}
1938 try{s.group[d.ke].users[d.auth].details=JSON.parse(r.details)}catch(er){}
1939 if(s.group[d.ke].users[d.auth].details.get_server_log!=='0'){
1940 cn.join('GRPLOG_'+d.ke)
1941 }
1942 s.group[d.ke].users[d.auth].lang=s.getLanguageFile(s.group[d.ke].users[d.auth].details.lang)
1943 s.log({ke:d.ke,mid:'$USER'},{type:s.group[d.ke].users[d.auth].lang['Websocket Connected'],msg:{mail:r.mail,id:d.uid,ip:cn.ip}})
1944 if(!s.group[d.ke].mon){
1945 s.group[d.ke].mon={}
1946 if(!s.group[d.ke].mon){s.group[d.ke].mon={}}
1947 }
1948 if(s.ocv){
1949 tx({f:'detector_plugged',plug:s.ocv.plug,notice:s.ocv.notice})
1950 s.tx({f:'readPlugins',ke:d.ke},s.ocv.id)
1951 }
1952 tx({f:'users_online',users:s.group[d.ke].users})
1953 s.tx({f:'user_status_change',ke:d.ke,uid:cn.uid,status:1,user:s.group[d.ke].users[d.auth]},'GRP_'+d.ke)
1954 s.init('diskUsedEmit',d)
1955 s.init('apps',d)
1956 s.sqlQuery('SELECT * FROM API WHERE ke=? && uid=?',[d.ke,d.uid],function(err,rrr) {
1957 tx({
1958 f:'init_success',
1959 users:s.group[d.ke].vid,
1960 apis:rrr,
1961 os:{
1962 platform:s.platform,
1963 cpuCount:os.cpus().length,
1964 totalmem:s.totalmem
1965 }
1966 })
1967 http.get('http://'+config.ip+':'+config.port+'/'+cn.auth+'/monitor/'+cn.ke, function(res){
1968 var body = '';
1969 res.on('data', function(chunk){
1970 body += chunk;
1971 });
1972 res.on('end', function(){
1973 var rr = JSON.parse(body);
1974 setTimeout(function(g){
1975 g=function(t){
1976 s.camera('snapshot',{mid:t.mid,ke:t.ke,mon:t})
1977 }
1978 if(rr.mid){
1979 g(rr)
1980 }else{
1981 rr.forEach(g)
1982 }
1983 },2000)
1984 });
1985 }).on('error', function(e){
1986// s.systemLog("Get Snapshot Error", e);
1987 });
1988 })
1989 }
1990 s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
1991 if(r&&r[0]){
1992 d.success(r)
1993 }else{
1994 s.sqlQuery('SELECT * FROM API WHERE ke=? AND code=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
1995 if(r&&r[0]){
1996 r=r[0]
1997 r.details=JSON.parse(r.details)
1998 if(r.details.auth_socket==='1'){
1999 s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND uid=?',[r.ke,r.uid],function(err,r) {
2000 if(r&&r[0]){
2001 d.success(r)
2002 }else{
2003 d.failed()
2004 }
2005 })
2006 }else{
2007 d.failed()
2008 }
2009 }else{
2010 d.failed()
2011 }
2012 })
2013 }
2014 })
2015 return;
2016 }
2017 if((d.id||d.uid||d.mid)&&cn.ke){
2018 try{
2019 switch(d.f){
2020 case'ocv_in':
2021 if(s.ocv){
2022 s.tx(d.data,s.ocv.id)
2023 }
2024 break;
2025 case'monitorOrder':
2026 if(d.monitorOrder&&d.monitorOrder instanceof Array){
2027 s.sqlQuery('SELECT details FROM Users WHERE uid=? AND ke=?',[cn.uid,cn.ke],function(err,r){
2028 if(r&&r[0]){
2029 r=JSON.parse(r[0].details);
2030 r.monitorOrder=d.monitorOrder;
2031 s.sqlQuery('UPDATE Users SET details=? WHERE uid=? AND ke=?',[JSON.stringify(r),cn.uid,cn.ke])
2032 }
2033 })
2034 }
2035 break;
2036 case'update':
2037 if(!config.updateKey){
2038 tx({error:lang.updateKeyText1});
2039 return;
2040 }
2041 if(d.key===config.updateKey){
2042 exec('chmod +x '+__dirname+'/UPDATE.sh&&'+__dirname+'/./UPDATE.sh',{detached: true})
2043 }else{
2044 tx({error:lang.updateKeyText2});
2045 }
2046 break;
2047 case'cron':
2048 if(s.group[cn.ke]&&s.group[cn.ke].users[cn.auth].details&&!s.group[cn.ke].users[cn.auth].details.sub){
2049 s.tx({f:d.ff},s.cron.id)
2050 }
2051 break;
2052 case'api':
2053 switch(d.ff){
2054 case'delete':
2055 d.set=[],d.ar=[];
2056 d.form.ke=cn.ke;d.form.uid=cn.uid;delete(d.form.ip);
2057 if(!d.form.code){tx({f:'form_incomplete',form:'APIs'});return}
2058 d.for=Object.keys(d.form);
2059 d.for.forEach(function(v){
2060 d.set.push(v+'=?'),d.ar.push(d.form[v]);
2061 });
2062 s.sqlQuery('DELETE FROM API WHERE '+d.set.join(' AND '),d.ar,function(err,r){
2063 if(!err){
2064 tx({f:'api_key_deleted',form:d.form});
2065 delete(s.api[d.form.code]);
2066 }else{
2067 s.systemLog('API Delete Error : '+e.ke+' : '+' : '+e.mid,err)
2068 }
2069 })
2070 break;
2071 case'add':
2072 d.set=[],d.qu=[],d.ar=[];
2073 d.form.ke=cn.ke,d.form.uid=cn.uid,d.form.code=s.gid(30);
2074 d.for=Object.keys(d.form);
2075 d.for.forEach(function(v){
2076 d.set.push(v),d.qu.push('?'),d.ar.push(d.form[v]);
2077 });
2078 d.ar.push(cn.ke);
2079 s.sqlQuery('INSERT INTO API ('+d.set.join(',')+') VALUES ('+d.qu.join(',')+')',d.ar,function(err,r){
2080 d.form.time=s.moment(new Date,'YYYY-DD-MM HH:mm:ss');
2081 if(!err){tx({f:'api_key_added',form:d.form});}else{s.systemLog(err)}
2082 });
2083 break;
2084 }
2085 break;
2086 case'settings':
2087 switch(d.ff){
2088 case'filters':
2089 switch(d.fff){
2090 case'save':case'delete':
2091 s.sqlQuery('SELECT details FROM Users WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,r){
2092 if(r&&r[0]){
2093 r=r[0];
2094 d.d=JSON.parse(r.details);
2095 if(d.form.id===''){d.form.id=s.gid(5)}
2096 if(!d.d.filters)d.d.filters={};
2097 //save/modify or delete
2098 if(d.fff==='save'){
2099 d.d.filters[d.form.id]=d.form;
2100 }else{
2101 delete(d.d.filters[d.form.id]);
2102 }
2103 s.sqlQuery('UPDATE Users SET details=? WHERE ke=? AND uid=?',[JSON.stringify(d.d),d.ke,d.uid],function(err,r){
2104 tx({f:'filters_change',uid:d.uid,ke:d.ke,filters:d.d.filters});
2105 });
2106 }
2107 })
2108 break;
2109 }
2110 break;
2111 case'edit':
2112 s.sqlQuery('SELECT details FROM Users WHERE ke=? AND uid=?',[d.ke,d.uid],function(err,r){
2113 if(r&&r[0]){
2114 r=r[0];
2115 d.d=JSON.parse(r.details);
2116 if(d.d.get_server_log==='1'){
2117 cn.join('GRPLOG_'+d.ke)
2118 }else{
2119 cn.leave('GRPLOG_'+d.ke)
2120 }
2121 ///unchangeable from client side, so reset them in case they did.
2122 d.form.details=JSON.parse(d.form.details)
2123 //admin permissions
2124 d.form.details.permissions=d.d.permissions
2125 d.form.details.edit_size=d.d.edit_size
2126 d.form.details.edit_days=d.d.edit_days
2127 d.form.details.use_admin=d.d.use_admin
2128 d.form.details.use_webdav=d.d.use_webdav
2129 d.form.details.use_ldap=d.d.use_ldap
2130 //check
2131 if(d.d.edit_days=="0"){
2132 d.form.details.days=d.d.days;
2133 }
2134 if(d.d.edit_size=="0"){
2135 d.form.details.size=d.d.size;
2136 }
2137 if(d.d.sub){
2138 d.form.details.sub=d.d.sub;
2139 if(d.d.monitors){d.form.details.monitors=d.d.monitors;}
2140 if(d.d.allmonitors){d.form.details.allmonitors=d.d.allmonitors;}
2141 if(d.d.video_delete){d.form.details.video_delete=d.d.video_delete;}
2142 if(d.d.video_view){d.form.details.video_view=d.d.video_view;}
2143 if(d.d.monitor_edit){d.form.details.monitor_edit=d.d.monitor_edit;}
2144 if(d.d.size){d.form.details.size=d.d.size;}
2145 if(d.d.days){d.form.details.days=d.d.days;}
2146 delete(d.form.details.mon_groups)
2147 }
2148 var newSize = d.form.details.size
2149 d.form.details=JSON.stringify(d.form.details)
2150 ///
2151 d.set=[],d.ar=[];
2152 if(d.form.pass&&d.form.pass!==''){d.form.pass=s.md5(d.form.pass);}else{delete(d.form.pass)};
2153 delete(d.form.password_again);
2154 d.for=Object.keys(d.form);
2155 d.for.forEach(function(v){
2156 d.set.push(v+'=?'),d.ar.push(d.form[v]);
2157 });
2158 d.ar.push(d.ke),d.ar.push(d.uid);
2159 s.sqlQuery('UPDATE Users SET '+d.set.join(',')+' WHERE ke=? AND uid=?',d.ar,function(err,r){
2160 if(!d.d.sub){
2161 s.group[d.ke].sizeLimit = parseFloat(newSize)
2162 delete(s.group[d.ke].webdav)
2163 s.init('apps',d)
2164 }
2165 tx({f:'user_settings_change',uid:d.uid,ke:d.ke,form:d.form});
2166 });
2167 }
2168 })
2169 break;
2170 }
2171 break;
2172 case'monitor':
2173 switch(d.ff){
2174 case'control':
2175 if(!s.group[d.ke]||!s.group[d.ke].mon[d.mid]){return}
2176 d.m=s.group[d.ke].mon_conf[d.mid];
2177 if(d.m.details.control!=="1"){s.log(d,{type:lang['Control Error'],msg:lang.ControlErrorText1});return}
2178 if(!d.m.details.control_base_url||d.m.details.control_base_url===''){
2179 d.base=s.init('url_no_path',d.m);
2180 }else{
2181 d.base=d.m.details.control_base_url;
2182 }
2183 if(!d.m.details.control_url_stop_timeout||d.m.details.control_url_stop_timeout===''){d.m.details.control_url_stop_timeout=1000}
2184 d.setURL=function(url){
2185 d.URLobject=URL.parse(url)
2186 if(!d.URLobject.port){d.URLobject.port=80}
2187 d.options = {
2188 host: d.URLobject.hostname,
2189 port: d.URLobject.port,
2190 method: "GET",
2191 path: d.URLobject.pathname,
2192 };
2193 if(d.URLobject.query){
2194 d.options.path=d.options.path+'?'+d.URLobject.query
2195 }
2196 if(d.URLobject.username&&d.URLobject.password){
2197 d.options.auth=d.URLobject.username+':'+d.URLobject.password
2198 }
2199 if(d.URLobject.auth){
2200 d.options.auth=d.URLobject.auth
2201 }
2202 }
2203 d.setURL(d.base+d.m.details['control_url_'+d.direction])
2204 http.get(d.options, function(first) {
2205 first.on('data', function(toss) {});
2206 first.endCommand=function(){
2207 clearTimeout(first.endCommandLastResort)
2208 if(d.m.details.control_stop=='1'&&d.direction!=='center'){
2209 d.setURL(d.base+d.m.details['control_url_'+d.direction+'_stop'])
2210 setTimeout(function(){
2211 http.get(d.options, function(data) {
2212 data.on('end', function(){
2213 if(err){s.log(d,{type:'Control Error',msg:err});return false}
2214 s.tx({f:'control',mid:d.mid,ke:d.ke,direction:d.direction,url_stop:true});
2215 });
2216 }).on('error', function(err) {
2217 s.log(d,{type:'Control Error',msg:err});
2218 }).end();
2219 },d.m.details.control_url_stop_timeout)
2220 }else{
2221 tx({f:'control',mid:d.mid,ke:d.ke,direction:d.direction,url_stop:false});
2222 }
2223 }
2224 first.on('end',first.endCommand);
2225 first.endCommandLastResort=setTimeout(first.endCommand,3000)
2226 }).on('error', function(err) {
2227 s.log(d,{type:'Control Error',msg:err});
2228 }).end();
2229 break;
2230 case'jpeg_off':
2231 delete(cn.jpeg_on);
2232 if(cn.monitor_watching){
2233 Object.keys(cn.monitor_watching).forEach(function(n,v){
2234 v=cn.monitor_watching[n];
2235 cn.join('MON_STREAM_'+n);
2236 });
2237 }
2238 tx({f:'mode_jpeg_off'})
2239 break;
2240 case'jpeg_on':
2241 cn.jpeg_on=true;
2242 if(cn.monitor_watching){
2243 Object.keys(cn.monitor_watching).forEach(function(n,v){
2244 v=cn.monitor_watching[n];
2245 cn.leave('MON_STREAM_'+n);
2246 });
2247 }
2248 tx({f:'mode_jpeg_on'})
2249 break;
2250 case'watch_on':
2251 if(!d.ke){d.ke=cn.ke}
2252 s.init(0,{mid:d.id,ke:d.ke});
2253 if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]||s.group[d.ke].mon[d.id].started===0){return false}
2254 s.camera(d.ff,d,cn,tx)
2255 cn.join('MON_'+d.id);
2256 if(cn.jpeg_on!==true){
2257 cn.join('MON_STREAM_'+d.id);
2258 } if(s.group[d.ke]&&s.group[d.ke].mon&&s.group[d.ke].mon[d.id]&&s.group[d.ke].mon[d.id].watch){
2259
2260 tx({f:'monitor_watch_on',id:d.id,ke:d.ke})
2261 s.tx({viewers:Object.keys(s.group[d.ke].mon[d.id].watch).length,ke:d.ke,id:d.id},'MON_'+d.id)
2262 }
2263 break;
2264 case'watch_off':
2265 if(!d.ke){d.ke=cn.ke;};cn.leave('MON_'+d.id);s.camera(d.ff,d,cn,tx);
2266 s.tx({viewers:d.ob,ke:d.ke,id:d.id},'MON_'+d.id)
2267 break;
2268 case'start':case'stop':
2269 s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[cn.ke,d.id],function(err,r) {
2270 if(r&&r[0]){r=r[0]
2271 s.camera(d.ff,{type:r.type,url:s.init('url',r),id:d.id,mode:d.ff,ke:cn.ke});
2272 }
2273 })
2274 break;
2275 }
2276 break;
2277 case'video':
2278 switch(d.ff){
2279 case'fix':
2280 s.video('fix',d)
2281 break;
2282 case'delete':
2283 s.video('delete',d)
2284 break;
2285 }
2286 break;
2287 case'ffprobe':
2288 if(s.group[cn.ke].users[cn.auth]){
2289 switch(d.ff){
2290 case'stop':
2291 exec('kill -9 '+s.group[cn.ke].users[cn.auth].ffprobe.pid,{detatched: true})
2292 break;
2293 default:
2294 if(s.group[cn.ke].users[cn.auth].ffprobe){
2295 return
2296 }
2297 s.group[cn.ke].users[cn.auth].ffprobe=1;
2298 tx({f:'ffprobe_start'})
2299 exec('ffprobe '+('-v quiet -print_format json -show_format -show_streams '+d.query),function(err,data){
2300 tx({f:'ffprobe_data',data:data.toString('utf8')})
2301 delete(s.group[cn.ke].users[cn.auth].ffprobe)
2302 tx({f:'ffprobe_stop'})
2303 })
2304 //auto kill in 30 seconds
2305 setTimeout(function(){
2306 exec('kill -9 '+d.pid,{detached: true})
2307 },30000)
2308 break;
2309 }
2310 }
2311 break;
2312 case'onvif':
2313 d.ip=d.ip.replace(/ /g,'');
2314 d.port=d.port.replace(/ /g,'');
2315 if(d.ip===''){
2316 var interfaces = os.networkInterfaces();
2317 var addresses = [];
2318 for (var k in interfaces) {
2319 for (var k2 in interfaces[k]) {
2320 var address = interfaces[k][k2];
2321 if (address.family === 'IPv4' && !address.internal) {
2322 addresses.push(address.address);
2323 }
2324 }
2325 }
2326 d.arr=[]
2327 addresses.forEach(function(v){
2328 if(v.indexOf('0.0.0')>-1){return false}
2329 v=v.split('.');
2330 delete(v[3]);
2331 v=v.join('.');
2332 d.arr.push(v+'1-'+v+'254')
2333 })
2334 d.ip=d.arr.join(',')
2335 }
2336 if(d.port===''){
2337 d.port='80,8080,554'
2338 }
2339 d.ip.split(',').forEach(function(v){
2340 if(v.indexOf('-')>-1){
2341 v=v.split('-');
2342 d.IP_RANGE_START = v[0],
2343 d.IP_RANGE_END = v[1];
2344 }else{
2345 d.IP_RANGE_START = v;
2346 d.IP_RANGE_END = v;
2347 }
2348 if(!d.IP_LIST){
2349 d.IP_LIST = s.ipRange(d.IP_RANGE_START,d.IP_RANGE_END);
2350 }else{
2351 d.IP_LIST=d.IP_LIST.concat(s.ipRange(d.IP_RANGE_START,d.IP_RANGE_END))
2352 }
2353 //check port
2354 if(d.port.indexOf('-')>-1){
2355 d.port=d.port.split('-');
2356 d.PORT_RANGE_START = d.port[0];
2357 d.PORT_RANGE_END = d.port[1];
2358 d.PORT_LIST = s.portRange(d.PORT_RANGE_START,d.PORT_RANGE_END);
2359 }else{
2360 d.PORT_LIST=d.port.split(',')
2361 }
2362 //check user name and pass
2363 d.USERNAME='';
2364 if(d.user){
2365 d.USERNAME = d.user
2366 }
2367 d.PASSWORD='';
2368 if(d.pass){
2369 d.PASSWORD = d.pass
2370 }
2371 })
2372 d.cams=[]
2373 d.IP_LIST.forEach(function(ip_entry,n) {
2374 d.PORT_LIST.forEach(function(port_entry,nn) {
2375 return new Cam({
2376 hostname: ip_entry,
2377 username: d.USERNAME,
2378 password: d.PASSWORD,
2379 port: port_entry,
2380 timeout : 5000
2381 }, function CamFunc(err,data) {
2382 if (err) return;
2383 data={f:'onvif',ip:ip_entry,port:port_entry}
2384 var cam_obj = this;
2385 cam_obj.getSystemDateAndTime(function(er, date, xml) {
2386 if (!er) data.date = date;
2387 cam_obj.getDeviceInformation(function(er, info, xml) {
2388 if (!er) data.info = info;
2389 try {
2390 cam_obj.getStreamUri({
2391 protocol: 'RTSP'
2392 },function(er, stream, xml) {
2393 if (!er) data.url = stream;
2394 tx(data)
2395 });
2396 }catch(err){
2397 tx(data);
2398 }
2399 });
2400 });
2401 });
2402 }); // foreach
2403 }); // foreach
2404 break;
2405 }
2406 }catch(er){
2407 s.systemLog('ERROR CATCH 1',er)
2408 }
2409 }else{
2410 tx({ok:false,msg:lang.NotAuthorizedText1});
2411 }
2412 });
2413 //functions for receiving detector data
2414 cn.on('ocv',function(d){
2415 if(!cn.ocv&&d.f==='init'){
2416 if(config.pluginKeys[d.plug]===d.pluginKey){
2417 s.api[cn.id]={ocvID:cn.id,permissions:{},details:{},ip:'0.0.0.0'};
2418 s.ocv={started:moment(),id:cn.id,plug:d.plug,notice:d.notice};
2419 cn.ocv=1;
2420 s.tx({f:'api_key',key:cn.id},cn.id)
2421 s.tx({f:'detector_plugged',plug:d.plug,notice:d.notice},'CPU')
2422 s.tx({f:'readPlugins',ke:d.ke},'CPU')
2423 s.systemLog('Connected to plugin : Detector - '+d.plug)
2424 }else{
2425 cn.disconnect()
2426 }
2427 }else{
2428 if(config.pluginKeys[d.plug]===d.pluginKey){
2429 switch(d.f){
2430 case'trigger':
2431 s.camera('motion',d)
2432 break;
2433 case's.tx':
2434 s.tx(d.data,d.to)
2435 break;
2436 case'sql':
2437 s.sqlQuery(d.query,d.values);
2438 break;
2439 case'log':
2440 s.systemLog('PLUGIN : '+d.plug+' : ',d)
2441 break;
2442 }
2443 }else{
2444 cn.disconnect()
2445 }
2446 }
2447 })
2448 //functions for retrieving cron announcements
2449 cn.on('cron',function(d){
2450 if(d.f==='init'){
2451 if(config.cron.key){
2452 if(config.cron.key===d.cronKey){
2453 s.cron={started:moment(),last_run:moment(),id:cn.id};
2454 }else{
2455 cn.disconnect()
2456 }
2457 }else{
2458 s.cron={started:moment(),last_run:moment(),id:cn.id};
2459 }
2460 }else{
2461 if(s.cron&&cn.id===s.cron.id){
2462 delete(d.cronKey)
2463 switch(d.f){
2464 case'filters':
2465 s.filter(d.ff,d);
2466 break;
2467 case's.tx':
2468 s.tx(d.data,d.to)
2469 break;
2470 case's.video':
2471 s.video(d.data,d.file)
2472 break;
2473 case'start':case'end':
2474 d.mid='_cron';s.log(d,{type:'cron',msg:d.msg})
2475 break;
2476 default:
2477 s.systemLog('CRON : ',d)
2478 break;
2479 }
2480 }else{
2481 cn.disconnect()
2482 }
2483 }
2484 })
2485 // admin page socket functions
2486 cn.on('super',function(d){
2487 if(!cn.init&&d.f=='init'){
2488 d.ok=s.superAuth({mail:d.mail,pass:d.pass},function(data){
2489 cn.uid=d.mail
2490 cn.join('$');
2491 cn.ip=cn.request.connection.remoteAddress
2492 s.log({ke:'$',mid:'$USER'},{type:lang['Websocket Connected'],msg:{for:lang['Superuser'],id:cn.uid,ip:cn.ip}})
2493 cn.init='super';
2494 cn.mail=d.mail;
2495 s.tx({f:'init_success',mail:d.mail},cn.id);
2496 })
2497 if(d.ok===false){
2498 cn.disconnect();
2499 }
2500 }else{
2501 if(cn.mail&&cn.init=='super'){
2502 switch(d.f){
2503 case'logs':
2504 switch(d.ff){
2505 case'delete':
2506 s.sqlQuery('DELETE FROM Logs WHERE ke=?',[d.ke])
2507 break;
2508 }
2509 break;
2510 case'system':
2511 switch(d.ff){
2512 case'update':
2513 s.ffmpegKill()
2514 s.systemLog('Shinobi ordered to update',{by:cn.mail,ip:cn.ip,distro:d.distro})
2515 exec('chmod +x '+__dirname+'/UPDATE.sh&&'+__dirname+'/./UPDATE.sh '+d.distro,{detached: true})
2516 break;
2517 case'restart':
2518 d.check=function(x){return d.target.indexOf(x)>-1}
2519 if(d.check('system')){
2520 s.systemLog('Shinobi ordered to restart',{by:cn.mail,ip:cn.ip})
2521 s.ffmpegKill()
2522 exec('pm2 restart '+__dirname+'/camera.js')
2523 }
2524 if(d.check('cron')){
2525 s.systemLog('Shinobi CRON ordered to restart',{by:cn.mail,ip:cn.ip})
2526 exec('pm2 restart '+__dirname+'/cron.js')
2527 }
2528 if(d.check('logs')){
2529 s.systemLog('Flush PM2 Logs',{by:cn.mail,ip:cn.ip})
2530 exec('pm2 flush')
2531 }
2532 break;
2533 case'configure':
2534 s.systemLog('conf.json Modified',{by:cn.mail,ip:cn.ip,old:jsonfile.readFileSync(location.config)})
2535 jsonfile.writeFile(location.config,d.data,{spaces: 2},function(){
2536 s.tx({f:'save_configuration'},cn.id)
2537 })
2538 break;
2539 }
2540 break;
2541 case'accounts':
2542 switch(d.ff){
2543 case'register':
2544 if(d.form.mail!==''&&d.form.pass!==''){
2545 if(d.form.pass===d.form.password_again){
2546 s.sqlQuery('SELECT * FROM Users WHERE mail=?',[d.form.mail],function(err,r) {
2547 if(r&&r[0]){//found one exist
2548 d.msg='Email address is in use.';
2549 s.tx({f:'error',ff:'account_register',msg:d.msg},cn.id)
2550 }else{//create new
2551 //user id
2552 d.form.uid=s.gid();
2553 //check to see if custom key set
2554 if(!d.form.ke||d.form.ke===''){
2555 d.form.ke=s.gid()
2556 }
2557 s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[d.form.ke,d.form.uid,d.form.mail,s.md5(d.form.pass),d.form.details])
2558 s.tx({f:'add_account',details:d.form.details,ke:d.form.ke,uid:d.form.uid,mail:d.form.mail},'$');
2559 }
2560 })
2561 }else{
2562 d.msg=lang["Passwords Don't Match"];
2563 }
2564 }else{
2565 d.msg=lang['Fields cannot be empty'];
2566 }
2567 if(d.msg){
2568 s.tx({f:'error',ff:'account_register',msg:d.msg},cn.id)
2569 }
2570 break;
2571 case'edit':
2572 if(d.form.pass&&d.form.pass!==''){
2573 if(d.form.pass===d.form.password_again){
2574 d.form.pass=s.md5(d.form.pass);
2575 }else{
2576 s.tx({f:'error',ff:'account_edit',msg:lang["Passwords Don't Match"]},cn.id)
2577 return
2578 }
2579 }else{
2580 delete(d.form.pass);
2581 }
2582 delete(d.form.password_again);
2583 d.keys=Object.keys(d.form);
2584 d.set=[];
2585 d.values=[];
2586 d.keys.forEach(function(v,n){
2587 if(d.set==='ke'||d.set==='password_again'||!d.form[v]){return}
2588 d.set.push(v+'=?')
2589 d.values.push(d.form[v])
2590 })
2591 d.values.push(d.account.mail)
2592 s.sqlQuery('UPDATE Users SET '+d.set.join(',')+' WHERE mail=?',d.values,function(err,r) {
2593 if(err){
2594 s.tx({f:'error',ff:'account_edit',msg:lang.AccountEditText1},cn.id)
2595 return
2596 }
2597 s.tx({f:'edit_account',form:d.form,ke:d.account.ke,uid:d.account.uid},'$');
2598 delete(s.group[d.account.ke].init);
2599 s.init('apps',d.account)
2600 })
2601 break;
2602 case'delete':
2603 s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[d.account.uid,d.account.ke,d.account.mail])
2604 s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[d.account.uid,d.account.ke])
2605 s.tx({f:'delete_account',ke:d.account.ke,uid:d.account.uid,mail:d.account.mail},'$');
2606 break;
2607 }
2608 break;
2609 }
2610 }
2611 }
2612 })
2613 // admin page socket functions
2614 cn.on('a',function(d){
2615 if(!cn.init&&d.f=='init'){
2616 s.sqlQuery('SELECT * FROM Users WHERE auth=? && uid=?',[d.auth,d.uid],function(err,r){
2617 if(r&&r[0]){
2618 r=r[0];
2619 if(!s.group[d.ke]){s.group[d.ke]={users:{}}}
2620 if(!s.group[d.ke].users[d.auth]){s.group[d.ke].users[d.auth]={cnid:cn.id}}
2621 try{s.group[d.ke].users[d.auth].details=JSON.parse(r.details)}catch(er){}
2622 cn.join('ADM_'+d.ke);
2623 cn.ke=d.ke;
2624 cn.uid=d.uid;
2625 cn.auth=d.auth;
2626 cn.init='admin';
2627 }else{
2628 cn.disconnect();
2629 }
2630 })
2631 }else{
2632 s.auth({auth:d.auth,ke:d.ke,id:d.id,ip:cn.request.connection.remoteAddress},function(user){
2633 if(!user.details.sub){
2634 switch(d.f){
2635 case'accounts':
2636 switch(d.ff){
2637 case'edit':
2638 d.keys=Object.keys(d.form);
2639 d.condition=[];
2640 d.value=[];
2641 d.keys.forEach(function(v){
2642 d.condition.push(v+'=?')
2643 d.value.push(d.form[v])
2644 })
2645 d.value=d.value.concat([cn.ke,d.$uid])
2646 s.sqlQuery("UPDATE Users SET "+d.condition.join(',')+" WHERE ke=? AND uid=?",d.value)
2647 s.tx({f:'edit_sub_account',ke:cn.ke,uid:d.$uid,mail:d.mail,form:d.form},'ADM_'+d.ke);
2648 break;
2649 case'delete':
2650 s.sqlQuery('DELETE FROM Users WHERE uid=? AND ke=? AND mail=?',[d.$uid,cn.ke,d.mail])
2651 s.sqlQuery('DELETE FROM API WHERE uid=? AND ke=?',[d.$uid,cn.ke])
2652 s.tx({f:'delete_sub_account',ke:cn.ke,uid:d.$uid,mail:d.mail},'ADM_'+d.ke);
2653 break;
2654 }
2655 break;
2656 }
2657 }
2658 })
2659 }
2660 })
2661 //functions for webcam recorder
2662 cn.on('r',function(d){
2663 if(!cn.ke&&d.f==='init'){
2664 s.sqlQuery('SELECT ke,uid,auth,mail,details FROM Users WHERE ke=? AND auth=? AND uid=?',[d.ke,d.auth,d.uid],function(err,r) {
2665 if(r&&r[0]){
2666 r=r[0]
2667 cn.ke=d.ke,cn.uid=d.uid,cn.auth=d.auth;
2668 if(!s.group[d.ke])s.group[d.ke]={};
2669 if(!s.group[d.ke].users)s.group[d.ke].users={};
2670 s.group[d.ke].users[d.auth]={cnid:cn.id,uid:r.uid,mail:r.mail,details:JSON.parse(r.details),logged_in_at:moment(new Date).format(),login_type:'Streamer'}
2671 }
2672 })
2673 }else{
2674 switch(d.f){
2675 case'monitor_chunk':
2676 if(!s.group[d.ke]||!s.group[d.ke].mon[d.mid]){return}
2677 if(s.group[d.ke].mon[d.mid].started!==1){s.tx({error:'Not Started'},cn.id);return false};
2678 s.group[d.ke].mon[d.mid].spawn.stdin.write(new Buffer(d.chunk, "binary"));
2679 break;
2680 case'monitor_frame':
2681 if(!s.group[d.ke]||!s.group[d.ke].mon[d.mid]){return}
2682 if(s.group[d.ke].mon[d.mid].started!==1){s.tx({error:'Not Started'},cn.id);return false};
2683 s.group[d.ke].mon[d.mid].spawn.stdin.write(d.frame);
2684 break;
2685 }
2686 }
2687 })
2688 //functions for dispersing work to child servers;
2689 cn.on('c',function(d){
2690// if(!cn.ke&&d.socket_key===s.child_key){
2691 if(!cn.shinobi_child&&d.f=='init'){
2692 cn.ip=cn.request.connection.remoteAddress;
2693 cn.name=d.u.name;
2694 cn.shinobi_child=1;
2695 tx=function(z){cn.emit('c',z);}
2696 if(!s.child_nodes[cn.ip]){s.child_nodes[cn.ip]=d.u;};
2697 s.child_nodes[cn.ip].cnid=cn.id;
2698 s.child_nodes[cn.ip].cpu=0;
2699 tx({f:'init_success',child_nodes:s.child_nodes});
2700 }else{
2701 if(d.f!=='s.tx'){s.systemLog('CRON',d)};
2702 switch(d.f){
2703 case'cpu':
2704 s.child_nodes[cn.ip].cpu=d.cpu;
2705 break;
2706 case'sql':
2707 s.sqlQuery(d.query,d.values);
2708 break;
2709 case'camera':
2710 s.camera(d.mode,d.data)
2711 break;
2712 case's.tx':
2713 s.tx(d.data,d.to)
2714 break;
2715 case's.log':
2716 s.log(d.data,d.to)
2717 break;
2718 case'created_file':
2719 if(d.details&&d.details.dir&&d.details.dir!==''){
2720 d.dir=s.checkCorrectPathEnding(d.details.dir)+d.ke+'/'+d.id+'/'
2721 }else{
2722 d.dir=s.dir.videos+d.ke+'/'+d.id+'/';
2723 }
2724 fs.writeFile(d.dir+d.filename,d.created_file,'binary',function (err,data) {
2725 if (err) {
2726 return console.error('created_file'+d.d.mid,err);
2727 }
2728 tx({f:'delete_file',file:d.filename,ke:d.d.ke,mid:d.d.mid}); s.tx({f:'video_build_success',filename:s.group[d.d.ke].mon[d.d.mid].open+'.'+s.group[d.d.ke].mon[d.d.mid].open_ext,mid:d.d.mid,ke:d.d.ke,time:s.nameToTime(s.group[d.d.ke].mon[d.d.mid].open),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+d.d.ke);
2729 });
2730 break;
2731 }
2732 }
2733// }
2734 })
2735 //embed functions
2736 cn.on('e', function (d) {
2737 tx=function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);}
2738 switch(d.f){
2739 case'init':
2740 if(!s.group[d.ke]||!s.group[d.ke].mon[d.id]||s.group[d.ke].mon[d.id].started===0){return false}
2741 s.auth({auth:d.auth,ke:d.ke,id:d.id,ip:cn.request.connection.remoteAddress},function(user){
2742 cn.embedded=1;
2743 cn.ke=d.ke;
2744 if(!cn.mid){cn.mid={}}
2745 cn.mid[d.id]={};
2746// if(!s.group[d.ke].embed){s.group[d.ke].embed={}}
2747// if(!s.group[d.ke].embed[d.mid]){s.group[d.ke].embed[d.mid]={}}
2748// s.group[d.ke].embed[d.mid][cn.id]={}
2749
2750 s.camera('watch_on',d,cn,tx)
2751 cn.join('MON_'+d.id);
2752 cn.join('MON_STREAM_'+d.id);
2753 cn.join('STR_'+d.ke);
2754 if(s.group[d.ke]&&s.group[d.ke].mon[d.id]&&s.group[d.ke].mon[d.id].watch){
2755
2756 tx({f:'monitor_watch_on',id:d.id,ke:d.ke},'MON_'+d.id)
2757 s.tx({viewers:Object.keys(s.group[d.ke].mon[d.id].watch).length,ke:d.ke,id:d.id},'MON_'+d.id)
2758 }
2759 });
2760 break;
2761 }
2762 })
2763 cn.on('disconnect', function () {
2764 if(cn.ke){
2765 if(cn.monitor_watching){
2766 cn.monitor_count=Object.keys(cn.monitor_watching)
2767 if(cn.monitor_count.length>0){
2768 cn.monitor_count.forEach(function(v){
2769 s.camera('watch_off',{id:v,ke:cn.monitor_watching[v].ke},s.cn(cn))
2770 })
2771 }
2772 }
2773 if(!cn.embedded){
2774 if(s.group[cn.ke].users[cn.auth].login_type==='Dashboard'){
2775 s.tx({f:'user_status_change',ke:cn.ke,uid:cn.uid,status:0})
2776 }
2777 s.log({ke:cn.ke,mid:'$USER'},{type:lang['Websocket Disconnected'],msg:{mail:s.group[cn.ke].users[cn.auth].mail,id:cn.uid,ip:cn.ip}})
2778 delete(s.group[cn.ke].users[cn.auth]);
2779 }
2780 }
2781 if(cn.ocv){
2782 s.tx({f:'detector_unplugged',plug:s.ocv.plug},'CPU')
2783 delete(s.ocv);
2784 delete(s.api[cn.id])
2785 }
2786 if(cn.cron){
2787 delete(s.cron);
2788 }
2789 if(cn.shinobi_child){
2790 delete(s.child_nodes[cn.ip]);
2791 }
2792 })
2793});
2794//Authenticator functions
2795s.api={};
2796//auth handler
2797//params = parameters
2798//cb = callback
2799//res = response, only needed for express (http server)
2800//request = request, only needed for express (http server)
2801s.auth=function(params,cb,res,req){
2802 if(req){
2803 //express (http server) use of auth function
2804 params.ip=req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
2805 var failed=function(){
2806 if(!req.ret){req.ret={ok:false}}
2807 req.ret.msg=lang['Not Authorized'];
2808 res.end(s.s(req.ret, null, 3));
2809 }
2810 }else{
2811 //socket.io use of auth function
2812 var failed=function(){
2813 //maybe log
2814 }
2815 }
2816 if(!params.auth||params.auth===''||params.auth===null||params.auth===undefined){
2817 return failed()
2818 }
2819 var clearAfterTime=function(){
2820 //remove temp key from memory
2821 clearTimeout(s.api[params.auth].timeout)
2822 s.api[params.auth].timeout=setTimeout(function(){
2823 delete(s.api[params.auth])
2824 },1000*60*5)
2825 }
2826 //check IP address of connecting user
2827 var finish=function(user){
2828 if(s.api[params.auth].ip.indexOf('0.0.0.0')>-1||s.api[params.auth].ip.indexOf(params.ip)>-1){
2829 cb(user);
2830 }else{
2831 failed();
2832 }
2833 }
2834 //check if auth key is user's temporary session key
2835 if(s.group[params.ke]&&s.group[params.ke].users&&s.group[params.ke].users[params.auth]){
2836 s.group[params.ke].users[params.auth].permissions={};
2837 cb(s.group[params.ke].users[params.auth]);
2838 }else{
2839 //check if key is already in memory to save query time
2840 if(s.api[params.auth]&&s.api[params.auth].details){
2841 finish(s.api[params.auth]);
2842 if(s.api[params.auth].timeout){
2843 clearAfterTime()
2844 }
2845 }else{
2846 //no key in memory, query db to see if key exists
2847 s.sqlQuery('SELECT * FROM API WHERE code=? AND ke=?',[params.auth,params.ke],function(err,r){
2848 if(r&&r[0]){
2849 r=r[0];
2850 s.api[params.auth]={ip:r.ip,uid:r.uid,ke:r.ke,permissions:JSON.parse(r.details),details:{}};
2851 s.sqlQuery('SELECT details FROM Users WHERE uid=? AND ke=?',[r.uid,r.ke],function(err,rr){
2852 if(rr&&rr[0]){
2853 rr=rr[0];
2854 try{
2855 s.api[params.auth].mail=rr.mail
2856 s.api[params.auth].details=JSON.parse(rr.details)
2857 s.api[params.auth].lang=s.getLanguageFile(s.api[params.auth].details.lang)
2858 }catch(er){}
2859 }
2860 finish(s.api[params.auth]);
2861 })
2862 }else{
2863 s.sqlQuery('SELECT * FROM Users WHERE auth=? AND ke=?',[params.auth,params.ke],function(err,r){
2864 if(r&&r[0]){
2865 r=r[0];
2866 r.ip='0.0.0.0'
2867 s.api[params.auth]=r
2868 clearAfterTime()
2869 finish(r)
2870 }else{
2871 failed();
2872 }
2873 })
2874 }
2875 })
2876 }
2877 }
2878}
2879//super user authentication handler
2880s.superAuth=function(x,callback){
2881 req={};
2882 req.super=require(location.super);
2883 req.super.forEach(function(v,n){
2884 if(x.md5===true){
2885 x.pass=s.md5(x.pass);
2886 }
2887 if(x.mail.toLowerCase()===v.mail.toLowerCase()&&x.pass===v.pass){
2888 req.found=1;
2889 if(x.users===true){
2890 s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r) {
2891 callback({$user:v,users:r,config:config,lang:lang})
2892 })
2893 }else{
2894 callback({$user:v,config:config,lang:lang})
2895 }
2896 }
2897 })
2898 if(req.found!==1){
2899 return false;
2900 }else{
2901 return true;
2902 }
2903}
2904////Pages
2905app.enable('trust proxy');
2906app.use('/libs',express.static(__dirname + '/web/libs'));
2907app.use(bodyParser.json());
2908app.use(bodyParser.urlencoded({extended: true}));
2909app.set('views', __dirname + '/web/pages');
2910app.set('view engine','ejs');
2911//readme
2912app.get('/:auth/logout/:ke/:id', function (req,res){
2913 if(s.group[req.params.ke]&&s.group[req.params.ke].users[req.params.auth]){
2914 delete(s.api[req.params.auth]);
2915 delete(s.group[req.params.ke].users[req.params.auth]);
2916 s.sqlQuery("UPDATE Users SET auth=? WHERE auth=? AND ke=? AND uid=?",['',req.params.auth,req.params.ke,req.params.id])
2917 res.end(s.s({ok:true,msg:'You have been logged out, session key is now inactive.'}, null, 3))
2918 }else{
2919 res.end(s.s({ok:false,msg:'This group key does not exist or this user is not logged in.'}, null, 3))
2920 }
2921});
2922//readme
2923app.get('/info', function (req,res){
2924 res.sendFile(__dirname+'/index.html');
2925});
2926//main page
2927app.get(['/','/:screen'], function (req,res){
2928 res.render('index',{lang:lang,config:config,screen:req.params.screen},function(err,html){
2929 if(err){
2930 s.systemLog(err)
2931 }
2932 res.end(html)
2933 })
2934});
2935//update server
2936app.get('/:auth/update/:key', function (req,res){
2937 req.ret={ok:false};
2938 res.setHeader('Content-Type', 'application/json');
2939 req.fn=function(user){
2940 if(!config.updateKey){
2941 req.ret.msg=user.lang.updateKeyText1;
2942 return;
2943 }
2944 if(req.params.key===config.updateKey){
2945 req.ret.ok=true;
2946 exec('chmod +x '+__dirname+'/UPDATE.sh&&'+__dirname+'/./UPDATE.sh',{detached: true})
2947 }else{
2948 req.ret.msg=user.lang.updateKeyText2;
2949 }
2950 res.end(s.s(req.ret, null, 3));
2951 }
2952 s.auth(req.params,req.fn,res,req);
2953});
2954//get user details by API key
2955app.get('/:auth/userInfo/:ke',function (req,res){
2956 req.ret={ok:false};
2957 res.setHeader('Content-Type', 'application/json');
2958 res.header("Access-Control-Allow-Origin",req.headers.origin);
2959 s.auth(req.params,function(user){
2960 req.ret.ok=true
2961 req.ret.user=user
2962 res.end(s.s(req.ret, null, 3));
2963 },res,req);
2964})
2965//register function
2966app.post('/:auth/register/:ke/:uid',function (req,res){
2967 req.resp={ok:false};
2968 res.setHeader('Content-Type', 'application/json');
2969 s.auth(req.params,function(user){
2970 s.sqlQuery('SELECT * FROM Users WHERE uid=? AND ke=? AND details NOT LIKE ? LIMIT 1',[req.params.uid,req.params.ke,'%"sub"%'],function(err,u) {
2971 if(u&&u[0]){
2972 if(req.body.mail!==''&&req.body.pass!==''){
2973 if(req.body.pass===req.body.password_again){
2974 s.sqlQuery('SELECT * FROM Users WHERE mail=?',[req.body.mail],function(err,r) {
2975 if(r&&r[0]){//found one exist
2976 req.resp.msg='Email address is in use.';
2977 }else{//create new
2978 req.resp.msg='New Account Created';req.resp.ok=true;
2979 req.gid=s.gid();
2980 req.body.details='{"sub":"1","allmonitors":"1"}';
2981 s.sqlQuery('INSERT INTO Users (ke,uid,mail,pass,details) VALUES (?,?,?,?,?)',[req.params.ke,req.gid,req.body.mail,s.md5(req.body.pass),req.body.details])
2982 s.tx({f:'add_sub_account',details:req.body.details,ke:req.params.ke,uid:req.gid,mail:req.body.mail},'ADM_'+req.params.ke);
2983 }
2984 res.end(s.s(req.resp,null,3));
2985 })
2986 }else{
2987 req.resp.msg=user.lang['Passwords Don\'t Match'];
2988 }
2989 }else{
2990 req.resp.msg=user.lang['Fields cannot be empty'];
2991 }
2992 }else{
2993 req.resp.msg=user.lang['Not an Administrator Account'];
2994 }
2995 if(req.resp.msg){
2996 res.end(s.s(req.resp,null,3));
2997 }
2998 })
2999 },res,req);
3000})
3001//login function
3002s.deleteFactorAuth=function(r){
3003 delete(s.factorAuth[r.ke][r.uid])
3004 if(Object.keys(s.factorAuth[r.ke]).length===0){
3005 delete(s.factorAuth[r.ke])
3006 }
3007}
3008app.post(['/','/:screen'],function (req,res){
3009 req.ip=req.headers['cf-connecting-ip']||req.headers["CF-Connecting-IP"]||req.headers["'x-forwarded-for"]||req.connection.remoteAddress;
3010 if(req.query.json=='true'){
3011 res.header("Access-Control-Allow-Origin",req.headers.origin);
3012 }
3013 req.renderFunction=function(focus,data){
3014 if(req.query.json=='true'){
3015 delete(data.config)
3016 data.ok=true;
3017 res.setHeader('Content-Type', 'application/json');
3018 res.end(s.s(data, null, 3))
3019 }else{
3020 data.screen=req.params.screen
3021 res.render(focus,data,function(err,html){
3022 if(err){
3023 s.systemLog(err)
3024 }
3025 res.end(html)
3026 });
3027 }
3028 }
3029 req.failed=function(board){
3030 if(req.query.json=='true'){
3031 res.setHeader('Content-Type', 'application/json');
3032 res.end(s.s({ok:false}, null, 3))
3033 }else{
3034 res.render('index',{failedLogin:true,lang:lang,config:config,screen:req.params.screen},function(err,html){
3035 if(err){
3036 s.systemLog(err)
3037 }
3038 res.end(html);
3039 });
3040 }
3041 req.logTo={ke:'$',mid:'$USER'}
3042 req.logData={type:lang['Authentication Failed'],msg:{for:board,mail:req.body.mail,ip:req.ip}}
3043 if(board==='super'){
3044 s.log(req.logTo,req.logData)
3045 }else{
3046 s.sqlQuery('SELECT ke,uid,details FROM Users WHERE mail=?',[req.body.mail],function(err,r) {
3047 if(r&&r[0]){
3048 r=r[0]
3049 r.details=JSON.parse(r.details);
3050 r.lang=s.getLanguageFile(r.details.lang)
3051 req.logData.id=r.uid
3052 req.logData.type=r.lang['Authentication Failed']
3053 req.logTo.ke=r.ke
3054 }
3055 s.log(req.logTo,req.logData)
3056 })
3057 }
3058 }
3059 req.fn=function(r){
3060 switch(req.body.function){
3061 case'cam':
3062 s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND type=?',[r.ke,"dashcam"],function(err,rr){
3063 req.resp.mons=rr;
3064 req.renderFunction("dashcam",{$user:req.resp,lang:r.lang,define:s.getDefinitonFile(r.details.lang)});
3065 })
3066 break;
3067 case'streamer':
3068 s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND type=?',[r.ke,"socket"],function(err,rr){
3069 req.resp.mons=rr;
3070 req.renderFunction("streamer",{$user:req.resp,lang:r.lang,define:s.getDefinitonFile(r.details.lang)});
3071 })
3072 break;
3073 case'admin':
3074 if(!r.details.sub){
3075 s.sqlQuery('SELECT uid,mail,details FROM Users WHERE ke=? AND details LIKE \'%"sub"%\'',[r.ke],function(err,rr) {
3076 s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[r.ke],function(err,rrr) {
3077 req.renderFunction("admin",{$user:req.resp,$subs:rr,$mons:rrr,lang:r.lang,define:s.getDefinitonFile(r.details.lang)});
3078 })
3079 })
3080 }else{
3081 //not admin user
3082 req.renderFunction("home",{$user:req.resp,config:config,lang:r.lang,define:s.getDefinitonFile(r.details.lang),addStorage:s.dir.addStorage,fs:fs});
3083 }
3084 break;
3085 default:
3086 req.renderFunction("home",{$user:req.resp,config:config,lang:r.lang,define:s.getDefinitonFile(r.details.lang),addStorage:s.dir.addStorage,fs:fs});
3087 break;
3088 }
3089 s.log({ke:r.ke,mid:'$USER'},{type:r.lang['New Authentication Token'],msg:{for:req.body.function,mail:r.mail,id:r.uid,ip:req.ip}})
3090 // res.end();
3091 }
3092 if(req.body.mail&&req.body.pass){
3093 req.default=function(){
3094 s.sqlQuery('SELECT * FROM Users WHERE mail=? AND pass=?',[req.body.mail,s.md5(req.body.pass)],function(err,r) {
3095 req.resp={ok:false};
3096 if(!err&&r&&r[0]){
3097 r=r[0];r.auth=s.md5(s.gid());
3098 s.sqlQuery("UPDATE Users SET auth=? WHERE ke=? AND uid=?",[r.auth,r.ke,r.uid])
3099 req.resp={ok:true,auth_token:r.auth,ke:r.ke,uid:r.uid,mail:r.mail,details:r.details};
3100 r.details=JSON.parse(r.details);
3101 r.lang=s.getLanguageFile(r.details.lang)
3102 req.factorAuth=function(cb){
3103 if(r.details.factorAuth==="1"){
3104 if(!r.details.acceptedMachines||!(r.details.acceptedMachines instanceof Object)){
3105 r.details.acceptedMachines={}
3106 }
3107 if(!r.details.acceptedMachines[req.body.machineID]){
3108 req.complete=function(){
3109 s.factorAuth[r.ke][r.uid].info=req.resp;
3110 clearTimeout(s.factorAuth[r.ke][r.uid].expireAuth)
3111 s.factorAuth[r.ke][r.uid].expireAuth=setTimeout(function(){
3112 s.deleteFactorAuth(r)
3113 },1000*60*15)
3114 req.renderFunction("factor",{$user:req.resp,lang:r.lang})
3115 }
3116 if(!s.factorAuth[r.ke]){s.factorAuth[r.ke]={}}
3117 if(!s.factorAuth[r.ke][r.uid]){
3118 s.factorAuth[r.ke][r.uid]={key:s.nid(),user:r}
3119 r.mailOptions = {
3120 from: '"ShinobiCCTV" <no-reply@shinobi.video>',
3121 to: r.mail,
3122 subject: r.lang['2-Factor Authentication'],
3123 html: r.lang['Enter this code to proceed']+' <b>'+s.factorAuth[r.ke][r.uid].key+'</b>. '+r.lang.FactorAuthText1,
3124 };
3125 nodemailer.sendMail(r.mailOptions, (error, info) => {
3126 if (error) {
3127 s.systemLog(r.lang.MailError,error)
3128 req.fn(r)
3129 return
3130 }
3131 req.complete()
3132 });
3133 }else{
3134 req.complete()
3135 }
3136 }else{
3137 req.fn(r)
3138 }
3139 }else{
3140 req.fn(r)
3141 }
3142 }
3143 if(r.details.sub){
3144 s.sqlQuery('SELECT details FROM Users WHERE ke=? AND details NOT LIKE ?',[r.ke,'%"sub"%'],function(err,rr) {
3145 rr=rr[0];
3146 rr.details=JSON.parse(rr.details);
3147 r.details.mon_groups=rr.details.mon_groups;
3148 req.resp.details=JSON.stringify(r.details);
3149 req.factorAuth()
3150 })
3151 }else{
3152 req.factorAuth()
3153 }
3154 }else{
3155 req.failed(req.body.function)
3156 }
3157 })
3158 }
3159 if(LdapAuth&&req.body.function==='ldap'&&req.body.key!==''){
3160 s.sqlQuery('SELECT * FROM Users WHERE ke=? AND details NOT LIKE ?',[req.body.key,'%"sub"%'],function(err,r) {
3161 if(r&&r[0]){
3162 r=r[0]
3163 r.details=JSON.parse(r.details)
3164 r.lang=s.getLanguageFile(r.details.lang)
3165 if(r.details.use_ldap!=='0'&&r.details.ldap_enable==='1'&&r.details.ldap_url&&r.details.ldap_url!==''){
3166 req.mailArray={}
3167 req.body.mail.split(',').forEach(function(v){
3168 v=v.split('=')
3169 req.mailArray[v[0]]=v[1]
3170 })
3171 if(!r.details.ldap_bindDN||r.details.ldap_bindDN===''){
3172 r.details.ldap_bindDN=req.body.mail
3173 }
3174 if(!r.details.ldap_bindCredentials||r.details.ldap_bindCredentials===''){
3175 r.details.ldap_bindCredentials=req.body.pass
3176 }
3177 if(!r.details.ldap_searchFilter||r.details.ldap_searchFilter===''){
3178 r.details.ldap_searchFilter=req.body.mail
3179 if(req.mailArray.cn){
3180 r.details.ldap_searchFilter='cn='+req.mailArray.cn
3181 }
3182 if(req.mailArray.uid){
3183 r.details.ldap_searchFilter='uid='+req.mailArray.uid
3184 }
3185 }else{
3186 r.details.ldap_searchFilter=r.details.ldap_searchFilter.replace('{{username}}',req.body.mail)
3187 }
3188 if(!r.details.ldap_searchBase||r.details.ldap_searchBase===''){
3189 r.details.ldap_searchBase='dc=test,dc=com'
3190 }
3191 req.auth = new LdapAuth({
3192 url:r.details.ldap_url,
3193 bindDN:r.details.ldap_bindDN,
3194 bindCredentials:r.details.ldap_bindCredentials,
3195 searchBase:r.details.ldap_searchBase,
3196 searchFilter:'('+r.details.ldap_searchFilter+')',
3197 reconnect:true
3198 });
3199 req.auth.on('error', function (err) {
3200 console.error('LdapAuth: ', err);
3201 });
3202
3203 req.auth.authenticate(req.body.mail, req.body.pass, function(err, user) {
3204 if(user){
3205 //found user
3206 if(!user.uid){
3207 user.uid=s.gid()
3208 }
3209 req.resp={
3210 ke:req.body.key,
3211 uid:user.uid,
3212 auth:s.md5(s.gid()),
3213 mail:user.mail,
3214 pass:s.md5(req.body.pass),
3215 details:JSON.stringify({
3216 sub:'1',
3217 ldap:'1',
3218 allmonitors:'1',
3219 filter: {}
3220 })
3221 }
3222 user.post=[]
3223 Object.keys(req.resp).forEach(function(v){
3224 user.post.push(req.resp[v])
3225 })
3226 s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Success'],msg:{user:user}})
3227 s.sqlQuery('SELECT * FROM Users WHERE ke=? AND mail=?',[req.body.key,user.cn],function(err,rr){
3228 if(rr&&rr[0]){
3229 //already registered
3230 rr=rr[0]
3231 req.resp=rr;
3232 rr.details=JSON.parse(rr.details)
3233 req.resp.lang=s.getLanguageFile(rr.details.lang)
3234 s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User Authenticated'],msg:{user:user,shinobiUID:rr.uid}})
3235 s.sqlQuery("UPDATE Users SET auth=? WHERE ke=? AND uid=?",[req.resp.auth,req.resp.ke,rr.uid])
3236 }else{
3237 //new ldap login
3238 s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User is New'],msg:{info:r.lang['Creating New Account'],user:user}})
3239 req.resp.lang=r.lang
3240 s.sqlQuery('INSERT INTO Users (ke,uid,auth,mail,pass,details) VALUES (?,?,?,?,?,?)',user.post)
3241 }
3242 req.resp.details=JSON.stringify(req.resp.details)
3243 req.resp.auth_token=req.resp.auth
3244 req.resp.ok=true
3245 req.fn(req.resp)
3246 })
3247 return
3248 }
3249 s.log({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Failed'],msg:{err:err}})
3250 //no user
3251 req.default()
3252 });
3253
3254 req.auth.close(function(err) {
3255
3256 })
3257 }else{
3258 req.default()
3259 }
3260 }else{
3261 req.default()
3262 }
3263 })
3264 }else{
3265 if(req.body.function==='super'){
3266 if(!fs.existsSync(location.super)){
3267 res.end(lang.superAdminText)
3268 return
3269 }
3270 req.ok=s.superAuth({mail:req.body.mail,pass:req.body.pass,users:true,md5:true},function(data){
3271 s.sqlQuery('SELECT * FROM Logs WHERE ke=? ORDER BY `time` DESC LIMIT 30',['$'],function(err,r) {
3272 if(!r){
3273 r=[]
3274 }
3275 data.Logs=r;
3276 fs.readFile(location.config,'utf8',function(err,file){
3277 data.plainConfig=JSON.parse(file)
3278 req.renderFunction("super",data);
3279 })
3280 })
3281 })
3282 if(req.ok===false){
3283 req.failed(req.body.function)
3284 }
3285 }else{
3286 req.default()
3287 }
3288 }
3289 }else{
3290 if(req.body.machineID&&req.body.factorAuthKey){
3291 if(s.factorAuth[req.body.ke]&&s.factorAuth[req.body.ke][req.body.id]&&s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
3292 if(s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
3293 if(req.body.remember==="1"){
3294 req.details=JSON.parse(s.factorAuth[req.body.ke][req.body.id].info.details)
3295 req.lang=s.getLanguageFile(req.details.lang)
3296 if(!req.details.acceptedMachines||!(req.details.acceptedMachines instanceof Object)){
3297 req.details.acceptedMachines={}
3298 }
3299 if(!req.details.acceptedMachines[req.body.machineID]){
3300 req.details.acceptedMachines[req.body.machineID]={}
3301 s.sqlQuery("UPDATE Users SET details=? WHERE ke=? AND uid=?",[s.s(req.details),req.body.ke,req.body.id])
3302 }
3303 }
3304 req.resp=s.factorAuth[req.body.ke][req.body.id].info
3305 req.fn(s.factorAuth[req.body.ke][req.body.id].user)
3306 }else{
3307 req.renderFunction("factor",{$user:s.factorAuth[req.body.ke][req.body.id].info,lang:req.lang});
3308 res.end();
3309 }
3310 }else{
3311 req.failed(lang['2-Factor Authentication'])
3312 }
3313 }else{
3314 req.failed(lang['2-Factor Authentication'])
3315 }
3316 }
3317});
3318// Get MPEG-DASH stream (mpd)
3319app.get('/:auth/mpd/:ke/:id/:file', function (req,res){
3320 res.header("Access-Control-Allow-Origin",req.headers.origin);
3321 req.fn=function(user){
3322 req.extension=req.params.file.split('.')
3323 req.extension=req.extension[req.extension.length-1]
3324 switch(req.extension){
3325 case'mpd':
3326 res.header("Content-Type","application/dash+xml");
3327 break;
3328 }
3329 req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/'+req.params.file;
3330 res.on('finish',function(){res.end();});
3331 if (fs.existsSync(req.dir)){
3332 fs.createReadStream(req.dir).pipe(res);
3333 }else{
3334 res.end(user.lang['File Not Found'])
3335 }
3336 }
3337 s.auth(req.params,req.fn,res,req);
3338});
3339// Get HLS stream (m3u8)
3340app.get('/:auth/hls/:ke/:id/:file', function (req,res){
3341 res.header("Access-Control-Allow-Origin",req.headers.origin);
3342 req.fn=function(user){
3343 req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/'+req.params.file;
3344 res.on('finish',function(){res.end();});
3345 if (fs.existsSync(req.dir)){
3346 fs.createReadStream(req.dir).pipe(res);
3347 }else{
3348 res.end(user.lang['File Not Found'])
3349 }
3350 }
3351 s.auth(req.params,req.fn,res,req);
3352});
3353//Get JPEG snap
3354app.get('/:auth/jpeg/:ke/:id/s.jpg', function(req,res){
3355 res.header("Access-Control-Allow-Origin",req.headers.origin);
3356 s.auth(req.params,function(user){
3357 if(user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
3358 res.end(user.lang['Not Permitted'])
3359 return
3360 }
3361 req.dir=s.dir.streams+req.params.ke+'/'+req.params.id+'/s.jpg';
3362 res.writeHead(200, {
3363 'Content-Type': 'image/jpeg',
3364 'Cache-Control': 'no-cache',
3365 'Pragma': 'no-cache'
3366 });
3367 res.on('finish',function(){res.end();delete(res)});
3368 if (fs.existsSync(req.dir)){
3369 fs.createReadStream(req.dir).pipe(res);
3370 }else{
3371 fs.createReadStream(config.defaultMjpeg).pipe(res);
3372 }
3373 },res,req);
3374});
3375//Get FLV stream
3376app.get('/:auth/flv/:ke/:id/s.flv', function(req,res) {
3377 res.header("Access-Control-Allow-Origin",req.headers.origin);
3378 s.auth(req.params,function(user){
3379 if(s.group[req.params.ke].mon[req.params.id].firstFLVchunk){
3380 if(!req.params.feed){req.params.feed='1'}
3381 //variable name of contentWriter
3382 var contentWriter
3383 //set headers
3384 res.setHeader('Content-Type', 'video/x-flv');
3385 res.setHeader('Access-Control-Allow-Origin','*');
3386 //write first frame on stream
3387 res.write(s.group[req.params.ke].mon[req.params.id].firstFLVchunk)
3388 //write new frames as they happen
3389 s.group[req.params.ke].mon[req.params.id].emitter.on('data',contentWriter=function(buffer){
3390 res.write(buffer)
3391 })
3392 //remove contentWriter when client leaves
3393 res.on('close', function () {
3394 s.group[req.params.ke].mon[req.params.id].emitter.removeListener('data',contentWriter)
3395 })
3396 }else{
3397 res.setHeader('Content-Type', 'application/json');
3398 res.end(s.s({ok:false,msg:'FLV not started or not ready'},null,3))
3399 }
3400 })
3401})
3402//Get MJPEG stream
3403app.get(['/:auth/mjpeg/:ke/:id','/:auth/mjpeg/:ke/:id/:addon'], function(req,res) {
3404 res.header("Access-Control-Allow-Origin",req.headers.origin);
3405 if(req.params.addon=='full'){
3406 res.render('mjpeg',{url:'/'+req.params.auth+'/mjpeg/'+req.params.ke+'/'+req.params.id});
3407 res.end()
3408 }else{
3409 s.auth(req.params,function(user){
3410 if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
3411 res.end(user.lang['Not Permitted'])
3412 return
3413 }
3414 res.writeHead(200, {
3415 'Content-Type': 'multipart/x-mixed-replace; boundary=shinobi',
3416 'Cache-Control': 'no-cache',
3417 'Connection': 'keep-alive',
3418 'Pragma': 'no-cache'
3419 });
3420 var contentWriter,content = fs.readFileSync(config.defaultMjpeg,'binary');
3421 res.write("--shinobi\r\n");
3422 res.write("Content-Type: image/jpeg\r\n");
3423 res.write("Content-Length: " + content.length + "\r\n");
3424 res.write("\r\n");
3425 res.write(content,'binary');
3426 res.write("\r\n");
3427 if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]&&s.group[req.params.ke].mon[req.params.id].emitter){
3428 s.group[req.params.ke].mon[req.params.id].emitter.on('data',contentWriter=function(d){
3429 content = d;
3430 res.write(content,'binary');
3431 })
3432 res.on('close', function () {
3433 s.group[req.params.ke].mon[req.params.id].emitter.removeListener('data',contentWriter)
3434 });
3435 }else{
3436 res.end();
3437 }
3438 },res,req);
3439 }
3440});
3441//embed monitor
3442app.get(['/:auth/embed/:ke/:id','/:auth/embed/:ke/:id/:addon'], function (req,res){
3443 res.header("Access-Control-Allow-Origin",req.headers.origin);
3444 req.params.protocol=req.protocol;
3445 s.auth(req.params,function(user){
3446 if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
3447 res.end(user.lang['Not Permitted'])
3448 return
3449 }
3450 if(s.group[req.params.ke]&&s.group[req.params.ke].mon[req.params.id]){
3451 if(s.group[req.params.ke].mon[req.params.id].started===1){
3452 res.render("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]))});
3453 res.end()
3454 }else{
3455 res.end(user.lang['Cannot watch a monitor that isn\'t running.'])
3456 }
3457 }else{
3458 res.end(user.lang['No Monitor Exists with this ID.'])
3459 }
3460 },res,req);
3461});
3462// Get monitors json
3463app.get(['/:auth/monitor/:ke','/:auth/monitor/:ke/:id'], function (req,res){
3464 req.ret={ok:false};
3465 res.setHeader('Content-Type', 'application/json');
3466 res.header("Access-Control-Allow-Origin",req.headers.origin);
3467 req.fn=function(user){
3468 if(user.permissions.get_monitors==="0"){
3469 res.end(s.s([]))
3470 return
3471 }
3472 req.sql='SELECT * FROM Monitors WHERE ke=?';req.ar=[req.params.ke];
3473 if(!req.params.id){
3474 if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
3475 try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
3476 req.or=[];
3477 user.details.monitors.forEach(function(v,n){
3478 req.or.push('mid=?');req.ar.push(v)
3479 })
3480 req.sql+=' AND ('+req.or.join(' OR ')+')'
3481 }
3482 }else{
3483 if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
3484 req.sql+=' and mid=?';req.ar.push(req.params.id)
3485 }else{
3486 res.end('[]');
3487 return;
3488 }
3489 }
3490 s.sqlQuery(req.sql,req.ar,function(err,r){
3491// r.forEach(function(v,n){
3492// r[n].subStream={}
3493// var details = JSON.parse(r[n].details)
3494// if(details.rawh264==='1'){
3495// r[n].subStream.h264 = '/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+'/1'
3496// }
3497// if(details.snap==='1'){
3498// r[n].subStream.jpeg = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
3499// }
3500// })
3501 if(r.length===1){r=r[0];}
3502 res.end(s.s(r, null, 3));
3503 })
3504 }
3505 s.auth(req.params,req.fn,res,req);
3506});
3507// Get videos json
3508app.get(['/:auth/videos/:ke','/:auth/videos/:ke/:id'], function (req,res){
3509 res.setHeader('Content-Type', 'application/json');
3510 res.header("Access-Control-Allow-Origin",req.headers.origin);
3511 s.auth(req.params,function(user){
3512 if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_view.indexOf(req.params.id)===-1){
3513 res.end(s.s([]))
3514 return
3515 }
3516 req.sql='SELECT * FROM Videos WHERE ke=?';req.ar=[req.params.ke];
3517 req.count_sql='SELECT COUNT(*) FROM Videos WHERE ke=?';req.count_ar=[req.params.ke];
3518 if(req.query.archived=='1'){
3519 req.sql+=' AND details LIKE \'%"archived":"1"\''
3520 req.count_sql+=' AND details LIKE \'%"archived":"1"\''
3521 }
3522 if(!req.params.id){
3523 if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
3524 try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
3525 req.or=[];
3526 user.details.monitors.forEach(function(v,n){
3527 req.or.push('mid=?');req.ar.push(v)
3528 })
3529 req.sql+=' AND ('+req.or.join(' OR ')+')'
3530 req.count_sql+=' AND ('+req.or.join(' OR ')+')'
3531 }
3532 }else{
3533 if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
3534 req.sql+=' and mid=?';req.ar.push(req.params.id)
3535 req.count_sql+=' and mid=?';req.count_ar.push(req.params.id)
3536 }else{
3537 res.end('[]');
3538 return;
3539 }
3540 }
3541 if(req.query.start||req.query.end){
3542 if(!req.query.startOperator||req.query.startOperator==''){
3543 req.query.startOperator='>='
3544 }
3545 if(!req.query.endOperator||req.query.endOperator==''){
3546 req.query.endOperator='<='
3547 }
3548 switch(true){
3549 case(req.query.start&&req.query.start!==''&&req.query.end&&req.query.end!==''):
3550 req.query.start=req.query.start.replace('T',' ')
3551 req.query.end=req.query.end.replace('T',' ')
3552 req.sql+=' AND `time` '+req.query.startOperator+' ? AND `end` '+req.query.endOperator+' ?';
3553 req.count_sql+=' AND `time` '+req.query.startOperator+' ? AND `end` '+req.query.endOperator+' ?';
3554 req.ar.push(req.query.start)
3555 req.ar.push(req.query.end)
3556 req.count_ar.push(req.query.start)
3557 req.count_ar.push(req.query.end)
3558 break;
3559 case(req.query.start&&req.query.start!==''):
3560 req.query.start=req.query.start.replace('T',' ')
3561 req.sql+=' AND `time` '+req.query.startOperator+' ?';
3562 req.count_sql+=' AND `time` '+req.query.startOperator+' ?';
3563 req.ar.push(req.query.start)
3564 req.count_ar.push(req.query.start)
3565 break;
3566 case(req.query.end&&req.query.end!==''):
3567 req.query.end=req.query.end.replace('T',' ')
3568 req.sql+=' AND `end` '+req.query.endOperator+' ?';
3569 req.count_sql+=' AND `end` '+req.query.endOperator+' ?';
3570 req.ar.push(req.query.end)
3571 req.count_ar.push(req.query.end)
3572 break;
3573 }
3574 }
3575 req.sql+=' ORDER BY `time` DESC';
3576 if(!req.query.limit||req.query.limit==''){
3577 req.query.limit='100'
3578 }
3579 if(req.query.limit!=='0'){
3580 req.sql+=' LIMIT '+req.query.limit
3581 }
3582 s.sqlQuery(req.sql,req.ar,function(err,r){
3583 if(!r){
3584 res.end(s.s({total:0,limit:req.query.limit,skip:0,videos:[]}, null, 3));
3585 return
3586 }
3587 s.sqlQuery(req.count_sql,req.count_ar,function(err,count){
3588 r.forEach(function(v){
3589 v.href='/'+req.params.auth+'/videos/'+v.ke+'/'+v.mid+'/'+s.moment(v.time)+'.'+v.ext;
3590 })
3591 if(req.query.limit.indexOf(',')>-1){
3592 req.skip=parseInt(req.query.limit.split(',')[0])
3593 req.query.limit=parseInt(req.query.limit.split(',')[0])
3594 }else{
3595 req.skip=0
3596 req.query.limit=parseInt(req.query.limit)
3597 }
3598 res.end(s.s({total:count[0]['COUNT(*)'],limit:req.query.limit,skip:req.skip,videos:r}, null, 3));
3599 })
3600 })
3601 },res,req);
3602});
3603// Get events json (motion logs)
3604app.get(['/:auth/events/:ke','/:auth/events/:ke/:id','/:auth/events/:ke/:id/:limit','/:auth/events/:ke/:id/:limit/:start','/:auth/events/:ke/:id/:limit/:start/:end'], function (req,res){
3605 req.ret={ok:false};
3606 res.setHeader('Content-Type', 'application/json');
3607 res.header("Access-Control-Allow-Origin",req.headers.origin);
3608 s.auth(req.params,function(user){
3609 if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_view.indexOf(req.params.id)===-1){
3610 res.end(s.s([]))
3611 return
3612 }
3613 req.sql='SELECT * FROM Events WHERE ke=?';req.ar=[req.params.ke];
3614 if(!req.params.id){
3615 if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
3616 try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
3617 req.or=[];
3618 user.details.monitors.forEach(function(v,n){
3619 req.or.push('mid=?');req.ar.push(v)
3620 })
3621 req.sql+=' AND ('+req.or.join(' OR ')+')'
3622 }
3623 }else{
3624 if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
3625 req.sql+=' and mid=?';req.ar.push(req.params.id)
3626 }else{
3627 res.end('[]');
3628 return;
3629 }
3630 }
3631 if(req.params.start&&req.params.start!==''){
3632 req.params.start=req.params.start.replace('T',' ')
3633 if(req.params.end&&req.params.end!==''){
3634 req.params.end=req.params.end.replace('T',' ')
3635 req.sql+=' AND `time` >= ? AND `time` <= ?';
3636 req.ar.push(decodeURIComponent(req.params.start))
3637 req.ar.push(decodeURIComponent(req.params.end))
3638 }else{
3639 req.sql+=' AND `time` >= ?';
3640 req.ar.push(decodeURIComponent(req.params.start))
3641 }
3642 }
3643 if(!req.params.limit||req.params.limit==''){req.params.limit=100}
3644 req.sql+=' ORDER BY `time` DESC LIMIT '+req.params.limit+'';
3645 s.sqlQuery(req.sql,req.ar,function(err,r){
3646 if(err){
3647 err.sql=req.sql;
3648 res.end(s.s(err, null, 3));
3649 return
3650 }
3651 if(!r){r=[]}
3652 r.forEach(function(v,n){
3653 r[n].details=JSON.parse(v.details);
3654 })
3655 res.end(s.s(r, null, 3));
3656 })
3657 },res,req);
3658});
3659// Get logs json
3660app.get(['/:auth/logs/:ke','/:auth/logs/:ke/:id','/:auth/logs/:ke/:limit','/:auth/logs/:ke/:id/:limit'], function (req,res){
3661 req.ret={ok:false};
3662 res.setHeader('Content-Type', 'application/json');
3663 res.header("Access-Control-Allow-Origin",req.headers.origin);
3664 s.auth(req.params,function(user){
3665 if(user.permissions.get_logs==="0"){
3666 res.end(s.s([]))
3667 return
3668 }
3669 req.sql='SELECT * FROM Logs WHERE ke=?';req.ar=[req.params.ke];
3670 if(!req.params.id){
3671 if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
3672 try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
3673 req.or=[];
3674 user.details.monitors.forEach(function(v,n){
3675 req.or.push('mid=?');req.ar.push(v)
3676 })
3677 req.sql+=' AND ('+req.or.join(' OR ')+')'
3678 }
3679 }else{
3680 if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1||req.params.id.indexOf('$')>-1){
3681 req.sql+=' and mid=?';req.ar.push(req.params.id)
3682 }else{
3683 res.end('[]');
3684 return;
3685 }
3686 }
3687 if(!req.params.limit||req.params.limit==''){req.params.limit=100}
3688 req.sql+=' ORDER BY `time` DESC LIMIT '+req.params.limit+'';
3689 s.sqlQuery(req.sql,req.ar,function(err,r){
3690 if(err){
3691 err.sql=req.sql;
3692 res.end(s.s(err, null, 3));
3693 return
3694 }
3695 if(!r){r=[]}
3696 r.forEach(function(v,n){
3697 r[n].info=JSON.parse(v.info)
3698 })
3699 res.end(s.s(r, null, 3));
3700 })
3701 },res,req);
3702});
3703// Get monitors online json
3704app.get('/:auth/smonitor/:ke', function (req,res){
3705 req.ret={ok:false};
3706 res.setHeader('Content-Type', 'application/json');
3707 res.header("Access-Control-Allow-Origin",req.headers.origin);
3708 req.fn=function(user){
3709 if(user.permissions.get_monitors==="0"){
3710 res.end(s.s([]))
3711 return
3712 }
3713 req.sql='SELECT * FROM Monitors WHERE ke=?';req.ar=[req.params.ke];
3714 if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
3715 try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
3716 req.or=[];
3717 user.details.monitors.forEach(function(v,n){
3718 req.or.push('mid=?');req.ar.push(v)
3719 })
3720 req.sql+=' AND ('+req.or.join(' OR ')+')'
3721 }
3722 s.sqlQuery(req.sql,req.ar,function(err,r){
3723 if(r&&r[0]){
3724 req.ar=[];
3725 r.forEach(function(v){
3726 if(s.group[req.params.ke]&&s.group[req.params.ke].mon[v.mid]&&s.group[req.params.ke].mon[v.mid].started===1){
3727 req.ar.push(v)
3728 }
3729 })
3730 }else{
3731 req.ar=[];
3732 }
3733 res.end(s.s(req.ar, null, 3));
3734 })
3735 }
3736 s.auth(req.params,req.fn,res,req);
3737});
3738// Monitor Add,Edit,Delete
3739app.all(['/:auth/configureMonitor/:ke/:id','/:auth/configureMonitor/:ke/:id/:f'], function (req,res){
3740 req.ret={ok:false};
3741 res.setHeader('Content-Type', 'application/json');
3742 res.header("Access-Control-Allow-Origin",req.headers.origin);
3743 s.auth(req.params,function(user){
3744 if(req.params.f!=='delete'){
3745 if(!req.body.data&&!req.query.data){
3746 req.ret.msg='No Monitor Data found.'
3747 res.end(s.s(req.ret, null, 3))
3748 return
3749 }
3750 try{
3751 if(req.query.data){
3752 req.monitor=JSON.parse(req.query.data)
3753 }else{
3754 req.monitor=JSON.parse(req.body.data)
3755 }
3756 }catch(er){
3757 if(!req.monitor){
3758 req.ret.msg=user.lang.monitorEditText1;
3759 res.end(s.s(req.ret, null, 3))
3760 }
3761 return
3762 }
3763 if(!user.details.sub||user.details.allmonitors==='1'||user.details.monitor_edit.indexOf(req.monitor.mid)>-1){
3764 if(req.monitor&&req.monitor.mid&&req.monitor.name){
3765 req.set=[],req.ar=[];
3766 req.monitor.mid=req.monitor.mid.replace(/[^\w\s]/gi,'').replace(/ /g,'');
3767 try{
3768 JSON.parse(req.monitor.details)
3769 }catch(er){
3770 if(!req.monitor.details||!req.monitor.details.stream_type){
3771 req.ret.msg=user.lang.monitorEditText2;
3772 res.end(s.s(req.ret, null, 3))
3773 return
3774 }else{
3775 req.monitor.details=JSON.stringify(req.monitor.details)
3776 }
3777 }
3778 req.monitor.ke=req.params.ke
3779 req.logObject={details:JSON.parse(req.monitor.details),ke:req.params.ke,mid:req.params.id}
3780 s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[req.monitor.ke,req.monitor.mid],function(er,r){
3781 req.tx={f:'monitor_edit',mid:req.monitor.mid,ke:req.monitor.ke,mon:req.monitor};
3782 if(r&&r[0]){
3783 req.tx.new=false;
3784 Object.keys(req.monitor).forEach(function(v){
3785 if(req.monitor[v]&&req.monitor[v]!==''){
3786 req.set.push(v+'=?'),req.ar.push(req.monitor[v]);
3787 }
3788 })
3789 req.set=req.set.join(',');
3790 req.ar.push(req.monitor.ke),req.ar.push(req.monitor.mid);
3791 s.log(req.monitor,{type:'Monitor Updated',msg:'by user : '+user.uid});
3792 req.ret.msg=user.lang['Monitor Updated by user']+' : '+user.uid;
3793 s.sqlQuery('UPDATE Monitors SET '+req.set+' WHERE ke=? AND mid=?',req.ar)
3794 req.finish=1;
3795 }else{
3796 if(!s.group[req.monitor.ke].init.max_camera||s.group[req.monitor.ke].init.max_camera==''||Object.keys(s.group[req.monitor.ke].mon).length <= parseInt(s.group[req.monitor.ke].init.max_camera)){
3797 req.tx.new=true;
3798 req.st=[];
3799 Object.keys(req.monitor).forEach(function(v){
3800 if(req.monitor[v]&&req.monitor[v]!==''){
3801 req.set.push(v),req.st.push('?'),req.ar.push(req.monitor[v]);
3802 }
3803 })
3804 // req.set.push('ke'),req.st.push('?'),req.ar.push(req.monitor.ke);
3805 req.set=req.set.join(','),req.st=req.st.join(',');
3806 s.log(req.monitor,{type:'Monitor Added',msg:'by user : '+user.uid});
3807 req.ret.msg=user.lang['Monitor Added by user']+' : '+user.uid;
3808 s.sqlQuery('INSERT INTO Monitors ('+req.set+') VALUES ('+req.st+')',req.ar)
3809 req.finish=1;
3810 }else{
3811 req.tx.f='monitor_edit_failed';
3812 req.tx.ff='max_reached';
3813 req.ret.msg=user.lang.monitorEditFailedMaxReached;
3814 }
3815 }
3816 if(req.finish===1){
3817 req.monitor.details=JSON.parse(req.monitor.details)
3818 req.ret.ok=true;
3819 s.init(0,{mid:req.monitor.mid,ke:req.monitor.ke});
3820 s.group[req.monitor.ke].mon_conf[req.monitor.mid]=s.init('noReference',req.monitor);
3821 if(req.monitor.mode==='stop'){
3822 s.camera('stop',req.monitor);
3823 }else{
3824 s.camera('stop',req.monitor);setTimeout(function(){s.camera(req.monitor.mode,req.monitor);},5000)
3825 };
3826 s.tx(req.tx,'STR_'+req.monitor.ke);
3827 };
3828 s.tx(req.tx,'GRP_'+req.monitor.ke);
3829 res.end(s.s(req.ret, null, 3))
3830 })
3831 }else{
3832 req.ret.msg=user.lang.monitorEditText1;
3833 res.end(s.s(req.ret, null, 3))
3834 }
3835 }else{
3836 req.ret.msg=user.lang['Not Permitted'];
3837 res.end(s.s(req.ret, null, 3))
3838 }
3839 }else{
3840 if(!user.details.sub||user.details.allmonitors==='1'||user.details.monitor_edit.indexOf(req.params.id)>-1){
3841 s.log(s.group[req.params.ke].mon_conf[req.params.id],{type:'Monitor Deleted',msg:'by user : '+user.uid});
3842 req.params.delete=1;s.camera('stop',req.params);
3843 s.tx({f:'monitor_delete',uid:user.uid,mid:req.params.id,ke:req.params.ke},'GRP_'+req.params.ke);
3844 s.sqlQuery('DELETE FROM Monitors WHERE ke=? AND mid=?',[req.params.ke,req.params.id])
3845 req.ret.ok=true;
3846 req.ret.msg='Monitor Deleted by user : '+user.uid
3847 res.end(s.s(req.ret, null, 3))
3848 }
3849 }
3850 })
3851})
3852app.get(['/:auth/monitor/:ke/:id/:f','/:auth/monitor/:ke/:id/:f/:ff','/:auth/monitor/:ke/:id/:f/:ff/:fff'], function (req,res){
3853 req.ret={ok:false};
3854 res.setHeader('Content-Type', 'application/json');
3855 res.header("Access-Control-Allow-Origin",req.headers.origin);
3856 s.auth(req.params,function(user){
3857 if(user.permissions.control_monitors==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitor_edit.indexOf(req.params.id)===-1){
3858 res.end(user.lang['Not Permitted'])
3859 return
3860 }
3861 if(req.params.f===''){req.ret.msg=user.lang.monitorGetText1;res.end(s.s(req.ret, null, 3));return}
3862 if(req.params.f!=='stop'&&req.params.f!=='start'&&req.params.f!=='record'){
3863 req.ret.msg='Mode not recognized.';
3864 res.end(s.s(req.ret, null, 3));
3865 return;
3866 }
3867 s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[req.params.ke,req.params.id],function(err,r){
3868 if(r&&r[0]){
3869 r=r[0];
3870 if(req.query.reset==='1'||(s.group[r.ke]&&s.group[r.ke].mon_conf[r.mid].mode!==req.params.f)||req.query.fps&&(!s.group[r.ke].mon[r.mid].currentState||!s.group[r.ke].mon[r.mid].currentState.trigger_on)){
3871 if(req.query.reset!=='1'||!s.group[r.ke].mon[r.mid].trigger_timer){
3872 if(!s.group[r.ke].mon[r.mid].currentState)s.group[r.ke].mon[r.mid].currentState={}
3873 s.group[r.ke].mon[r.mid].currentState.mode=r.mode.toString()
3874 s.group[r.ke].mon[r.mid].currentState.fps=r.fps.toString()
3875 if(!s.group[r.ke].mon[r.mid].currentState.trigger_on){
3876 s.group[r.ke].mon[r.mid].currentState.trigger_on=true
3877 }else{
3878 s.group[r.ke].mon[r.mid].currentState.trigger_on=false
3879 }
3880 r.mode=req.params.f;
3881 try{r.details=JSON.parse(r.details);}catch(er){}
3882 if(req.query.fps){
3883 r.fps=parseFloat(r.details.detector_trigger_record_fps)
3884 s.group[r.ke].mon[r.mid].currentState.detector_trigger_record_fps=r.fps
3885 }
3886 r.id=r.mid;
3887 s.sqlQuery('UPDATE Monitors SET mode=? WHERE ke=? AND mid=?',[r.mode,r.ke,r.mid]);
3888 s.group[r.ke].mon_conf[r.mid]=r;
3889 s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke);
3890 s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke);
3891 s.camera('stop',s.init('noReference',r));
3892 if(req.params.f!=='stop'){
3893 s.camera(req.params.f,s.init('noReference',r));
3894 }
3895 req.ret.msg=user.lang['Monitor mode changed']+' : '+req.params.f;
3896 }else{
3897 req.ret.msg=user.lang['Reset Timer'];
3898 }
3899 req.ret.cmd_at=s.moment(new Date,'YYYY-MM-DD HH:mm:ss');
3900 req.ret.ok=true;
3901 if(req.params.ff&&req.params.f!=='stop'){
3902 req.params.ff=parseFloat(req.params.ff);
3903 clearTimeout(s.group[r.ke].mon[r.mid].trigger_timer)
3904 switch(req.params.fff){
3905 case'day':case'days':
3906 req.timeout=req.params.ff*1000*60*60*24
3907 break;
3908 case'hr':case'hour':case'hours':
3909 req.timeout=req.params.ff*1000*60*60
3910 break;
3911 case'min':case'minute':case'minutes':
3912 req.timeout=req.params.ff*1000*60
3913 break;
3914 default://seconds
3915 req.timeout=req.params.ff*1000
3916 break;
3917 }
3918 s.group[r.ke].mon[r.mid].trigger_timer=setTimeout(function(){
3919 delete(s.group[r.ke].mon[r.mid].trigger_timer)
3920 s.sqlQuery('UPDATE Monitors SET mode=? WHERE ke=? AND mid=?',[s.group[r.ke].mon[r.mid].currentState.mode,r.ke,r.mid]);
3921 r.neglectTriggerTimer=1;
3922 r.mode=s.group[r.ke].mon[r.mid].currentState.mode;
3923 r.fps=s.group[r.ke].mon[r.mid].currentState.fps;
3924 s.camera('stop',s.init('noReference',r),function(){
3925 if(s.group[r.ke].mon[r.mid].currentState.mode!=='stop'){
3926 s.camera(s.group[r.ke].mon[r.mid].currentState.mode,s.init('noReference',r));
3927 }
3928 s.group[r.ke].mon_conf[r.mid]=r;
3929 });
3930 s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke);
3931 s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke);
3932 },req.timeout);
3933// req.ret.end_at=s.moment(new Date,'YYYY-MM-DD HH:mm:ss').add(req.timeout,'milliseconds');
3934 }
3935 }else{
3936 req.ret.msg=user.lang['Monitor mode is already']+' : '+req.params.f;
3937 }
3938 }else{
3939 req.ret.msg=user.lang['Monitor or Key does not exist.'];
3940 }
3941 res.end(s.s(req.ret, null, 3));
3942 })
3943 },res,req);
3944})
3945//get file from fileBin bin
3946app.get(['/:auth/fileBin/:ke','/:auth/fileBin/:ke/:id'],function (req,res){
3947 res.setHeader('Content-Type', 'application/json');
3948 res.header("Access-Control-Allow-Origin",req.headers.origin);
3949 req.fn=function(user){
3950 req.sql='SELECT * FROM Files WHERE ke=?';req.ar=[req.params.ke];
3951 if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
3952 try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
3953 req.or=[];
3954 user.details.monitors.forEach(function(v,n){
3955 req.or.push('mid=?');req.ar.push(v)
3956 })
3957 req.sql+=' AND ('+req.or.join(' OR ')+')'
3958 }else{
3959 if(req.params.id&&(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1)){
3960 req.sql+=' and mid=?';req.ar.push(req.params.id)
3961 }
3962 }
3963 s.sqlQuery(req.sql,req.ar,function(err,r){
3964 if(!r){
3965 r=[]
3966 }else{
3967 r.forEach(function(v){
3968 v.details=JSON.parse(v.details)
3969 v.href='/'+req.params.auth+'/fileBin/'+req.params.ke+'/'+req.params.id+'/'+v.details.year+'/'+v.details.month+'/'+v.details.day+'/'+v.name;
3970 })
3971 }
3972 res.end(s.s(r, null, 3));
3973 })
3974 }
3975 s.auth(req.params,req.fn,res,req);
3976});
3977//get file from fileBin bin
3978app.get('/:auth/fileBin/:ke/:id/:year/:month/:day/:file', function (req,res){
3979 res.header("Access-Control-Allow-Origin",req.headers.origin);
3980 req.fn=function(user){
3981 req.failed=function(){
3982 res.end(user.lang['File Not Found'])
3983 }
3984 if (!s.group[req.params.ke].fileBin[req.params.id+'/'+req.params.file]){
3985 s.sqlQuery('SELECT * FROM Files WHERE ke=? AND mid=? AND name=?',[req.params.ke,req.params.id,req.params.file],function(err,r){
3986 if(r&&r[0]){
3987 r=r[0]
3988 r.details=JSON.parse(r.details)
3989 req.dir=s.dir.fileBin+req.params.ke+'/'+req.params.id+'/'+r.details.year+'/'+r.details.month+'/'+r.details.day+'/'+req.params.file;
3990 if(fs.existsSync(req.dir)){
3991 res.on('finish',function(){res.end();});
3992 fs.createReadStream(req.dir).pipe(res);
3993 }else{
3994 req.failed()
3995 }
3996 }else{
3997 req.failed()
3998 }
3999 })
4000 }else{
4001 res.end(user.lang['Please Wait for Completion'])
4002 }
4003 }
4004 s.auth(req.params,req.fn,res,req);
4005});
4006// Get video file
4007app.get('/:auth/videos/:ke/:id/:file', function (req,res){
4008 s.auth(req.params,function(user){
4009 if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
4010 res.end(user.lang['Not Permitted'])
4011 return
4012 }
4013 s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND time=?',[req.params.ke,req.params.id,s.nameToTime(req.params.file)],function(err,r){
4014 if(r&&r[0]){
4015 req.dir=s.video('getDir',r[0])+req.params.file
4016 if (fs.existsSync(req.dir)){
4017 req.ext=req.params.file.split('.')[1];
4018 var total = fs.statSync(req.dir).size;
4019 if (req.headers['range']) {
4020 var range = req.headers.range;
4021 var parts = range.replace(/bytes=/, "").split("-");
4022 var partialstart = parts[0];
4023 var partialend = parts[1];
4024
4025 var start = parseInt(partialstart, 10);
4026 var end = partialend ? parseInt(partialend, 10) : total-1;
4027 var chunksize = (end-start)+1;
4028 var file = fs.createReadStream(req.dir, {start: start, end: end});
4029 req.headerWrite={ 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/'+req.ext }
4030 req.writeCode=206
4031 } else {
4032 req.headerWrite={ 'Content-Length': total, 'Content-Type': 'video/'+req.ext};
4033 var file=fs.createReadStream(req.dir)
4034 req.writeCode=200
4035 }
4036 if(req.query.downloadName){
4037 req.headerWrite['content-disposition']='attachment; filename="'+req.query.downloadName+'"';
4038 }
4039 res.writeHead(req.writeCode,req.headerWrite);
4040 file.on('close',function(){
4041 res.end();
4042 })
4043 file.pipe(res);
4044 }else{
4045 res.end(user.lang['File Not Found'])
4046 }
4047 }else{
4048 res.end(user.lang['File Not Found'])
4049 }
4050 })
4051 },res,req);
4052});
4053//motion trigger
4054app.get('/:auth/motion/:ke/:id', function (req,res){
4055 s.auth(req.params,function(user){
4056 if(req.query.data){
4057 try{
4058 var d={id:req.params.id,ke:req.params.ke,details:JSON.parse(req.query.data)};
4059 }catch(err){
4060 res.end('Data Broken',err);
4061 return;
4062 }
4063 }else{
4064 res.end('No Data');
4065 return;
4066 }
4067 if(!d.ke||!d.id||!s.group[d.ke]){
4068 res.end(user.lang['No Group with this key exists']);
4069 return;
4070 }
4071 s.camera('motion',d,function(){
4072 res.end(user.lang['Trigger Successful'])
4073 });
4074},res,req);
4075})
4076//modify video file
4077app.get(['/:auth/videos/:ke/:id/:file/:mode','/:auth/videos/:ke/:id/:file/:mode/:f'], function (req,res){
4078 req.ret={ok:false};
4079 res.setHeader('Content-Type', 'application/json');
4080 res.header("Access-Control-Allow-Origin",req.headers.origin);
4081 s.auth(req.params,function(user){
4082 if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_delete.indexOf(req.params.id)===-1){
4083 res.end(user.lang['Not Permitted'])
4084 return
4085 }
4086 req.sql='SELECT * FROM Videos WHERE ke=? AND mid=? AND time=?';
4087 req.ar=[req.params.ke,req.params.id,s.nameToTime(req.params.file)];
4088 s.sqlQuery(req.sql,req.ar,function(err,r){
4089 if(r&&r[0]){
4090 r=r[0];r.filename=s.moment(r.time)+'.'+r.ext;
4091 switch(req.params.mode){
4092 case'fix':
4093 req.ret.ok=true;
4094 s.video('fix',r)
4095 break;
4096 case'status':
4097 req.params.f=parseInt(req.params.f)
4098 if(isNaN(req.params.f)||req.params.f===0){
4099 req.ret.msg='Not a valid value.';
4100 }else{
4101 req.ret.ok=true;
4102 s.sqlQuery('UPDATE Videos SET status=? WHERE ke=? AND mid=? AND time=?',[req.params.f,req.params.ke,req.params.id,s.nameToTime(req.params.file)])
4103 s.tx({f:'video_edit',status:req.params.f,filename:r.filename,mid:r.mid,ke:r.ke,time:s.nameToTime(r.filename),end:s.moment(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+r.ke);
4104 }
4105 break;
4106 case'delete':
4107 req.ret.ok=true;
4108 s.video('delete',r)
4109 break;
4110 default:
4111 req.ret.msg=user.lang.modifyVideoText1;
4112 break;
4113 }
4114 }else{
4115 req.ret.msg=user.lang['No such file'];
4116 }
4117 res.end(s.s(req.ret, null, 3));
4118 })
4119 },res,req);
4120})
4121//ffmpeg pushed stream in here to make a pipe
4122app.all(['/streamIn/:ke/:id','/streamIn/:ke/:id/:feed'], function (req, res) {
4123 var checkOrigin = function(search){return req.headers.host.indexOf(search)>-1}
4124 if(checkOrigin('127.0.0.1')){
4125 if(!req.params.feed){req.params.feed='1'}
4126 if(!s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed]){
4127 s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed] = new events.EventEmitter().setMaxListeners(0)
4128 }
4129 //req.params.feed = Feed Number
4130 res.connection.setTimeout(0);
4131 req.on('data', function(buffer){
4132 s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed].emit('data',buffer)
4133 });
4134 req.on('end',function(){
4135// console.log('streamIn closed',req.params);
4136 });
4137 }else{
4138 res.end('Local connection is only allowed.')
4139 }
4140})
4141//simulate RTSP over HTTP
4142app.get(['/:auth/h264/:ke/:id/:feed','/:auth/h264/:ke/:id'], function (req, res) {
4143 res.header("Access-Control-Allow-Origin",req.headers.origin);
4144 s.auth(req.params,function(user){
4145 if(!req.params.feed){req.params.feed='1'}
4146 if(!s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed]){
4147 s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed] = new events.EventEmitter().setMaxListeners(0)
4148 }
4149 var contentWriter
4150 var date = new Date();
4151 res.writeHead(200, {
4152 'Date': date.toUTCString(),
4153 'Connection': 'keep-alive',
4154 'Cache-Control': 'no-cache',
4155 'Pragma': 'no-cache',
4156 'Content-Type': 'video/mp4',
4157 'Server': 'Shinobi H.264 Test Stream',
4158 });
4159 s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed].on('data',contentWriter=function(buffer){
4160 res.write(buffer)
4161 })
4162 res.on('close', function () {
4163 s.group[req.params.ke].mon[req.params.id].streamIn[req.params.feed].removeListener('data',contentWriter)
4164 })
4165 })
4166});
4167try{
4168s.cpuUsage=function(e){
4169 k={}
4170 switch(s.platform){
4171 case'win32':
4172 k.cmd="@for /f \"skip=1\" %p in ('wmic cpu get loadpercentage') do @echo %p%"
4173 break;
4174 case'darwin':
4175 k.cmd="ps -A -o %cpu | awk '{s+=$1} END {print s}'";
4176 break;
4177 case'linux':
4178 k.cmd='LANG=C top -b -n 2 | grep "^'+config.cpuUsageMarker+'" | awk \'{print $2}\' | tail -n1';
4179 break;
4180 }
4181 if(k.cmd){
4182 exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
4183 if(s.isWin===true){
4184 d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"")
4185 }
4186 e(d)
4187 });
4188 }else{
4189 e(0)
4190 }
4191}
4192s.ramUsage=function(e){
4193 k={}
4194 switch(s.platform){
4195 case'win32':
4196 k.cmd = "wmic OS get FreePhysicalMemory /Value"
4197 break;
4198 case'darwin':
4199 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;}'"
4200 break;
4201 default:
4202 k.cmd = "LANG=C free | grep Mem | awk '{print $4/$2 * 100.0}'";
4203 break;
4204 }
4205 if(k.cmd){
4206 exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
4207 if(s.isWin===true){
4208 d=(parseInt(d.split('=')[1])/(s.totalmem/1000))*100
4209 }
4210 e(d)
4211 });
4212 }else{
4213 e(0)
4214 }
4215}
4216 setInterval(function(){
4217 s.cpuUsage(function(cpu){
4218 s.ramUsage(function(ram){
4219 s.tx({f:'os',cpu:cpu,ram:ram},'CPU');
4220 })
4221 })
4222 },10000);
4223}catch(err){s.systemLog(lang['CPU indicator will not work. Continuing...'])}
4224//check disk space every 20 minutes
4225if(config.autoDropCache===true){
4226 setInterval(function(){
4227 exec('echo 3 > /proc/sys/vm/drop_caches',{detached: true})
4228 },60000*20);
4229}
4230s.beat=function(){
4231 setTimeout(s.beat, 8000);
4232 io.sockets.emit('ping',{beat:1});
4233}
4234s.beat();
4235setTimeout(function(){
4236 //get current disk used for each isolated account (admin user) on startup
4237 s.sqlQuery('SELECT * FROM Users WHERE details NOT LIKE ?',['%"sub"%'],function(err,r){
4238 if(r&&r[0]){
4239 var count = r.length
4240 var countFinished = 0
4241 r.forEach(function(v,n){
4242 v.size=0;
4243 v.limit=JSON.parse(v.details).size
4244 s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND status!=?',[v.ke,0],function(err,rr){
4245 ++countFinished
4246 if(r&&r[0]){
4247 rr.forEach(function(b){
4248 v.size+=b.size
4249 })
4250 }
4251 s.systemLog(v.mail+' : '+lang.startUpText0+' : '+rr.length,v.size)
4252 if(!s.group[v.ke]){
4253 s.group[v.ke]={}
4254 }
4255 if(!s.group[v.ke].init){
4256 s.group[v.ke].init={}
4257 }
4258 if(!v.limit||v.limit===''){v.limit=10000}else{v.limit=parseFloat(v.limit)}
4259 //save global space limit for group key (mb)
4260 s.group[v.ke].sizeLimit=v.limit;
4261 //save global used space as megabyte value
4262 s.group[v.ke].usedSpace=v.size/1000000;
4263 //emit the changes to connected users
4264 s.init('diskUsedEmit',v)
4265 s.systemLog(v.mail+' : '+lang.startUpText1,countFinished+'/'+count)
4266 if(countFinished===count){
4267 s.systemLog(lang.startUpText2)
4268 ////close open videos
4269 s.sqlQuery('SELECT * FROM Videos WHERE status=?',[0],function(err,r){
4270 if(r&&r[0]){
4271 r.forEach(function(v){
4272 s.init(0,v)
4273 v.filename=s.moment(v.time);
4274 s.video('close',v);
4275 })
4276 }
4277 s.systemLog(lang.startUpText3)
4278 setTimeout(function(){
4279 s.systemLog(lang.startUpText4)
4280 //preliminary monitor start
4281 s.sqlQuery('SELECT * FROM Monitors', function(err,r) {
4282 if(err){s.systemLog(err)}
4283 if(r&&r[0]){
4284 r.forEach(function(v){
4285 s.init(0,v);
4286 r.ar={};
4287 r.ar.id=v.mid;
4288 Object.keys(v).forEach(function(b){
4289 r.ar[b]=v[b];
4290 })
4291 if(!s.group[v.ke]){
4292 s.group[v.ke]={}
4293 s.group[v.ke].mon_conf={}
4294 }
4295 v.details=JSON.parse(v.details);
4296 s.group[v.ke].mon_conf[v.mid]=v;
4297 s.camera(v.mode,r.ar);
4298 });
4299 }
4300 s.systemLog(lang.startUpText5)
4301 process.send('ready')
4302 });
4303 },3000)
4304 })
4305 }
4306 })
4307 })
4308 }
4309 })
4310},1500)