diff --git a/camera.js b/camera.js index 1444ed61..cac88b35 100644 --- a/camera.js +++ b/camera.js @@ -33,6 +33,8 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => { require('./libs/auth.js')(s,config,lang) //express web server with ejs const app = require('./libs/webServer.js')(s,config,lang,io) + //chain framework + require('./libs/chains.js')(s,config,lang,app,io) //data port require('./libs/dataPort.js')(s,config,lang,app,io) //page layout load @@ -97,6 +99,4 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => { require('./libs/cron.js')(s,config,lang) //video browser functions require('./libs/videoBrowser.js')(s,config,lang,app,io) - //chain framework - require('./libs/chains.js')(s,config,lang,app,io) }) diff --git a/libs/chains.js b/libs/chains.js index b1f13f43..458592ed 100644 --- a/libs/chains.js +++ b/libs/chains.js @@ -1,5 +1,6 @@ module.exports = function(s,config,lang,app,io){ s.loadedChains = {} + s.recentSnapshots = {} s.loadedChainActions = {} const { loadChains, diff --git a/libs/chains/actions.js b/libs/chains/actions.js index 6d93fe2a..e237dae7 100644 --- a/libs/chains/actions.js +++ b/libs/chains/actions.js @@ -1,10 +1,13 @@ const moment = require('moment') module.exports = function(s,config,lang){ const { + findMonitorsAssociatedToTags, createEventBasedRecording, } = require('../events/utils.js')(s,config,lang) const { addExtenderAction, + getMonitorIdFromData, + doMonitorActionForItem, } = require('./utils.js')(s,config) // static actions addExtenderAction('forceRecord',(groupKey,item,data) => { @@ -12,26 +15,12 @@ module.exports = function(s,config,lang){ const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId] const monitorDetails = monitorConfig.details const secondBefore = (parseInt(monitorDetails.detector_buffer_seconds_before) || 5) + 1 - createEventBasedRecording(monitor,moment(eventTime).subtract(secondBefore,'seconds').format('YYYY-MM-DDTHH-mm-ss')) + createEventBasedRecording(monitorConfig,moment(eventTime).subtract(secondBefore,'seconds').format('YYYY-MM-DDTHH-mm-ss')) } - function recurseMonitorIds(monitorIds){ - for (let i = 0; i < monitorIds.length; i++) { - const monitorId = item[i] - beginRecording(monitorId) - } - } - if(item.allMonitors){ - const monitorIds = Object.keys(s.group[groupKey].rawMonitorConfigurations) - recurseMonitorIds(monitorIds) - }else{ - if(item.monitorIds)recurseMonitorIds(item.monitorIds); - // for (let i = 0; i < item.monitorTags.length; i++) { - // const monitorIds = someHowGetMonitorIdsFromTag(item[i]) - // recurseMonitorIds(monitorIds) - // } - } - }) + doMonitorActionForItem(groupKey,item,data,beginRecording) + }); addExtenderAction('createLog',(groupKey,item,data) => { + const monitorId = item.monitorId || item.monitorIdFromData ? getMonitorIdFromData(data) : '$USER'; s.userLog({ ke: groupKey, mid: monitorId, @@ -39,5 +28,5 @@ module.exports = function(s,config,lang){ type: item.title, text: item.text }); - }) + }); } diff --git a/libs/chains/core.js b/libs/chains/core.js index 74489c31..6c70f1b8 100644 --- a/libs/chains/core.js +++ b/libs/chains/core.js @@ -1,5 +1,8 @@ module.exports = function(s,config){ - let loadedChains = s.loadedChains + const { + getMonitorIdFromData, + } = require('./utils.js')(s,config) + var loadedChains = s.loadedChains async function loadChains(){ const selectResponse = await s.knexQueryPromise({ action: "select", @@ -10,17 +13,43 @@ module.exports = function(s,config){ foundChains.forEach(loadChain); } function loadChain(item){ - // "item" should always be the first item in a chain + const name = item.name const groupKey = item.ke const extenderThatStartsThis = item.ignitor item.conditions = JSON.parse(item.conditions) item.next = JSON.parse(item.next) if(!loadedChains[extenderThatStartsThis])loadedChains[extenderThatStartsThis] = {} if(!loadedChains[extenderThatStartsThis][groupKey])loadedChains[extenderThatStartsThis][groupKey] = {} - loadedChains[extenderThatStartsThis][groupKey] = item + loadedChains[extenderThatStartsThis][groupKey][name] = item + } + function unloadChain(item){ + const name = item.name + const groupKey = item.ke + const extenderThatStartsThis = item.ignitor + delete(loadedChains[extenderThatStartsThis][groupKey][name]) } function saveChain(item){ - + await loadChain(item) + return s.knexQueryPromise({ + action: "insert", + table: "Chains", + insert: Object.assign({},item,{ + conditions: JSON.stringify(item.conditions), + next: JSON.stringify(item.next), + }) + }) + } + function deleteChain(item){ + await unloadChain(item) + return s.knexQueryPromise({ + action: "delete", + table: "Chains", + where: { + ke: item.ke, + name: item.name, + ignitor: item.ignitor, + } + }) } function evaluateCondition(condition,toCheck){ var param = toCheck[condition.p1] @@ -68,7 +97,7 @@ module.exports = function(s,config){ } if(hasOpenBracket)++numberOfOpenAndCloseBrackets; if(hasCloseBracket)++numberOfOpenAndCloseBrackets; - if(matrices)conditionChain[place].matrixCount = matrices.length + // if(matrices)conditionChain[place].matrixCount = matrices.length switch(condition.p1){ case'tag': case'x': @@ -102,9 +131,14 @@ module.exports = function(s,config){ } } break; - default: - conditionChain[place].ok = evaluateCondition(condition,d.details) + case'mid': + var requiredMonitorId = getMonitorIdFromData(data); + var monitorId = condition.p3; + conditionChain[place].ok = monitorId === requiredMonitorId; break; + // default: + // conditionChain[place].ok = evaluateCondition(condition,d.details) + // break; } } const allowBrackets = numberOfOpenAndCloseBrackets === 0 || isEven(numberOfOpenAndCloseBrackets); @@ -143,8 +177,8 @@ module.exports = function(s,config){ if(theChain){ for (const groupKey in theChain) { const items = theChain[groupKey] - for (let i = 0; i < items.length; i++) { - const item = items[i]; + for (const name in items) { + const item = items[name]; executeChainItem(groupKey,item,data); } } @@ -154,10 +188,13 @@ module.exports = function(s,config){ return { loadChains, loadChain, + unloadChain, saveChain, + deleteChain, evaluateCondition, checkChainItemConditions, executeChainItem, addChainControllerToExtender, + getMonitorIdFromData, } } diff --git a/libs/chains/utils.js b/libs/chains/utils.js index 3202e8de..e2598189 100644 --- a/libs/chains/utils.js +++ b/libs/chains/utils.js @@ -1,8 +1,76 @@ module.exports = function(s,config){ + var recentSnapshots = s.recentSnapshots function addExtenderAction(name,theAction){ s.loadedChainActions[name] = theAction } + function getMonitorIdFromData(data){ + let monitorId = null + data.forEach((obj) => { + if(obj.mid || obj.id)monitorId = monitorId || obj.mid || obj.id; + }) + return monitorId; + } + async function getRecentSnapshot(monitorConfig){ + const monitorId = monitorConfig.mid + const groupKey = monitorConfig.ke + if(recentSnapshots[`${groupKey}${monitorId}`]){ + return recentSnapshots[`${groupKey}${monitorId}`] + } + const { screenShot, isStaticFile } = await s.getRawSnapshotFromMonitor(monitorConfig,{ + secondsInward: monitorConfig.details.snap_seconds_inward + }); + recentSnapshots[`${groupKey}${monitorId}`] = screenShot; + setTimeout(() => { + delete(recentSnapshots[`${groupKey}${monitorId}`]); + }, 20 * 1000) + return screenShot + } + async function getRecentRecording(monitorConfig){ + const monitorId = monitorConfig.mid + const groupKey = monitorConfig.ke + let videoPath = null + let videoName = null + const eventBasedRecording = await getEventBasedRecordingUponCompletion({ + ke: groupKey, + mid: monitorId + }) + if(eventBasedRecording.filePath){ + videoPath = eventBasedRecording.filePath + videoName = eventBasedRecording.filename + }else{ + const siftedVideoFileFromRam = await s.mergeDetectorBufferChunks(monitorConfig) + videoPath = siftedVideoFileFromRam.filePath + videoName = siftedVideoFileFromRam.filename + } + return { + path: videoPath, + name: videoName + } + } + function recurseMonitorIds(monitorIds,someFunction){ + for (let i = 0; i < monitorIds.length; i++) { + const monitorId = item[i] + someFunction(monitorId) + } + } + function doMonitorActionForItem(groupKey,item,data,someFunction){ + if(item.monitorIdFromData){ + const monitorId = getMonitorIdFromData(data) + if(monitorId)someFunction(monitorId) + }else if(item.allMonitors){ + const monitorIds = Object.keys(s.group[groupKey].rawMonitorConfigurations) + recurseMonitorIds(monitorIds,someFunction) + }else{ + if(item.monitorIds)recurseMonitorIds(item.monitorIds,someFunction); + const monitorTags = item.monitorTags || [] + const monitorIdsFromTags = findMonitorsAssociatedToTags(groupKey,monitorTags) + recurseMonitorIds(monitorIdsFromTags,someFunction) + } + } return { addExtenderAction, + getMonitorIdFromData, + getRecentSnapshot, + doMonitorActionForItem, } } diff --git a/libs/events/utils.js b/libs/events/utils.js index f366fb8d..f3248ee1 100644 --- a/libs/events/utils.js +++ b/libs/events/utils.js @@ -834,13 +834,16 @@ module.exports = (s,config,lang) => { return `${icon} ${tag}`; } function getObjectTagsFromMatrices(d){ - if(d.details.reason === 'motion'){ + const eventDetails = d.details + if(!eventDetails){ + return [] + }else if(eventDetails.reason === 'motion'){ return [getTagWithIcon(lang.Motion)] - }else if(d.details.matrices){ - const matrices = d.details.matrices + }else if(eventDetails.matrices){ + const matrices = eventDetails.matrices return [...new Set(matrices.map(matrix => getTagWithIcon(matrix.tag)))]; } - return [getTagWithIcon(d.details.reason)] + return [getTagWithIcon(eventDetails.reason)] } function getObjectTagNotifyText(d){ const monitorId = d.mid || d.id @@ -870,5 +873,6 @@ module.exports = (s,config,lang) => { triggerEvent: triggerEvent, addEventDetailsToString: addEventDetailsToString, getEventBasedRecordingUponCompletion: getEventBasedRecordingUponCompletion, + findMonitorsAssociatedToTags, } } diff --git a/libs/monitor.js b/libs/monitor.js index 95506de6..a991542d 100644 --- a/libs/monitor.js +++ b/libs/monitor.js @@ -302,13 +302,14 @@ module.exports = function(s,config,lang){ } s.mergeDetectorBufferChunks = function(monitor,callback){ return new Promise((resolve,reject) => { - var pathDir = s.dir.streams+monitor.ke+'/'+monitor.id+'/' + const monitorId = monitor.mid || monitor.id; + var pathDir = s.dir.streams+monitor.ke+'/'+monitorId+'/' var mergedFile = s.formattedTime()+'.mp4' var mergedFilepath = pathDir+mergedFile fs.readdir(pathDir,function(err,streamDirItems){ var items = [] var copiedItems = [] - var videoLength = s.group[monitor.ke].rawMonitorConfigurations[monitor.id].details.detector_send_video_length + var videoLength = s.group[monitor.ke].rawMonitorConfigurations[monitorId].details.detector_send_video_length if(!videoLength || videoLength === '')videoLength = '10' if(videoLength.length === 1)videoLength = '0' + videoLength var createMerged = function(copiedItems){ diff --git a/libs/notifications/discordBot.js b/libs/notifications/discordBot.js index d88f7e5b..98ac2967 100644 --- a/libs/notifications/discordBot.js +++ b/libs/notifications/discordBot.js @@ -5,6 +5,13 @@ module.exports = function(s,config,lang,getSnapshot){ getObjectTagNotifyText, getEventBasedRecordingUponCompletion, } = require('../events/utils.js')(s,config,lang) + const { + addExtenderAction, + doMonitorActionForItem, + } = require('./utils.js')(s,config) + const { + getRecentSnapshot, + } = require('../chains/utils.js')(s,config) //discord bot if(config.discordBot === true){ try{ @@ -224,6 +231,93 @@ module.exports = function(s,config,lang,getSnapshot){ },[],monitorConfig.ke) } } + const loadedChainAction = async (groupKey,item,data) => { + const currentTime = new Date() + const timeoutUntilAllowAgain = item.timeoutUntilAllowAgain + function replaceParamsInString(monitorConfig){ + const name = monitorConfig.name + const objectTags = data[0] && data[0].details ? data[0].details.matrices.map(item => item.tag) : [] + const newString = item.text + .replace(/${MONITOR_NAME}/g, name) + .replace(/${OBJECT_TAGS}/g, objectTags.join(', ')) + return newString; + } + const notifyText = replaceParamsInString(monitorConfig,data) + async function sendText(){ + sendMessage({ + author: { + name: "", + icon_url: config.iconURL + }, + title: notifyText, + description: notifyText+' '+currentTime, + fields: [], + timestamp: currentTime, + footer: { + icon_url: config.iconURL, + text: "Shinobi Systems" + } + },[], groupKey) + } + async function sendSnapshot(monitorId){ + const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId] + const monitorName = monitorConfig.name + const snapshotBuffer = await getRecentSnapshot(monitorConfig) + // const notifyText = getObjectTagNotifyText(data[0]) + sendMessage({ + author: { + name: monitorName, + icon_url: config.iconURL + }, + title: notifyText, + description: notifyText+' '+currentTime, + fields: [], + timestamp: currentTime, + footer: { + icon_url: config.iconURL, + text: "Shinobi Systems" + } + },[ + { + attachment: snapshotBuffer, + name: notifyText + '.jpg' + } + ], groupKey) + } + async function sendVideo(monitorId){ + const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId] + const monitorName = monitorConfig.name + const video = await getRecentRecording(monitorConfig) + // const notifyText = getObjectTagNotifyText(data[0]) + if(videoPath){ + sendMessage({ + author: { + name: monitorName, + icon_url: config.iconURL + }, + title: `${notifyText}`, + description: notifyText+' '+currentTime, + fields: [], + timestamp: currentTime, + footer: { + icon_url: config.iconURL, + text: "Shinobi Systems" + } + },[ + { + attachment: videoPath, + name: notifyText + '.mp4' + } + ],d.ke) + } + } + async function sendMedia(monitorId){ + if(item.sendSnapshot)await sendSnapshot(monitorId); + if(item.sendVideo)await sendVideo(monitorId); + } + await sendText() + doMonitorActionForItem(groupKey,item,data,sendMedia) + } s.loadGroupAppExtender(loadDiscordBotForUser) s.unloadGroupAppExtender(unloadDiscordBotForUser) s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForDiscord) @@ -231,6 +325,7 @@ module.exports = function(s,config,lang,getSnapshot){ s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForDiscord) s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForDiscord) s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForDiscord) + s.definitions["Monitor Settings"].blocks["Notifications"].info[0].info.push( { "name": "detail=notify_discord", @@ -389,6 +484,9 @@ module.exports = function(s,config,lang,getSnapshot){ } ] }) + s.loadedChainActions['notifyDiscord'] = (groupKey,item,data) => { + + } }catch(err){ console.log(err) console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.') diff --git a/samples/chain.js b/samples/chain.js index d2df8b03..c3d966cc 100644 --- a/samples/chain.js +++ b/samples/chain.js @@ -1,22 +1,25 @@ // example chain module.exports = { + name: 'Sample Chain', ke: 'groupKey', - mid: 'monitorId', ignitor: 'onEventTrigger', conditions: [ {p1: 'time', p2: '>', p3: '05:00:00', p4: '&&'}, - {p1: 'time', p2: '<', p3: '13:00:00', p4: '&&'} + {p1: 'time', p2: '<', p3: '13:00:00', p4: '&&'}, + {p1: 'mid', p2: '===', p3: 'monitorId', p4: '&&'}, ], next: [ { action: 'forceRecord', allMonitors: false, + monitorIdFromData: true, monitorIds: [], monitorTags: [], next: [ { action: 'createLog', - monitorId: '$USER', //actual monitor id or $USER for user level log + monitorIdFromData: true, + // monitorId: '$USER', //actual monitor id or $USER for user level log title: "Text Log on Recording After Event", text: "Recording has Started" }, @@ -28,13 +31,14 @@ module.exports = { timeoutUntilAllowAgain: 1000 * 60 * 10, // 10 minutes sendSnapshot: true, sendVideo: true, - sendForTriggeredMonitorOnly: true, + monitorIdFromData: true, monitorIds: [], monitorTags: [], next: [ { action: 'createLog', - monitorId: '$USER', //actual monitor id or $USER for user level log + monitorIdFromData: true, + // monitorId: '$USER', //actual monitor id or $USER for user level log title: "Discord Note", text: 'Person detected in Back-1' }, @@ -45,6 +49,7 @@ module.exports = { sendSnapshot: true, sendVideo: true, allMonitors: false, + monitorIdFromData: true, monitorIds: [], monitorTags: [], },