Giatso : Critical Fixes and Matrix Support
parent
e7f2b443c1
commit
aa20e613a4
|
@ -25,7 +25,7 @@
|
|||
"auth": {
|
||||
"user": "your_email@gmail.com",
|
||||
"pass": "your_password_or_app_specific_password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cron":{},
|
||||
"pluginKeys":{}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
"accountAdded": "Account Added",
|
||||
"accountAddedText": "Account has been added.",
|
||||
"monitorDeleted": "Monitor Deleted",
|
||||
"accountCreationError": "Account Creation Error",
|
||||
"accountEditError": "Account Edit Error",
|
||||
"Unmute": "Unmute",
|
||||
"byUser": "by user",
|
||||
"accountDeleted": "Account Deleted",
|
||||
|
@ -34,7 +36,6 @@
|
|||
"Active Monitors": "Active Monitors",
|
||||
"Storage Use": "Storage Use",
|
||||
"Use Raw Snapshot": "Use Raw Snapshot",
|
||||
"Account Edited": "Account Edited",
|
||||
"Failed to Edit Account": "Failed to Edit Account",
|
||||
"How to Connect": "How to Connect",
|
||||
"Login": "Login",
|
||||
|
@ -559,7 +560,14 @@
|
|||
"InvalidJSONText": "Please ensure this is a valid JSON string for Shinobi monitor configuration.",
|
||||
"Passwords don't match": "Passwords don't match",
|
||||
"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.",
|
||||
"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",
|
||||
"No Events found for this video": "No Events found for this video",
|
||||
"Video and Time Span (Minutes)": "Video and Time Span (Minutes)",
|
||||
|
@ -1057,6 +1065,7 @@
|
|||
"Monitor mode is already": "Monitor mode is already",
|
||||
"Monitor or Key does not exist.": "Monitor or Key does not exist.",
|
||||
"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!",
|
||||
"Downloading...": "Downloading...",
|
||||
"Loading...": "Loading...",
|
||||
|
|
|
@ -360,10 +360,19 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
},7000)
|
||||
}
|
||||
const getLargestMatrix = (matrices) => {
|
||||
const getLargestMatrix = (matrices,imgWidth,imgHeight) => {
|
||||
var largestMatrix = {width: 0, height: 0}
|
||||
const maxWidth = imgWidth * 0.95;
|
||||
const maxHeight = imgHeight * 0.95;
|
||||
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
|
||||
}
|
||||
|
@ -377,7 +386,7 @@ module.exports = function(s,config,lang){
|
|||
const imageCenterX = imgWidth / 2
|
||||
const imageCenterY = imgHeight / 2
|
||||
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;
|
||||
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
|
||||
const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
|
||||
|
@ -414,6 +423,7 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
}
|
||||
function setHomePositionPreset(e){
|
||||
const monitorId = e.mid || e.id
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
setPresetForCurrentPosition({
|
||||
|
|
|
@ -7,7 +7,9 @@ module.exports = (s,config,lang) => {
|
|||
if(config.doCronAsWorker===undefined)config.doCronAsWorker = true;
|
||||
const startWorker = () => {
|
||||
const pathToWorkerScript = __dirname + `/cron/worker.js`
|
||||
const workerProcess = new Worker(pathToWorkerScript)
|
||||
const workerProcess = new Worker(pathToWorkerScript,{
|
||||
workerData: config
|
||||
})
|
||||
workerProcess.on('message',function(data){
|
||||
if(data.time === 'moment()')data.time = moment();
|
||||
switch(data.f){
|
||||
|
|
|
@ -3,8 +3,8 @@ const path = require('path');
|
|||
const moment = require('moment');
|
||||
const exec = require('child_process').exec;
|
||||
const spawn = require('child_process').spawn;
|
||||
const { parentPort, isMainThread } = require('worker_threads');
|
||||
const config = require(process.cwd() + '/conf.json')
|
||||
const { parentPort, isMainThread, workerData } = require('worker_threads');
|
||||
const config = workerData
|
||||
process.on('uncaughtException', function (err) {
|
||||
errorLog('uncaughtException',err);
|
||||
});
|
||||
|
|
|
@ -590,9 +590,6 @@ module.exports = (s,config,lang,app,io) => {
|
|||
if(autoCompressionEnabled){
|
||||
reEncodeVideoAndBinOriginalAddToQueue({
|
||||
video: response.insertQuery,
|
||||
targetVideoCodec: 'vp9',
|
||||
targetAudioCodec: 'libopus',
|
||||
targetQuality: '-q:v 1 -q:a 1',
|
||||
targetExtension: 'webm',
|
||||
doSlowly: false,
|
||||
automated: true,
|
||||
|
|
|
@ -73,6 +73,7 @@ module.exports = (s,config,lang) => {
|
|||
doResolve(response)
|
||||
}
|
||||
try{
|
||||
proc.removeAllListeners()
|
||||
proc.on('exit',() => {
|
||||
response.msg = 'proc.on.exit'
|
||||
clearTimeout(killTimer)
|
||||
|
@ -148,6 +149,7 @@ module.exports = (s,config,lang) => {
|
|||
clearInterval(activeMonitor.getMonitorCpuUsage);
|
||||
clearInterval(activeMonitor.objectCountIntervals);
|
||||
clearTimeout(activeMonitor.timeoutToRestart)
|
||||
clearTimeout(activeMonitor.fatalErrorTimeout);
|
||||
delete(activeMonitor.onvifConnection)
|
||||
// if(activeMonitor.onChildNodeExit){
|
||||
// activeMonitor.onChildNodeExit()
|
||||
|
@ -709,7 +711,7 @@ module.exports = (s,config,lang) => {
|
|||
clearTimeout(activeMonitor.trigger_timer)
|
||||
delete(activeMonitor.trigger_timer)
|
||||
clearInterval(activeMonitor.detector_notrigger_timeout)
|
||||
clearTimeout(activeMonitor.err_fatal_timeout);
|
||||
clearTimeout(activeMonitor.fatalErrorTimeout);
|
||||
activeMonitor.isStarted = false
|
||||
activeMonitor.isRecording = false
|
||||
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'){
|
||||
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
|
||||
if(!fileStream){
|
||||
var currentDate = s.formattedTime(null,'YYYY-MM-DD')
|
||||
|
@ -1219,7 +1221,7 @@ module.exports = (s,config,lang) => {
|
|||
activeMonitor.spawn.stdout.on('data',frameToStreamPrimary)
|
||||
}
|
||||
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({
|
||||
ke: groupKey,
|
||||
mid: monitorId,
|
||||
|
@ -1267,9 +1269,6 @@ module.exports = (s,config,lang) => {
|
|||
s.debugLog('Queue Automatic Compression',response.insertQuery)
|
||||
reEncodeVideoAndBinOriginalAddToQueue({
|
||||
video: response.insertQuery,
|
||||
targetVideoCodec: 'vp9',
|
||||
targetAudioCodec: 'libopus',
|
||||
targetQuality: '-q:v 1 -q:a 1',
|
||||
targetExtension: 'webm',
|
||||
doSlowly: false,
|
||||
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){
|
||||
var checkLog = function(d,x){return d.indexOf(x)>-1}
|
||||
const groupKey = e.ke
|
||||
|
@ -1323,30 +1335,18 @@ module.exports = (s,config,lang) => {
|
|||
break;
|
||||
case checkLog(d,'Connection refused'):
|
||||
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'):
|
||||
await cameraDestroy(e)
|
||||
activeMonitor.timeoutToRestart = setTimeout(() => {
|
||||
launchMonitorProcesses(e)
|
||||
},15000)
|
||||
break;
|
||||
case checkLog(d,'mjpeg_decode_dc'):
|
||||
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'):
|
||||
await cameraDestroy(e)
|
||||
activeMonitor.timeoutToRestart = setTimeout(() => {
|
||||
launchMonitorProcesses(e)
|
||||
},15000)
|
||||
// activeMonitor.timeoutToRestart = setTimeout(() => {
|
||||
// doFatalErrorCatch(e,d)
|
||||
// },15000)
|
||||
break;
|
||||
case checkLog(d,'No route to host'):
|
||||
await cameraDestroy(e)
|
||||
activeMonitor.timeoutToRestart = setTimeout(() => {
|
||||
launchMonitorProcesses(e)
|
||||
activeMonitor.timeoutToRestart = setTimeout(async () => {
|
||||
doFatalErrorCatch(e,d)
|
||||
},60000)
|
||||
break;
|
||||
}
|
||||
|
@ -1425,6 +1425,7 @@ module.exports = (s,config,lang) => {
|
|||
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 startMonitorInQueue = theGroup.startMonitorInQueue
|
||||
if(!activeMonitor.isStarted)return;
|
||||
// e = monitor object
|
||||
clearTimeout(activeMonitor.resetFatalErrorCountTimer)
|
||||
activeMonitor.resetFatalErrorCountTimer = setTimeout(()=>{
|
||||
|
@ -1601,17 +1602,18 @@ module.exports = (s,config,lang) => {
|
|||
const groupKey = e.ke
|
||||
const monitorId = e.mid || e.id
|
||||
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);
|
||||
clearTimeout(activeMonitor.err_fatal_timeout);
|
||||
clearTimeout(activeMonitor.fatalErrorTimeout);
|
||||
++activeMonitor.errorFatalCount;
|
||||
if(activeMonitor.isStarted === true){
|
||||
activeMonitor.err_fatal_timeout = setTimeout(function(){
|
||||
activeMonitor.fatalErrorTimeout = setTimeout(function(){
|
||||
if(maxCount !== 0 && activeMonitor.errorFatalCount > maxCount){
|
||||
s.userLog(e,{type:lang["Fatal Error"],msg:lang.onFatalErrorExit});
|
||||
s.camera('stop',{mid:monitorId,ke:groupKey})
|
||||
}else{
|
||||
launchMonitorProcesses(s.cleanMonitorObject(e))
|
||||
launchMonitorProcesses(monitorConfig)
|
||||
};
|
||||
},5000);
|
||||
}else{
|
||||
|
@ -1623,7 +1625,6 @@ module.exports = (s,config,lang) => {
|
|||
status: lang.Died,
|
||||
code: 7
|
||||
});
|
||||
const monitorConfig = copyMonitorConfiguration(groupKey,monitorId)
|
||||
s.onMonitorDiedExtensions.forEach(function(extender){
|
||||
extender(monitorConfig,e)
|
||||
})
|
||||
|
@ -1753,6 +1754,7 @@ module.exports = (s,config,lang) => {
|
|||
copyMonitorConfiguration,
|
||||
getMonitorConfiguration,
|
||||
isGroupBelowMaxMonitorCount,
|
||||
setNoEventsDetector,
|
||||
cameraDestroy: cameraDestroy,
|
||||
createSnapshot: createSnapshot,
|
||||
processKill: processKill,
|
||||
|
|
|
@ -24,4 +24,5 @@ module.exports = function(s,config,lang){
|
|||
require('./notifications/pushover.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/webhook.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/mqtt.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/matrix.js')(s,config,lang,getSnapshot)
|
||||
}
|
||||
|
|
|
@ -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.')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -323,13 +323,14 @@ module.exports = (s,config,lang) => {
|
|||
function reEncodeVideoAndBinOriginalAddToQueue(data){
|
||||
const groupKey = data.video.ke
|
||||
if(!reEncodeVideoAndBinOriginalQueue[groupKey]){
|
||||
reEncodeVideoAndBinOriginalQueue[groupKey] = async.queue(async function(data, callback) {
|
||||
const response = await reEncodeVideoAndBinOriginal(data)
|
||||
callback(response)
|
||||
reEncodeVideoAndBinOriginalQueue[groupKey] = async.queue(function(data, callback) {
|
||||
reEncodeVideoAndBinOriginal(data).then((response) => {
|
||||
callback(response)
|
||||
})
|
||||
}, 1);
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
reEncodeVideoAndBinOriginalQueue[groupKey].push(data,(response) => {
|
||||
reEncodeVideoAndBinOriginalQueue[groupKey].push(data, function(response){
|
||||
resolve(response)
|
||||
})
|
||||
})
|
||||
|
@ -348,7 +349,7 @@ module.exports = (s,config,lang) => {
|
|||
targetAudioCodec = targetAudioCodec || `copy`
|
||||
targetQuality = targetQuality || ``
|
||||
onPercentChange = onPercentChange || function(){};
|
||||
if(!targetVideoCodec || !targetAudioCodec){
|
||||
if(!targetVideoCodec || !targetAudioCodec || !targetQuality){
|
||||
switch(targetExtension){
|
||||
case'mp4':
|
||||
targetVideoCodec = `libx264`
|
||||
|
@ -359,7 +360,7 @@ module.exports = (s,config,lang) => {
|
|||
case'mkv':
|
||||
targetVideoCodec = `vp9`
|
||||
targetAudioCodec = `libopus`
|
||||
targetQuality = `-q:v 1 -q:a 1`
|
||||
targetQuality = `-q:v 1 -b:a 96K`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -427,6 +428,8 @@ module.exports = (s,config,lang) => {
|
|||
},'GRP_'+groupKey);
|
||||
onPercentChange(percent)
|
||||
s.debugLog('stderr',outputFilePath,`${percent}%`)
|
||||
}else{
|
||||
s.debugLog('stderr',lang['Compression Info'],text)
|
||||
}
|
||||
})
|
||||
videoBuildProcess.on('exit',async function(data){
|
||||
|
|
|
@ -790,8 +790,9 @@ module.exports = function(s,config,lang,app,io){
|
|||
r[n].status = activeMonitor.monitorStatus
|
||||
r[n].code = activeMonitor.monitorStatusCode
|
||||
r[n].subStreamChannel = activeMonitor.subStreamChannel
|
||||
r[n].subStreamActive = !!activeMonitor.subStreamProcess
|
||||
}
|
||||
var buildStreamURL = function(type,channelNumber){
|
||||
function getStreamUrl(type,channelNumber){
|
||||
var streamURL
|
||||
if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''}
|
||||
switch(type){
|
||||
|
@ -810,7 +811,22 @@ module.exports = function(s,config,lang,app,io){
|
|||
case'mp4':
|
||||
streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.mp4'
|
||||
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(!r[n].streamsSortedByType[type]){
|
||||
r[n].streamsSortedByType[type]=[]
|
||||
|
@ -1374,6 +1390,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
dataPipe.pipe(res)
|
||||
}).catch((err) => {
|
||||
console.error('onGetVideoData ERROR',err,videoDetails)
|
||||
res.status(404)
|
||||
res.end(user.lang['File Not Found in Database'])
|
||||
})
|
||||
}else{
|
||||
|
@ -1383,6 +1400,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
}
|
||||
}else{
|
||||
res.status(404)
|
||||
res.end(user.lang['File Not Found in Database'])
|
||||
}
|
||||
})
|
||||
|
@ -1463,6 +1481,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(videoRow){
|
||||
sendVideo(videoRow)
|
||||
}else{
|
||||
res.status(404)
|
||||
res.end(user.lang['File Not Found in Database'])
|
||||
}
|
||||
})
|
||||
|
@ -1829,9 +1848,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
response.ok = true
|
||||
reEncodeVideoAndBinOriginalAddToQueue({
|
||||
video: r,
|
||||
targetVideoCodec: 'vp9',
|
||||
targetAudioCodec: 'libopus',
|
||||
targetQuality: '-q:v 1 -q:a 1',
|
||||
targetExtension: 'webm',
|
||||
doSlowly: false
|
||||
}).then((encodeResponse) => {
|
||||
|
|
|
@ -292,36 +292,40 @@ module.exports = function(s,config,lang,app){
|
|||
//found address already exists
|
||||
endData.msg = lang['Email address is in use.'];
|
||||
}else{
|
||||
endData.ok = true
|
||||
//create new
|
||||
//user id
|
||||
form.uid = s.gid()
|
||||
//check to see if custom key set
|
||||
if(!form.ke||form.ke===''){
|
||||
form.ke=s.gid()
|
||||
if(!form.ke){
|
||||
form.ke = s.gid()
|
||||
}else{
|
||||
form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
|
||||
form.ke = form.ke.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '').trim()
|
||||
}
|
||||
//check if "details" is object
|
||||
if(form.details instanceof Object){
|
||||
form.details = JSON.stringify(form.details)
|
||||
}
|
||||
//write user to db
|
||||
s.knexQuery({
|
||||
action: "insert",
|
||||
table: "Users",
|
||||
insert: {
|
||||
ke: form.ke,
|
||||
uid: form.uid,
|
||||
mail: form.mail,
|
||||
pass: s.createHash(form.pass),
|
||||
details: form.details
|
||||
if(!s.group[form.ke]){
|
||||
endData.ok = true
|
||||
//check if "details" is object
|
||||
if(form.details instanceof Object){
|
||||
form.details = JSON.stringify(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)
|
||||
//write user to db
|
||||
s.knexQuery({
|
||||
action: "insert",
|
||||
table: "Users",
|
||||
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()
|
||||
})
|
||||
|
|
|
@ -35,9 +35,36 @@ function saveAccountSettings(){
|
|||
postData.account = $.aN.selected
|
||||
}
|
||||
$.post(superApiPrefix + $user.sessionKey+'/accounts/'+webPath,postData,function(data){
|
||||
console.log(data)
|
||||
if(data.ok === true){
|
||||
$.aN.e.modal('hide')
|
||||
if(webPath === 'editAdmin'){
|
||||
if(data.ok === true){
|
||||
$.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()
|
||||
$.each(loadedUsers,function(n,user){
|
||||
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()
|
||||
$.each(loadedUsers,function(n,user){
|
||||
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'
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -54,8 +54,7 @@
|
|||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link logout">
|
||||
<i class="fa fa-sign-out-alt"></i>
|
||||
<p class="d-lg-none d-xl-none"><%-lang.Logout%></p>
|
||||
<p><%-lang.Logout%></p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -244,14 +243,12 @@ $.ccio.ws.on('f',function(d){
|
|||
d.msg = 'Saved Preferences'
|
||||
break;
|
||||
case'edit_account':
|
||||
d.msg='Account Edited';
|
||||
$.each(d.form,function(n,v){
|
||||
$.ccio.accounts[d.ke][n]=v
|
||||
})
|
||||
$('[ke="'+d.ke+'"] .mail').text(d.form.mail)
|
||||
break;
|
||||
case'add_account':
|
||||
d.msg='Account Created';
|
||||
$.ccio.tm(0,d,'#accounts-list')
|
||||
$.aN.selected = $.ccio.accounts[d.ke]
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue