diff --git a/libs/notification.js b/libs/notification.js index 3db2b604..b4afd440 100644 --- a/libs/notification.js +++ b/libs/notification.js @@ -14,4 +14,5 @@ module.exports = function(s,config,lang){ require('./notifications/discordBot.js')(s,config,lang,getSnapshot) require('./notifications/telegram.js')(s,config,lang,getSnapshot) require('./notifications/pushover.js')(s,config,lang,getSnapshot) + require('./notifications/webhook.js')(s,config,lang,getSnapshot) } diff --git a/libs/notifications/webhook.js b/libs/notifications/webhook.js new file mode 100644 index 00000000..935bbaeb --- /dev/null +++ b/libs/notifications/webhook.js @@ -0,0 +1,270 @@ +const fetch = require('node-fetch'); +const FormData = require('form-data'); +module.exports = function(s,config,lang,getSnapshot){ + function replaceQueryStringValues(webhookEndpoint,data){ + let newString = webhookEndpoint + .replace(/{{INNER_EVENT_TITLE}}/g,data.title) + .replace(/{{INNER_EVENT_INFO}}/g,s.stringJSON(data.info)); + return newString; + } + const sendMessage = function(sendBody,files,groupKey){ + let webhookEndpoint = s.group[groupKey].init.global_webhook_url; + if(!webhookEndpoint){ + s.userLog({ + ke: groupKey, + mid: '$USER' + },{ + type: lang.NotifyErrorText, + infoType: 'global_webhook', + msg: lang['Invalid Settings'] + }) + return new Promise((resolve,reject) => { + resolve({ + error: lang['Invalid Settings'], + ok: false, + }) + }) + } + const doPostMethod = s.group[groupKey].init.global_webhook_method === 'post'; + // const includeSnapshot = s.group[groupKey].init.global_webhook_include_image === '1'; + const webhookInfoData = { + info: sendBody, + files: [], + } + if(files){ + const formData = new FormData(); + files.forEach(async (file,n) => { + switch(file.type){ + case'video': + // video cannot be sent this way unless POST + if(doPostMethod){ + const fileName = file.name + formData.append(`file${n + 1}`, file.attachment, { + contentType: 'video/mp4', + name: fileName, + filename: fileName, + }); + webhookInfoData.files.push(fileName) + } + break; + case'photo': + if(doPostMethod){ + const fileName = file.name + formData.append(`file${n + 1}`, file.attachment, { + contentType: 'image/jpeg', + name: fileName, + filename: fileName, + }); + webhookInfoData.files.push(fileName) + }else{ + const base64StringofImage = file.attachment.toString('base64') + webhookInfoData.files.push(base64StringofImage) + } + break; + } + }) + }else{ + delete(webhookInfoData.files) + } + webhookEndpoint = replaceQueryStringValues(webhookEndpoint,{ + title: sendBody.title, + info: webhookInfoData, + }); + return new Promise((resolve,reject) => { + const response = { + ok: true, + } + fetch(webhookEndpoint,doPostMethod ? { + method: 'POST', + body: formData + } : undefined) + .then(res => res.text()) + .then((text) => { + response.response = text; + resolve(response) + }) + .catch((err) => { + response.ok = false; + response.error = err; + s.userLog({ + ke: groupKey, + mid: '$USER' + },{ + type: lang.NotifyErrorText, + infoType: 'global_webhook', + msg: err + }) + resolve(response) + }) + }) + } + const onEventTriggerBeforeFilterForGlobalWebhook = function(d,filter){ + filter.global_webhook = true + } + const onEventTriggerForGlobalWebhook = async (d,filter) => { + let filesSent = 0; + const monitorConfig = s.group[d.ke].rawMonitorConfigurations[d.id] + // d = event object + if(filter.global_webhook && monitorConfig.details.notify_global_webhook === '1' && !s.group[d.ke].activeMonitors[d.id].detector_global_webhook){ + var detector_global_webhook_timeout + if(!monitorConfig.details.detector_global_webhook_timeout||monitorConfig.details.detector_global_webhook_timeout===''){ + detector_global_webhook_timeout = 1000 * 60 * 10; + }else{ + detector_global_webhook_timeout = parseFloat(monitorConfig.details.detector_global_webhook_timeout) * 1000 * 60; + } + s.group[d.ke].activeMonitors[d.id].detector_global_webhook = setTimeout(function(){ + clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_global_webhook); + s.group[d.ke].activeMonitors[d.id].detector_global_webhook = null + },detector_global_webhook_timeout) + await getSnapshot(d,monitorConfig) + if(d.screenshotBuffer){ + sendMessage({ + title: lang.Event+' - '+d.screenshotName, + description: lang.EventText1+' '+d.currentTimestamp, + },[ + { + type: 'photo', + attachment: d.screenshotBuffer, + name: d.screenshotName+'.jpg' + } + ],d.ke) + ++filesSent; + } + if(filesSent === 0){ + sendMessage({ + title: lang.Event, + description: lang.EventText1+' '+d.currentTimestamp, + eventDetails: d.details + },[],d.ke) + } + } + } + const onTwoFactorAuthCodeNotificationForGlobalWebhook = function(r){ + // r = user + if(r.details.factor_global_webhook === '1'){ + sendMessage({ + title: r.lang['Enter this code to proceed'], + description: '**'+s.factorAuth[r.ke][r.uid].key+'** '+r.lang.FactorAuthText1, + },[],r.ke) + } + } + // const onDetectorNoTriggerTimeoutForGlobalWebhook = function(e){ + // //e = monitor object + // var currentTime = new Date() + // if(e.details.detector_notrigger_global_webhook === '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 + // sendMessage({ + // title: lang['\"No Motion"\ Detector'], + // description: html, + // },[],e.ke) + // } + // } + const onMonitorUnexpectedExitForGlobalWebhook = (monitorConfig) => { + if(monitorConfig.details.notify_global_webhook === '1' && 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() + sendMessage({ + title: lang['Process Unexpected Exit'] + ' : ' + monitorConfig.name, + description: description, + },[],monitorConfig.ke) + } + } + s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForGlobalWebhook) + s.onEventTrigger(onEventTriggerForGlobalWebhook) + s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForGlobalWebhook) + // s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForGlobalWebhook) + s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForGlobalWebhook) + s.definitions["Monitor Settings"].blocks["Notifications"].info[0].info.push( + { + "name": "detail=notify_global_webhook", + "field": lang.Webhook, + "description": "", + "default": "0", + "example": "", + "selector": "h_det_global_webhook", + "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_global_webhook", + "field": lang.Webhook, + "default": "1", + "example": "", + "fieldType": "select", + "possible": [ + { + "name": lang.No, + "value": "0" + }, + { + "name": lang.Yes, + "value": "1" + } + ] + }) + s.definitions["Account Settings"].blocks["Webhook"] = { + "evaluation": "$user.details.use_global_webhook !== '0'", + "name": lang.Webhook, + "color": "blue", + info: [ + { + "name": "detail=global_webhook", + "selector":"u_global_webhook", + "field": lang.Enabled, + "default": "0", + "example": "", + "fieldType": "select", + "possible": [ + { + "name": lang.No, + "value": "0" + }, + { + "name": lang.Yes, + "value": "1" + } + ] + }, + { + hidden: true, + "name": "detail=global_webhook_url", + "placeholder": "http://your-webhook-point/onEvent/{{INNER_EVENT_TITLE}}?info={{INNER_EVENT_INFO}}", + "field": lang["Webhook URL"], + "form-group-class":"u_global_webhook_input u_global_webhook_1", + }, + { + hidden: true, + "name": "detail=factor_global_webhook", + "field": lang["2-Factor Authentication"], + "form-group-class":"u_global_webhook_input u_global_webhook_1", + "default": "1", + "example": "", + "fieldType": "select", + "possible": [ + { + "name": lang.No, + "value": "0" + }, + { + "name": lang.Yes, + "value": "1" + } + ] + } + ] + } +}