update chain framework

action-chain
Moe 2023-09-18 21:16:55 -07:00
parent 21a6d2a355
commit a5d36f0e44
9 changed files with 244 additions and 41 deletions

View File

@ -33,6 +33,8 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => {
require('./libs/auth.js')(s,config,lang) require('./libs/auth.js')(s,config,lang)
//express web server with ejs //express web server with ejs
const app = require('./libs/webServer.js')(s,config,lang,io) const app = require('./libs/webServer.js')(s,config,lang,io)
//chain framework
require('./libs/chains.js')(s,config,lang,app,io)
//data port //data port
require('./libs/dataPort.js')(s,config,lang,app,io) require('./libs/dataPort.js')(s,config,lang,app,io)
//page layout load //page layout load
@ -97,6 +99,4 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => {
require('./libs/cron.js')(s,config,lang) require('./libs/cron.js')(s,config,lang)
//video browser functions //video browser functions
require('./libs/videoBrowser.js')(s,config,lang,app,io) require('./libs/videoBrowser.js')(s,config,lang,app,io)
//chain framework
require('./libs/chains.js')(s,config,lang,app,io)
}) })

View File

@ -1,5 +1,6 @@
module.exports = function(s,config,lang,app,io){ module.exports = function(s,config,lang,app,io){
s.loadedChains = {} s.loadedChains = {}
s.recentSnapshots = {}
s.loadedChainActions = {} s.loadedChainActions = {}
const { const {
loadChains, loadChains,

View File

@ -1,10 +1,13 @@
const moment = require('moment') const moment = require('moment')
module.exports = function(s,config,lang){ module.exports = function(s,config,lang){
const { const {
findMonitorsAssociatedToTags,
createEventBasedRecording, createEventBasedRecording,
} = require('../events/utils.js')(s,config,lang) } = require('../events/utils.js')(s,config,lang)
const { const {
addExtenderAction, addExtenderAction,
getMonitorIdFromData,
doMonitorActionForItem,
} = require('./utils.js')(s,config) } = require('./utils.js')(s,config)
// static actions // static actions
addExtenderAction('forceRecord',(groupKey,item,data) => { addExtenderAction('forceRecord',(groupKey,item,data) => {
@ -12,26 +15,12 @@ module.exports = function(s,config,lang){
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId] const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
const monitorDetails = monitorConfig.details const monitorDetails = monitorConfig.details
const secondBefore = (parseInt(monitorDetails.detector_buffer_seconds_before) || 5) + 1 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){ doMonitorActionForItem(groupKey,item,data,beginRecording)
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)
// }
}
})
addExtenderAction('createLog',(groupKey,item,data) => { addExtenderAction('createLog',(groupKey,item,data) => {
const monitorId = item.monitorId || item.monitorIdFromData ? getMonitorIdFromData(data) : '$USER';
s.userLog({ s.userLog({
ke: groupKey, ke: groupKey,
mid: monitorId, mid: monitorId,
@ -39,5 +28,5 @@ module.exports = function(s,config,lang){
type: item.title, type: item.title,
text: item.text text: item.text
}); });
}) });
} }

View File

@ -1,5 +1,8 @@
module.exports = function(s,config){ module.exports = function(s,config){
let loadedChains = s.loadedChains const {
getMonitorIdFromData,
} = require('./utils.js')(s,config)
var loadedChains = s.loadedChains
async function loadChains(){ async function loadChains(){
const selectResponse = await s.knexQueryPromise({ const selectResponse = await s.knexQueryPromise({
action: "select", action: "select",
@ -10,17 +13,43 @@ module.exports = function(s,config){
foundChains.forEach(loadChain); foundChains.forEach(loadChain);
} }
function loadChain(item){ function loadChain(item){
// "item" should always be the first item in a chain const name = item.name
const groupKey = item.ke const groupKey = item.ke
const extenderThatStartsThis = item.ignitor const extenderThatStartsThis = item.ignitor
item.conditions = JSON.parse(item.conditions) item.conditions = JSON.parse(item.conditions)
item.next = JSON.parse(item.next) item.next = JSON.parse(item.next)
if(!loadedChains[extenderThatStartsThis])loadedChains[extenderThatStartsThis] = {} if(!loadedChains[extenderThatStartsThis])loadedChains[extenderThatStartsThis] = {}
if(!loadedChains[extenderThatStartsThis][groupKey])loadedChains[extenderThatStartsThis][groupKey] = {} 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){ 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){ function evaluateCondition(condition,toCheck){
var param = toCheck[condition.p1] var param = toCheck[condition.p1]
@ -68,7 +97,7 @@ module.exports = function(s,config){
} }
if(hasOpenBracket)++numberOfOpenAndCloseBrackets; if(hasOpenBracket)++numberOfOpenAndCloseBrackets;
if(hasCloseBracket)++numberOfOpenAndCloseBrackets; if(hasCloseBracket)++numberOfOpenAndCloseBrackets;
if(matrices)conditionChain[place].matrixCount = matrices.length // if(matrices)conditionChain[place].matrixCount = matrices.length
switch(condition.p1){ switch(condition.p1){
case'tag': case'tag':
case'x': case'x':
@ -102,9 +131,14 @@ module.exports = function(s,config){
} }
} }
break; break;
default: case'mid':
conditionChain[place].ok = evaluateCondition(condition,d.details) var requiredMonitorId = getMonitorIdFromData(data);
var monitorId = condition.p3;
conditionChain[place].ok = monitorId === requiredMonitorId;
break; break;
// default:
// conditionChain[place].ok = evaluateCondition(condition,d.details)
// break;
} }
} }
const allowBrackets = numberOfOpenAndCloseBrackets === 0 || isEven(numberOfOpenAndCloseBrackets); const allowBrackets = numberOfOpenAndCloseBrackets === 0 || isEven(numberOfOpenAndCloseBrackets);
@ -143,8 +177,8 @@ module.exports = function(s,config){
if(theChain){ if(theChain){
for (const groupKey in theChain) { for (const groupKey in theChain) {
const items = theChain[groupKey] const items = theChain[groupKey]
for (let i = 0; i < items.length; i++) { for (const name in items) {
const item = items[i]; const item = items[name];
executeChainItem(groupKey,item,data); executeChainItem(groupKey,item,data);
} }
} }
@ -154,10 +188,13 @@ module.exports = function(s,config){
return { return {
loadChains, loadChains,
loadChain, loadChain,
unloadChain,
saveChain, saveChain,
deleteChain,
evaluateCondition, evaluateCondition,
checkChainItemConditions, checkChainItemConditions,
executeChainItem, executeChainItem,
addChainControllerToExtender, addChainControllerToExtender,
getMonitorIdFromData,
} }
} }

View File

@ -1,8 +1,76 @@
module.exports = function(s,config){ module.exports = function(s,config){
var recentSnapshots = s.recentSnapshots
function addExtenderAction(name,theAction){ function addExtenderAction(name,theAction){
s.loadedChainActions[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 { return {
addExtenderAction, addExtenderAction,
getMonitorIdFromData,
getRecentSnapshot,
doMonitorActionForItem,
} }
} }

View File

@ -834,13 +834,16 @@ module.exports = (s,config,lang) => {
return `${icon} ${tag}`; return `${icon} ${tag}`;
} }
function getObjectTagsFromMatrices(d){ function getObjectTagsFromMatrices(d){
if(d.details.reason === 'motion'){ const eventDetails = d.details
if(!eventDetails){
return []
}else if(eventDetails.reason === 'motion'){
return [getTagWithIcon(lang.Motion)] return [getTagWithIcon(lang.Motion)]
}else if(d.details.matrices){ }else if(eventDetails.matrices){
const matrices = d.details.matrices const matrices = eventDetails.matrices
return [...new Set(matrices.map(matrix => getTagWithIcon(matrix.tag)))]; return [...new Set(matrices.map(matrix => getTagWithIcon(matrix.tag)))];
} }
return [getTagWithIcon(d.details.reason)] return [getTagWithIcon(eventDetails.reason)]
} }
function getObjectTagNotifyText(d){ function getObjectTagNotifyText(d){
const monitorId = d.mid || d.id const monitorId = d.mid || d.id
@ -870,5 +873,6 @@ module.exports = (s,config,lang) => {
triggerEvent: triggerEvent, triggerEvent: triggerEvent,
addEventDetailsToString: addEventDetailsToString, addEventDetailsToString: addEventDetailsToString,
getEventBasedRecordingUponCompletion: getEventBasedRecordingUponCompletion, getEventBasedRecordingUponCompletion: getEventBasedRecordingUponCompletion,
findMonitorsAssociatedToTags,
} }
} }

View File

@ -302,13 +302,14 @@ module.exports = function(s,config,lang){
} }
s.mergeDetectorBufferChunks = function(monitor,callback){ s.mergeDetectorBufferChunks = function(monitor,callback){
return new Promise((resolve,reject) => { 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 mergedFile = s.formattedTime()+'.mp4'
var mergedFilepath = pathDir+mergedFile var mergedFilepath = pathDir+mergedFile
fs.readdir(pathDir,function(err,streamDirItems){ fs.readdir(pathDir,function(err,streamDirItems){
var items = [] var items = []
var copiedItems = [] 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 || videoLength === '')videoLength = '10'
if(videoLength.length === 1)videoLength = '0' + videoLength if(videoLength.length === 1)videoLength = '0' + videoLength
var createMerged = function(copiedItems){ var createMerged = function(copiedItems){

View File

@ -5,6 +5,13 @@ module.exports = function(s,config,lang,getSnapshot){
getObjectTagNotifyText, getObjectTagNotifyText,
getEventBasedRecordingUponCompletion, getEventBasedRecordingUponCompletion,
} = require('../events/utils.js')(s,config,lang) } = 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 //discord bot
if(config.discordBot === true){ if(config.discordBot === true){
try{ try{
@ -224,6 +231,93 @@ module.exports = function(s,config,lang,getSnapshot){
},[],monitorConfig.ke) },[],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.loadGroupAppExtender(loadDiscordBotForUser)
s.unloadGroupAppExtender(unloadDiscordBotForUser) s.unloadGroupAppExtender(unloadDiscordBotForUser)
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForDiscord) s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForDiscord)
@ -231,6 +325,7 @@ module.exports = function(s,config,lang,getSnapshot){
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForDiscord) s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForDiscord)
s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForDiscord) s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForDiscord)
s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForDiscord) s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForDiscord)
s.definitions["Monitor Settings"].blocks["Notifications"].info[0].info.push( s.definitions["Monitor Settings"].blocks["Notifications"].info[0].info.push(
{ {
"name": "detail=notify_discord", "name": "detail=notify_discord",
@ -389,6 +484,9 @@ module.exports = function(s,config,lang,getSnapshot){
} }
] ]
}) })
s.loadedChainActions['notifyDiscord'] = (groupKey,item,data) => {
}
}catch(err){ }catch(err){
console.log(err) console.log(err)
console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.') console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.')

View File

@ -1,22 +1,25 @@
// example chain // example chain
module.exports = { module.exports = {
name: 'Sample Chain',
ke: 'groupKey', ke: 'groupKey',
mid: 'monitorId',
ignitor: 'onEventTrigger', ignitor: 'onEventTrigger',
conditions: [ conditions: [
{p1: 'time', p2: '>', p3: '05:00:00', p4: '&&'}, {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: [ next: [
{ {
action: 'forceRecord', action: 'forceRecord',
allMonitors: false, allMonitors: false,
monitorIdFromData: true,
monitorIds: [], monitorIds: [],
monitorTags: [], monitorTags: [],
next: [ next: [
{ {
action: 'createLog', 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", title: "Text Log on Recording After Event",
text: "Recording has Started" text: "Recording has Started"
}, },
@ -28,13 +31,14 @@ module.exports = {
timeoutUntilAllowAgain: 1000 * 60 * 10, // 10 minutes timeoutUntilAllowAgain: 1000 * 60 * 10, // 10 minutes
sendSnapshot: true, sendSnapshot: true,
sendVideo: true, sendVideo: true,
sendForTriggeredMonitorOnly: true, monitorIdFromData: true,
monitorIds: [], monitorIds: [],
monitorTags: [], monitorTags: [],
next: [ next: [
{ {
action: 'createLog', 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", title: "Discord Note",
text: 'Person detected in Back-1' text: 'Person detected in Back-1'
}, },
@ -45,6 +49,7 @@ module.exports = {
sendSnapshot: true, sendSnapshot: true,
sendVideo: true, sendVideo: true,
allMonitors: false, allMonitors: false,
monitorIdFromData: true,
monitorIds: [], monitorIds: [],
monitorTags: [], monitorTags: [],
}, },