Giatso : Critical Fixes and Matrix Support

fix-multi-trigger
Moe 2023-02-18 04:56:41 +00:00
parent e7f2b443c1
commit aa20e613a4
14 changed files with 544 additions and 82 deletions

View File

@ -25,7 +25,7 @@
"auth": { "auth": {
"user": "your_email@gmail.com", "user": "your_email@gmail.com",
"pass": "your_password_or_app_specific_password" "pass": "your_password_or_app_specific_password"
} }
}, },
"cron":{}, "cron":{},
"pluginKeys":{} "pluginKeys":{}

View File

@ -12,6 +12,8 @@
"accountAdded": "Account Added", "accountAdded": "Account Added",
"accountAddedText": "Account has been added.", "accountAddedText": "Account has been added.",
"monitorDeleted": "Monitor Deleted", "monitorDeleted": "Monitor Deleted",
"accountCreationError": "Account Creation Error",
"accountEditError": "Account Edit Error",
"Unmute": "Unmute", "Unmute": "Unmute",
"byUser": "by user", "byUser": "by user",
"accountDeleted": "Account Deleted", "accountDeleted": "Account Deleted",
@ -34,7 +36,6 @@
"Active Monitors": "Active Monitors", "Active Monitors": "Active Monitors",
"Storage Use": "Storage Use", "Storage Use": "Storage Use",
"Use Raw Snapshot": "Use Raw Snapshot", "Use Raw Snapshot": "Use Raw Snapshot",
"Account Edited": "Account Edited",
"Failed to Edit Account": "Failed to Edit Account", "Failed to Edit Account": "Failed to Edit Account",
"How to Connect": "How to Connect", "How to Connect": "How to Connect",
"Login": "Login", "Login": "Login",
@ -559,7 +560,14 @@
"InvalidJSONText": "Please ensure this is a valid JSON string for Shinobi monitor configuration.", "InvalidJSONText": "Please ensure this is a valid JSON string for Shinobi monitor configuration.",
"Passwords don't match": "Passwords don't match", "Passwords don't match": "Passwords don't match",
"Email address is in use.": "Email address is in use.", "Email address is in use.": "Email address is in use.",
"Account Created": "Account Created",
"Account Edited": "Account Edited",
"Compression Info": "Compression Info",
"Compression Error": "Compression Error",
"Group Key is in use.": "Group Key is in use.", "Group Key is in use.": "Group Key is in use.",
"adminAccountCreatedMsg": "Your account has been created. Access the account at the primary login page (Non-Superuser).",
"adminAccountEditedMsg": "Your account has been edited. You may need to restart Shinobi for some changes to take effect.",
"createSubAccountsInfo": "If you are looking to share cameras with another account you may create them in the Sub-Account manager of your non-superuser Dashboard.",
"Create Sub-Accounts at /admin": "Create Sub-Accounts at /admin", "Create Sub-Accounts at /admin": "Create Sub-Accounts at /admin",
"No Events found for this video": "No Events found for this video", "No Events found for this video": "No Events found for this video",
"Video and Time Span (Minutes)": "Video and Time Span (Minutes)", "Video and Time Span (Minutes)": "Video and Time Span (Minutes)",
@ -1057,6 +1065,7 @@
"Monitor mode is already": "Monitor mode is already", "Monitor mode is already": "Monitor mode is already",
"Monitor or Key does not exist.": "Monitor or Key does not exist.", "Monitor or Key does not exist.": "Monitor or Key does not exist.",
"No Group with this key exists": "No Group with this key exists", "No Group with this key exists": "No Group with this key exists",
"Group with this key exists already": "Group with this key exists already",
"Downloaded!": "Downloaded!", "Downloaded!": "Downloaded!",
"Downloading...": "Downloading...", "Downloading...": "Downloading...",
"Loading...": "Loading...", "Loading...": "Loading...",

View File

@ -360,10 +360,19 @@ module.exports = function(s,config,lang){
}) })
},7000) },7000)
} }
const getLargestMatrix = (matrices) => { const getLargestMatrix = (matrices,imgWidth,imgHeight) => {
var largestMatrix = {width: 0, height: 0} var largestMatrix = {width: 0, height: 0}
const maxWidth = imgWidth * 0.95;
const maxHeight = imgHeight * 0.95;
matrices.forEach((matrix) => { matrices.forEach((matrix) => {
if(matrix.width > largestMatrix.width && matrix.height > largestMatrix.height)largestMatrix = matrix const matrixWidth = matrix.width
const matrixHeight = matrix.height
if(
matrixWidth > largestMatrix.width &&
matrixHeight > largestMatrix.height &&
matrixWidth <= maxWidth &&
matrixHeight <= maxHeight
)largestMatrix = matrix;
}) })
return largestMatrix.x ? largestMatrix : null return largestMatrix.x ? largestMatrix : null
} }
@ -377,7 +386,7 @@ module.exports = function(s,config,lang){
const imageCenterX = imgWidth / 2 const imageCenterX = imgWidth / 2
const imageCenterY = imgHeight / 2 const imageCenterY = imgHeight / 2
const matrices = event.details.matrices || [] const matrices = event.details.matrices || []
const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1)) const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1),imgWidth,imgHeight)
if(!largestMatrix)return; if(!largestMatrix)return;
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id] const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1' const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
@ -414,6 +423,7 @@ module.exports = function(s,config,lang){
} }
} }
function setHomePositionPreset(e){ function setHomePositionPreset(e){
const monitorId = e.mid || e.id
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
setPresetForCurrentPosition({ setPresetForCurrentPosition({

View File

@ -7,7 +7,9 @@ module.exports = (s,config,lang) => {
if(config.doCronAsWorker===undefined)config.doCronAsWorker = true; if(config.doCronAsWorker===undefined)config.doCronAsWorker = true;
const startWorker = () => { const startWorker = () => {
const pathToWorkerScript = __dirname + `/cron/worker.js` const pathToWorkerScript = __dirname + `/cron/worker.js`
const workerProcess = new Worker(pathToWorkerScript) const workerProcess = new Worker(pathToWorkerScript,{
workerData: config
})
workerProcess.on('message',function(data){ workerProcess.on('message',function(data){
if(data.time === 'moment()')data.time = moment(); if(data.time === 'moment()')data.time = moment();
switch(data.f){ switch(data.f){

View File

@ -3,8 +3,8 @@ const path = require('path');
const moment = require('moment'); const moment = require('moment');
const exec = require('child_process').exec; const exec = require('child_process').exec;
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const { parentPort, isMainThread } = require('worker_threads'); const { parentPort, isMainThread, workerData } = require('worker_threads');
const config = require(process.cwd() + '/conf.json') const config = workerData
process.on('uncaughtException', function (err) { process.on('uncaughtException', function (err) {
errorLog('uncaughtException',err); errorLog('uncaughtException',err);
}); });

View File

@ -590,9 +590,6 @@ module.exports = (s,config,lang,app,io) => {
if(autoCompressionEnabled){ if(autoCompressionEnabled){
reEncodeVideoAndBinOriginalAddToQueue({ reEncodeVideoAndBinOriginalAddToQueue({
video: response.insertQuery, video: response.insertQuery,
targetVideoCodec: 'vp9',
targetAudioCodec: 'libopus',
targetQuality: '-q:v 1 -q:a 1',
targetExtension: 'webm', targetExtension: 'webm',
doSlowly: false, doSlowly: false,
automated: true, automated: true,

View File

@ -73,6 +73,7 @@ module.exports = (s,config,lang) => {
doResolve(response) doResolve(response)
} }
try{ try{
proc.removeAllListeners()
proc.on('exit',() => { proc.on('exit',() => {
response.msg = 'proc.on.exit' response.msg = 'proc.on.exit'
clearTimeout(killTimer) clearTimeout(killTimer)
@ -148,6 +149,7 @@ module.exports = (s,config,lang) => {
clearInterval(activeMonitor.getMonitorCpuUsage); clearInterval(activeMonitor.getMonitorCpuUsage);
clearInterval(activeMonitor.objectCountIntervals); clearInterval(activeMonitor.objectCountIntervals);
clearTimeout(activeMonitor.timeoutToRestart) clearTimeout(activeMonitor.timeoutToRestart)
clearTimeout(activeMonitor.fatalErrorTimeout);
delete(activeMonitor.onvifConnection) delete(activeMonitor.onvifConnection)
// if(activeMonitor.onChildNodeExit){ // if(activeMonitor.onChildNodeExit){
// activeMonitor.onChildNodeExit() // activeMonitor.onChildNodeExit()
@ -709,7 +711,7 @@ module.exports = (s,config,lang) => {
clearTimeout(activeMonitor.trigger_timer) clearTimeout(activeMonitor.trigger_timer)
delete(activeMonitor.trigger_timer) delete(activeMonitor.trigger_timer)
clearInterval(activeMonitor.detector_notrigger_timeout) clearInterval(activeMonitor.detector_notrigger_timeout)
clearTimeout(activeMonitor.err_fatal_timeout); clearTimeout(activeMonitor.fatalErrorTimeout);
activeMonitor.isStarted = false activeMonitor.isStarted = false
activeMonitor.isRecording = false activeMonitor.isRecording = false
s.tx({f:'monitor_stopping',mid:monitorId,ke:groupKey,time:s.formattedTime()},'GRP_'+groupKey); s.tx({f:'monitor_stopping',mid:monitorId,ke:groupKey,time:s.formattedTime()},'GRP_'+groupKey);
@ -1119,7 +1121,7 @@ module.exports = (s,config,lang) => {
} }
if(e.details.record_timelapse === '1'){ if(e.details.record_timelapse === '1'){
var timelapseRecordingDirectory = s.getTimelapseFrameDirectory(e) var timelapseRecordingDirectory = s.getTimelapseFrameDirectory(e)
activeMonitor.spawn.stdio[7].on('data',function(data){ activeMonitor.spawn.stdio[7].on('data', async function(data){
var fileStream = activeMonitor.recordTimelapseWriter var fileStream = activeMonitor.recordTimelapseWriter
if(!fileStream){ if(!fileStream){
var currentDate = s.formattedTime(null,'YYYY-MM-DD') var currentDate = s.formattedTime(null,'YYYY-MM-DD')
@ -1219,7 +1221,7 @@ module.exports = (s,config,lang) => {
activeMonitor.spawn.stdout.on('data',frameToStreamPrimary) activeMonitor.spawn.stdout.on('data',frameToStreamPrimary)
} }
if(e.details.stream_channels && e.details.stream_channels !== ''){ if(e.details.stream_channels && e.details.stream_channels !== ''){
e.details.stream_channels.forEach((fields,number) => { s.parseJSON(e.details.stream_channels,{}).forEach((fields,number) => {
attachStreamChannelHandlers({ attachStreamChannelHandlers({
ke: groupKey, ke: groupKey,
mid: monitorId, mid: monitorId,
@ -1267,9 +1269,6 @@ module.exports = (s,config,lang) => {
s.debugLog('Queue Automatic Compression',response.insertQuery) s.debugLog('Queue Automatic Compression',response.insertQuery)
reEncodeVideoAndBinOriginalAddToQueue({ reEncodeVideoAndBinOriginalAddToQueue({
video: response.insertQuery, video: response.insertQuery,
targetVideoCodec: 'vp9',
targetAudioCodec: 'libopus',
targetQuality: '-q:v 1 -q:a 1',
targetExtension: 'webm', targetExtension: 'webm',
doSlowly: false, doSlowly: false,
automated: true, automated: true,
@ -1285,6 +1284,19 @@ module.exports = (s,config,lang) => {
} }
}) })
} }
async function doFatalErrorCatch(e,d){
const groupKey = e.ke
const monitorId = e.mid || e.id
if(activeMonitor.isStarted === true){
const activeMonitor = getActiveMonitor(groupKey,monitorId)
activeMonitor.isStarted = false
await cameraDestroy(e)
activeMonitor.isStarted = true
fatalError(e,d)
}else{
await cameraDestroy(e)
}
}
function cameraFilterFfmpegLog(e){ function cameraFilterFfmpegLog(e){
var checkLog = function(d,x){return d.indexOf(x)>-1} var checkLog = function(d,x){return d.indexOf(x)>-1}
const groupKey = e.ke const groupKey = e.ke
@ -1323,30 +1335,18 @@ module.exports = (s,config,lang) => {
break; break;
case checkLog(d,'Connection refused'): case checkLog(d,'Connection refused'):
case checkLog(d,'Connection timed out'): case checkLog(d,'Connection timed out'):
//restart
activeMonitor.timeoutToRestart = setTimeout(function(){
s.userLog(e,{type:lang['Connection timed out'],msg:lang['Retrying...']});
fatalError(e,'Connection timed out');
},1000)
break;
case checkLog(d,'Immediate exit requested'): case checkLog(d,'Immediate exit requested'):
await cameraDestroy(e)
activeMonitor.timeoutToRestart = setTimeout(() => {
launchMonitorProcesses(e)
},15000)
break;
case checkLog(d,'mjpeg_decode_dc'): case checkLog(d,'mjpeg_decode_dc'):
case checkLog(d,'bad vlc'): case checkLog(d,'bad vlc'):
case checkLog(d,'does not contain an image sequence pattern or a pattern is invalid.'):
case checkLog(d,'error dc'): case checkLog(d,'error dc'):
await cameraDestroy(e) // activeMonitor.timeoutToRestart = setTimeout(() => {
activeMonitor.timeoutToRestart = setTimeout(() => { // doFatalErrorCatch(e,d)
launchMonitorProcesses(e) // },15000)
},15000)
break; break;
case checkLog(d,'No route to host'): case checkLog(d,'No route to host'):
await cameraDestroy(e) activeMonitor.timeoutToRestart = setTimeout(async () => {
activeMonitor.timeoutToRestart = setTimeout(() => { doFatalErrorCatch(e,d)
launchMonitorProcesses(e)
},60000) },60000)
break; break;
} }
@ -1425,6 +1425,7 @@ module.exports = (s,config,lang) => {
const monitorConfig = theGroup.rawMonitorConfigurations[monitorId] const monitorConfig = theGroup.rawMonitorConfigurations[monitorId]
const doPingTest = e.type !== 'socket' && e.type !== 'dashcam' && e.protocol !== 'udp' && e.type !== 'local' && e.details.skip_ping !== '1'; const doPingTest = e.type !== 'socket' && e.type !== 'dashcam' && e.protocol !== 'udp' && e.type !== 'local' && e.details.skip_ping !== '1';
const startMonitorInQueue = theGroup.startMonitorInQueue const startMonitorInQueue = theGroup.startMonitorInQueue
if(!activeMonitor.isStarted)return;
// e = monitor object // e = monitor object
clearTimeout(activeMonitor.resetFatalErrorCountTimer) clearTimeout(activeMonitor.resetFatalErrorCountTimer)
activeMonitor.resetFatalErrorCountTimer = setTimeout(()=>{ activeMonitor.resetFatalErrorCountTimer = setTimeout(()=>{
@ -1601,17 +1602,18 @@ module.exports = (s,config,lang) => {
const groupKey = e.ke const groupKey = e.ke
const monitorId = e.mid || e.id const monitorId = e.mid || e.id
const activeMonitor = getActiveMonitor(groupKey,monitorId) const activeMonitor = getActiveMonitor(groupKey,monitorId)
const monitorDetails = getMonitorConfiguration(groupKey,monitorId).details const monitorConfig = copyMonitorConfiguration(groupKey,monitorId)
const monitorDetails = monitorConfig.details
const maxCount = !monitorDetails.fatal_max || isNaN(monitorDetails.fatal_max) ? 0 : parseFloat(monitorDetails.fatal_max); const maxCount = !monitorDetails.fatal_max || isNaN(monitorDetails.fatal_max) ? 0 : parseFloat(monitorDetails.fatal_max);
clearTimeout(activeMonitor.err_fatal_timeout); clearTimeout(activeMonitor.fatalErrorTimeout);
++activeMonitor.errorFatalCount; ++activeMonitor.errorFatalCount;
if(activeMonitor.isStarted === true){ if(activeMonitor.isStarted === true){
activeMonitor.err_fatal_timeout = setTimeout(function(){ activeMonitor.fatalErrorTimeout = setTimeout(function(){
if(maxCount !== 0 && activeMonitor.errorFatalCount > maxCount){ if(maxCount !== 0 && activeMonitor.errorFatalCount > maxCount){
s.userLog(e,{type:lang["Fatal Error"],msg:lang.onFatalErrorExit}); s.userLog(e,{type:lang["Fatal Error"],msg:lang.onFatalErrorExit});
s.camera('stop',{mid:monitorId,ke:groupKey}) s.camera('stop',{mid:monitorId,ke:groupKey})
}else{ }else{
launchMonitorProcesses(s.cleanMonitorObject(e)) launchMonitorProcesses(monitorConfig)
}; };
},5000); },5000);
}else{ }else{
@ -1623,7 +1625,6 @@ module.exports = (s,config,lang) => {
status: lang.Died, status: lang.Died,
code: 7 code: 7
}); });
const monitorConfig = copyMonitorConfiguration(groupKey,monitorId)
s.onMonitorDiedExtensions.forEach(function(extender){ s.onMonitorDiedExtensions.forEach(function(extender){
extender(monitorConfig,e) extender(monitorConfig,e)
}) })
@ -1753,6 +1754,7 @@ module.exports = (s,config,lang) => {
copyMonitorConfiguration, copyMonitorConfiguration,
getMonitorConfiguration, getMonitorConfiguration,
isGroupBelowMaxMonitorCount, isGroupBelowMaxMonitorCount,
setNoEventsDetector,
cameraDestroy: cameraDestroy, cameraDestroy: cameraDestroy,
createSnapshot: createSnapshot, createSnapshot: createSnapshot,
processKill: processKill, processKill: processKill,

View File

@ -24,4 +24,5 @@ module.exports = function(s,config,lang){
require('./notifications/pushover.js')(s,config,lang,getSnapshot) require('./notifications/pushover.js')(s,config,lang,getSnapshot)
require('./notifications/webhook.js')(s,config,lang,getSnapshot) require('./notifications/webhook.js')(s,config,lang,getSnapshot)
require('./notifications/mqtt.js')(s,config,lang,getSnapshot) require('./notifications/mqtt.js')(s,config,lang,getSnapshot)
require('./notifications/matrix.js')(s,config,lang,getSnapshot)
} }

View File

@ -0,0 +1,386 @@
const fs = require("fs")
const fetch = require("node-fetch")
module.exports = function(s,config,lang,getSnapshot){
const {
getEventBasedRecordingUponCompletion,
} = require('../events/utils.js')(s,config,lang)
//matrix bot
if(config.matrixBot === true){
const sdk = require("matrix-js-sdk")
try{
function sendFile(file,groupKey){
const client = s.group[groupKey].matrixBot
const roomId = s.group[groupKey].matrixBotRoom
const {
buffer,
name,
type,
info,
opttype,
} = file;
client.uploadContent(buffer, {
name: name,
type: opttype,
}).then(function(url) {
const content = {
msgtype: type || "m.file",
body: name,
info: info,
url: url.content_uri
};
client.sendMessage(roomId, content);
}).catch(err => {
s.userLog({
ke: groupKey,
mid: '$USER'
},{
type: 'matrix.org Error',
msg: err
});
})
}
const sendMessage = function(data,files,groupKey){
if(!data)data = {};
const client = s.group[groupKey].matrixBot
const roomId = s.group[groupKey].matrixBotRoom
if(!client){
s.userLog({ke:groupKey,mid:'$USER'},{type:'matrix.org Error'})
return
}
if(client.sendMessage){
if(data.text){
const sendBody = {
"body": data.text,
"msgtype": "m.text"
}
client.sendMessage(roomId, sendBody);
}
files.forEach((file) => {
sendFile(file,groupKey)
});
}else{
s.userLog({
ke: groupKey,
mid: '$USER'
},{
type: 'matrix.org Error',
msg: '!sendMessage'
});
}
}
const onEventTriggerBeforeFilterForMatrixBot = function(d,filter){
filter.matrixBot = false
}
const onEventTriggerForMatrixBot = async (d,filter) => {
const monitorConfig = s.group[d.ke].rawMonitorConfigurations[d.id]
// d = event object
const isEnabled = filter.matrixBot || monitorConfig.details.detector_matrixbot === '1' || monitorConfig.details.notify_matrix === '1'
if(s.group[d.ke].matrixBot && isEnabled && !s.group[d.ke].activeMonitors[d.id].detector_matrixbot){
var detector_matrixbot_timeout
if(!monitorConfig.details.detector_matrixbot_timeout||monitorConfig.details.detector_matrixbot_timeout===''){
detector_matrixbot_timeout = 1000 * 60 * 10;
}else{
detector_matrixbot_timeout = parseFloat(monitorConfig.details.detector_matrixbot_timeout) * 1000 * 60;
}
s.group[d.ke].activeMonitors[d.id].detector_matrixbot = setTimeout(function(){
clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_matrixbot);
s.group[d.ke].activeMonitors[d.id].detector_matrixbot = null
},detector_matrixbot_timeout)
await getSnapshot(d,monitorConfig)
if(d.screenshotBuffer){
const imageEventText = `${lang.Event} ${d.screenshotName} ${d.currentTimestamp}`
sendMessage({
text: imageEventText,
},[
{
buffer: d.screenshotBuffer,
name: d.screenshotName+'.jpg',
type: 'm.image',
opttype: 'image/jpeg',
info: {
mimetype: 'image/jpeg',
},
}
],d.ke)
}
if(monitorConfig.details.detector_matrixbot_send_video === '1'){
let videoPath = null
let videoName = null
const eventBasedRecording = await getEventBasedRecordingUponCompletion({
ke: d.ke,
mid: d.mid
})
if(eventBasedRecording.filePath){
videoPath = eventBasedRecording.filePath
videoName = eventBasedRecording.filename
}else{
const siftedVideoFileFromRam = await s.mergeDetectorBufferChunks(d)
videoPath = siftedVideoFileFromRam.filePath
videoName = siftedVideoFileFromRam.filename
}
if(videoPath){
sendMessage({},[
{
buffer: await fs.promises.readFile(videoPath),
name: videoName,
type: 'm.video',
opttype: 'video/mp4',
info: {
mimetype: 'video/mp4',
},
}
],d.ke)
}
}
}
}
const onTwoFactorAuthCodeNotificationForMatrixBot = function(user){
// r = user
if(r.details.factor_matrixbot === '1'){
const eventText = `${user.lang['2-Factor Authentication']} : ${user.lang['Enter this code to proceed']} **${factorAuthKey}** ${user.lang.FactorAuthText1}`
sendMessage({
text: eventText,
},[],d.ke)
}
}
const loadMatrixBotForUser = function(user){
const userDetails = s.parseJSON(user.details);
//matrixbot
if(!s.group[user.ke].matrixBot &&
config.matrixBot === true &&
userDetails.matrixbot === '1' &&
userDetails.matrixbot_baseUrl &&
userDetails.matrixbot_accessToken &&
userDetails.matrixbot_userId &&
userDetails.matrixbot_roomId
){
s.debugLog(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`)
s.debugLog(`Matrix Connecting... ${userDetails.matrixbot_baseUrl}`)
const client = sdk.createClient({
baseUrl: userDetails.matrixbot_baseUrl,
accessToken: userDetails.matrixbot_accessToken,
userId: userDetails.matrixbot_userId
});
s.group[user.ke].matrixBot = client
s.group[user.ke].matrixBotRoom = userDetails.matrixbot_roomId
client.startClient();
client.once('sync', function(state, prevState, res) {
s.userLog({
ke: user.ke,
mid: '$USER'
},{
type: 'matrix.org',
msg: lang.Ready
})
});
}
}
const unloadMatrixBotForUser = function(user){
if(s.group[user.ke].matrixBot && s.group[user.ke].matrixBot.stopClient){
s.group[user.ke].matrixBot.stopClient()
delete(s.group[user.ke].matrixBot)
}
}
const onDetectorNoTriggerTimeoutForMatrixBot = function(e){
//e = monitor object
var currentTime = new Date()
if(e.details.detector_notrigger_matrixbot === '1'){
var html = '*'+lang.NoMotionEmailText2+' ' + (e.details.detector_notrigger_timeout || 10) + ' '+lang.minutes+'.*\n'
html += '**' + lang['Monitor Name'] + '** : '+e.name + '\n'
html += '**' + lang['Monitor ID'] + '** : '+e.id + '\n'
html += currentTime
const eventText = `${lang['"No Motion" Detector']} : ${html}`
sendMessage({
text: eventText,
},[],e.ke)
}
}
const onMonitorUnexpectedExitForMatrixBot = (monitorConfig) => {
const isEnabled = monitorConfig.details.detector_matrixbot === '1' || monitorConfig.details.notify_matrix === '1'
if(isEnabled && monitorConfig.details.notify_onUnexpectedExit === '1'){
const ffmpegCommand = s.group[monitorConfig.ke].activeMonitors[monitorConfig.mid].ffmpeg
const description = lang['Process Crashed for Monitor'] + '\n' + ffmpegCommand
const currentTime = new Date()
const eventText = `${lang['Process Unexpected Exit']} : ${monitorConfig.name} : ${description}`
sendMessage({
text: eventText,
},[],monitorConfig.ke)
}
}
s.loadGroupAppExtender(loadMatrixBotForUser)
s.unloadGroupAppExtender(unloadMatrixBotForUser)
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForMatrixBot)
s.onEventTrigger(onEventTriggerForMatrixBot)
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForMatrixBot)
s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForMatrixBot)
s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForMatrixBot)
s.definitions["Monitor Settings"].blocks["Notifications"].info[0].info.push(
{
"name": "detail=notify_matrix",
"field": "Matrix.org",
"description": "",
"default": "0",
"example": "",
"selector": "h_det_matrixbot",
"fieldType": "select",
"possible": [
{
"name": lang.No,
"value": "0"
},
{
"name": lang.Yes,
"value": "1"
}
]
}
)
s.definitions["Monitor Settings"].blocks["Notifications"].info.push({
"evaluation": "$user.details.use_matrixbot !== '0'",
isFormGroupGroup: true,
"name": "Matrix.org",
"color": "purple",
"section-class": "h_det_matrixbot_input h_det_matrixbot_1",
"info": [
{
"name": "detail=detector_matrixbot_send_video",
"field": lang["Attach Video Clip"] + ` (${lang['on Event']})`,
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"possible": [
{
"name": lang.No,
"value": "0"
},
{
"name": lang.Yes,
"value": "1"
}
]
},
{
"name": "detail=detector_matrixbot_timeout",
"field": lang['Allow Next Alert'] + ` (${lang['on Event']})`,
"description": "",
"default": "10",
"example": "",
"possible": ""
},
{
"name": "detail=detector_notrigger_matrixbot",
"field": lang['No Trigger'],
"description": lang.noTriggerText,
"default": "0",
"example": "",
"fieldType": "select",
"possible": [
{
"name": lang.No,
"value": "0"
},
{
"name": lang.Yes,
"value": "1"
}
]
},
]
})
s.definitions["Account Settings"].blocks["2-Factor Authentication"].info.push({
"name": "detail=factor_matrixbot",
"field": 'Matrix.org',
"default": "1",
"example": "",
"fieldType": "select",
"possible": [
{
"name": lang.No,
"value": "0"
},
{
"name": lang.Yes,
"value": "1"
}
]
})
s.definitions["Account Settings"].blocks["Matrix.org"] = {
"evaluation": "$user.details.use_matrixbot !== '0'",
"name": "Matrix.org",
"color": "purple",
"info": [
{
"name": "detail=matrixbot",
"selector":"u_matrixbot_bot",
"field": lang.Enabled,
"default": "0",
"example": "",
"fieldType": "select",
"possible": [
{
"name": lang.No,
"value": "0"
},
{
"name": lang.Yes,
"value": "1"
}
]
},
{
hidden: true,
"name": "detail=matrixbot_baseUrl",
"placeholder": "",
"field": lang["Host"],
"form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
},
{
hidden: true,
"name": "detail=matrixbot_userId",
"placeholder": "xxxxxxxxxxxxxxxxxx",
"field": lang["User ID"],
"form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
},
{
hidden: true,
"name": "detail=matrixbot_roomId",
"placeholder": "xxxxxxxxxxxxxxxxxx",
"field": lang["Room ID"],
"form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
},
{
hidden: true,
"name": "detail=matrixbot_accessToken",
"fieldType": "password",
"placeholder": "XXXXXXXXXXXXXXXXXXXXXXXX",
"field": lang.Token,
"form-group-class":"u_matrixbot_bot_input u_matrixbot_bot_1",
},
]
}
s.definitions["Event Filters"].blocks["Action for Selected"].info.push({
"name": "actions=matrixBot",
"field": 'Matrix.org',
"fieldType": "select",
"form-group-class": "actions-row",
"default": "",
"example": "1",
"possible": [
{
"name": lang['Original Choice'],
"value": "",
"selected": true
},
{
"name": lang.Yes,
"value": "1",
}
]
})
}catch(err){
console.log(err)
console.log('Could not start Matrix bot, please run "npm install matrix-js-sdk" inside the Shinobi folder.')
}
}
}

View File

@ -323,13 +323,14 @@ module.exports = (s,config,lang) => {
function reEncodeVideoAndBinOriginalAddToQueue(data){ function reEncodeVideoAndBinOriginalAddToQueue(data){
const groupKey = data.video.ke const groupKey = data.video.ke
if(!reEncodeVideoAndBinOriginalQueue[groupKey]){ if(!reEncodeVideoAndBinOriginalQueue[groupKey]){
reEncodeVideoAndBinOriginalQueue[groupKey] = async.queue(async function(data, callback) { reEncodeVideoAndBinOriginalQueue[groupKey] = async.queue(function(data, callback) {
const response = await reEncodeVideoAndBinOriginal(data) reEncodeVideoAndBinOriginal(data).then((response) => {
callback(response) callback(response)
})
}, 1); }, 1);
} }
return new Promise((resolve) => { return new Promise((resolve) => {
reEncodeVideoAndBinOriginalQueue[groupKey].push(data,(response) => { reEncodeVideoAndBinOriginalQueue[groupKey].push(data, function(response){
resolve(response) resolve(response)
}) })
}) })
@ -348,7 +349,7 @@ module.exports = (s,config,lang) => {
targetAudioCodec = targetAudioCodec || `copy` targetAudioCodec = targetAudioCodec || `copy`
targetQuality = targetQuality || `` targetQuality = targetQuality || ``
onPercentChange = onPercentChange || function(){}; onPercentChange = onPercentChange || function(){};
if(!targetVideoCodec || !targetAudioCodec){ if(!targetVideoCodec || !targetAudioCodec || !targetQuality){
switch(targetExtension){ switch(targetExtension){
case'mp4': case'mp4':
targetVideoCodec = `libx264` targetVideoCodec = `libx264`
@ -359,7 +360,7 @@ module.exports = (s,config,lang) => {
case'mkv': case'mkv':
targetVideoCodec = `vp9` targetVideoCodec = `vp9`
targetAudioCodec = `libopus` targetAudioCodec = `libopus`
targetQuality = `-q:v 1 -q:a 1` targetQuality = `-q:v 1 -b:a 96K`
break; break;
} }
} }
@ -427,6 +428,8 @@ module.exports = (s,config,lang) => {
},'GRP_'+groupKey); },'GRP_'+groupKey);
onPercentChange(percent) onPercentChange(percent)
s.debugLog('stderr',outputFilePath,`${percent}%`) s.debugLog('stderr',outputFilePath,`${percent}%`)
}else{
s.debugLog('stderr',lang['Compression Info'],text)
} }
}) })
videoBuildProcess.on('exit',async function(data){ videoBuildProcess.on('exit',async function(data){

View File

@ -790,8 +790,9 @@ module.exports = function(s,config,lang,app,io){
r[n].status = activeMonitor.monitorStatus r[n].status = activeMonitor.monitorStatus
r[n].code = activeMonitor.monitorStatusCode r[n].code = activeMonitor.monitorStatusCode
r[n].subStreamChannel = activeMonitor.subStreamChannel r[n].subStreamChannel = activeMonitor.subStreamChannel
r[n].subStreamActive = !!activeMonitor.subStreamProcess
} }
var buildStreamURL = function(type,channelNumber){ function getStreamUrl(type,channelNumber){
var streamURL var streamURL
if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''} if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''}
switch(type){ switch(type){
@ -810,7 +811,22 @@ module.exports = function(s,config,lang,app,io){
case'mp4': case'mp4':
streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.mp4' streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.mp4'
break; break;
case'useSubstream':
try{
const monitorConfig = s.group[v.ke].rawMonitorConfigurations[v.mid]
const monitorDetails = monitorConfig.details
const subStreamChannelNumber = 1 + (monitorDetails.stream_channels || []).length
const subStreamType = monitorConfig.details.substream.output.stream_type
streamURL = getStreamUrl(subStreamType,subStreamChannelNumber)
}catch(err){
s.debugLog(err)
}
break;
} }
return streamURL
}
var buildStreamURL = function(type,channelNumber){
var streamURL = getStreamUrl(type,channelNumber)
if(streamURL){ if(streamURL){
if(!r[n].streamsSortedByType[type]){ if(!r[n].streamsSortedByType[type]){
r[n].streamsSortedByType[type]=[] r[n].streamsSortedByType[type]=[]
@ -1374,6 +1390,7 @@ module.exports = function(s,config,lang,app,io){
dataPipe.pipe(res) dataPipe.pipe(res)
}).catch((err) => { }).catch((err) => {
console.error('onGetVideoData ERROR',err,videoDetails) console.error('onGetVideoData ERROR',err,videoDetails)
res.status(404)
res.end(user.lang['File Not Found in Database']) res.end(user.lang['File Not Found in Database'])
}) })
}else{ }else{
@ -1383,6 +1400,7 @@ module.exports = function(s,config,lang,app,io){
}) })
} }
}else{ }else{
res.status(404)
res.end(user.lang['File Not Found in Database']) res.end(user.lang['File Not Found in Database'])
} }
}) })
@ -1463,6 +1481,7 @@ module.exports = function(s,config,lang,app,io){
if(videoRow){ if(videoRow){
sendVideo(videoRow) sendVideo(videoRow)
}else{ }else{
res.status(404)
res.end(user.lang['File Not Found in Database']) res.end(user.lang['File Not Found in Database'])
} }
}) })
@ -1829,9 +1848,6 @@ module.exports = function(s,config,lang,app,io){
response.ok = true response.ok = true
reEncodeVideoAndBinOriginalAddToQueue({ reEncodeVideoAndBinOriginalAddToQueue({
video: r, video: r,
targetVideoCodec: 'vp9',
targetAudioCodec: 'libopus',
targetQuality: '-q:v 1 -q:a 1',
targetExtension: 'webm', targetExtension: 'webm',
doSlowly: false doSlowly: false
}).then((encodeResponse) => { }).then((encodeResponse) => {

View File

@ -292,36 +292,40 @@ module.exports = function(s,config,lang,app){
//found address already exists //found address already exists
endData.msg = lang['Email address is in use.']; endData.msg = lang['Email address is in use.'];
}else{ }else{
endData.ok = true
//create new //create new
//user id //user id
form.uid = s.gid() form.uid = s.gid()
//check to see if custom key set //check to see if custom key set
if(!form.ke||form.ke===''){ if(!form.ke){
form.ke=s.gid() form.ke = s.gid()
}else{ }else{
form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '') form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '').trim()
} }
//check if "details" is object if(!s.group[form.ke]){
if(form.details instanceof Object){ endData.ok = true
form.details = JSON.stringify(form.details) //check if "details" is object
} if(form.details instanceof Object){
//write user to db form.details = JSON.stringify(form.details)
s.knexQuery({
action: "insert",
table: "Users",
insert: {
ke: form.ke,
uid: form.uid,
mail: form.mail,
pass: s.createHash(form.pass),
details: form.details
} }
}) //write user to db
s.tx({f:'add_account',details:form.details,ke:form.ke,uid:form.uid,mail:form.mail},'$') s.knexQuery({
endData.user = Object.assign(form,{}) action: "insert",
//init user table: "Users",
s.loadGroup(form) insert: {
ke: form.ke,
uid: form.uid,
mail: form.mail,
pass: s.createHash(form.pass),
details: form.details
}
});
s.tx({f:'add_account',details:form.details,ke:form.ke,uid:form.uid,mail:form.mail},'$')
endData.user = Object.assign(form,{})
//init user
s.loadGroup(form)
}else{
endData.msg = lang["Group with this key exists already"]
}
} }
close() close()
}) })

View File

@ -35,9 +35,36 @@ function saveAccountSettings(){
postData.account = $.aN.selected postData.account = $.aN.selected
} }
$.post(superApiPrefix + $user.sessionKey+'/accounts/'+webPath,postData,function(data){ $.post(superApiPrefix + $user.sessionKey+'/accounts/'+webPath,postData,function(data){
console.log(data) if(webPath === 'editAdmin'){
if(data.ok === true){ if(data.ok === true){
$.aN.e.modal('hide') $.aN.e.modal('hide')
new PNotify({
title: lang['Account Edited'],
text: lang.adminAccountEditedMsg,
type: 'success'
})
}else{
new PNotify({
title: lang.accountEditError,
text: lang['Failed to Edit Account'],
type: 'error'
})
}
}else{
if(data.ok === true){
$.aN.e.modal('hide')
new PNotify({
title: lang['Account Created'],
text: lang.adminAccountCreatedMsg,
type: 'success'
})
}else{
new PNotify({
title: lang.accountCreationError,
text: data.msg + '<br>' + lang.createSubAccountsInfo,
type: 'error'
})
}
} }
}) })
} }
@ -54,7 +81,11 @@ $.aN.e.on('change','[name="mail"]',function(){
var thisVal = $(this).val() var thisVal = $(this).val()
$.each(loadedUsers,function(n,user){ $.each(loadedUsers,function(n,user){
if($.aN.selected && user.ke !== $.aN.selected.ke && thisVal.toLowerCase() === user.mail.toLowerCase()){ if($.aN.selected && user.ke !== $.aN.selected.ke && thisVal.toLowerCase() === user.mail.toLowerCase()){
new PNotify({text:lang['Email address is in use.'],type:'error'}) new PNotify({
title: lang.accountCreationError,
text: lang['Email address is in use.'],
type: 'error'
})
} }
}) })
}) })
@ -63,7 +94,11 @@ $.aN.e.on('change','[name="ke"]',function(){
var thisVal = $(this).val() var thisVal = $(this).val()
$.each(loadedUsers,function(n,user){ $.each(loadedUsers,function(n,user){
if(!$.aN.modeIsEdit() && user.ke === thisVal){ if(!$.aN.modeIsEdit() && user.ke === thisVal){
new PNotify({text:lang['Group Key is in use.'] + ' ' + lang['Create Sub-Accounts at /admin'],type:'error'}) new PNotify({
title: lang.accountCreationError,
text: lang['Group Key is in use.'] + ' ' + lang.createSubAccountsInfo,
type: 'error'
})
} }
}) })
}) })

View File

@ -54,8 +54,7 @@
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link logout"> <a class="nav-link logout">
<i class="fa fa-sign-out-alt"></i> <p><%-lang.Logout%></p>
<p class="d-lg-none d-xl-none"><%-lang.Logout%></p>
</a> </a>
</li> </li>
</ul> </ul>
@ -244,14 +243,12 @@ $.ccio.ws.on('f',function(d){
d.msg = 'Saved Preferences' d.msg = 'Saved Preferences'
break; break;
case'edit_account': case'edit_account':
d.msg='Account Edited';
$.each(d.form,function(n,v){ $.each(d.form,function(n,v){
$.ccio.accounts[d.ke][n]=v $.ccio.accounts[d.ke][n]=v
}) })
$('[ke="'+d.ke+'"] .mail').text(d.form.mail) $('[ke="'+d.ke+'"] .mail').text(d.form.mail)
break; break;
case'add_account': case'add_account':
d.msg='Account Created';
$.ccio.tm(0,d,'#accounts-list') $.ccio.tm(0,d,'#accounts-list')
$.aN.selected = $.ccio.accounts[d.ke] $.aN.selected = $.ccio.accounts[d.ke]
break; break;