WEBHOOK conversion + cleanup work🎣

This commit is contained in:
Jokob-sk
2023-10-22 09:29:25 +11:00
parent fdd199935a
commit 55e398dd10
17 changed files with 653 additions and 128 deletions

View File

@@ -0,0 +1,8 @@
## Overview
A plugin to publish a notification via the Webhook gateway. Webhooks help you to connect to a lot of 3rd party tools, such as IFTTT, Zapier or <a href="https://n8n.io/" target="_blank">n8n</a> to name a few. Check out this simple <a href="https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md" target="_blank">n8n guide here</a> to get started. If enabled, configure related settings below.
### Usage
- Go to settings and fill in relevant details.

View File

@@ -0,0 +1,404 @@
{
"code_name": "_publisher_webhook",
"unique_prefix": "WEBHOOK",
"plugin_type": "publisher",
"enabled": true,
"data_source": "script",
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"display_name" : [
{
"language_code": "en_us",
"string" : "Webhook publisher"
},
{
"language_code": "es_es",
"string" : "Habilitar Webhook"
}
],
"icon":[{
"language_code": "en_us",
"string" : "<i class=\"fa-solid fa-circle-nodes\"></i>"
}],
"description": [
{
"language_code": "en_us",
"string" : "A plugin to publish a notification via Webhooks."
}
],
"params" : [
],
"database_column_definitions":
[
{
"column": "Index",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "N/A"
},
{
"language_code": "es_es",
"string" : "N/A"
}]
},
{
"column": "Plugin",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "N/A"
},
{
"language_code": "es_es",
"string" : "N/A"
}]
},
{
"column": "Object_PrimaryID",
"css_classes": "col-sm-2",
"show": false,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "N/A"
}]
},
{
"column": "Object_SecondaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Sent when"
}]
},
{
"column": "Watched_Value1",
"css_classes": "col-sm-3",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Notification GUID"
}]
},
{
"column": "Watched_Value2",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Response (stdout)"
}]
},
{
"column": "Watched_Value3",
"css_classes": "col-sm-2",
"show": true,
"type": "textarea_readonly",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Response (stderr)"
}]
},
{
"column": "Watched_Value4",
"css_classes": "col-sm-2",
"show": false,
"type": "device_mac",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Device"
}
]
},
{
"column": "UserData",
"css_classes": "col-sm-2",
"show": false,
"type": "textbox_save",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Comments"
},
{
"language_code": "es_es",
"string" : "Comentarios"
}]
},
{
"column": "Status",
"css_classes": "col-sm-1",
"show": false,
"type": "replace",
"default_value":"",
"options": [
{
"equals": "watched-not-changed",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-square-check'></i><div></div>"
},
{
"equals": "watched-changed",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-triangle-exclamation'></i></div>"
},
{
"equals": "new",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-circle-plus'></i></div>"
},
{
"equals": "missing-in-last-scan",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
}
],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Status"
},
{
"language_code": "es_es",
"string" : "Estado"
}]
},
{
"column": "Extra",
"css_classes": "col-sm-3",
"show": false,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code": "en_us",
"string" : "Extra"
},
{
"language_code": "es_es",
"string" : "Extra"
}]
}
],
"settings":[
{
"function": "RUN",
"events": ["test"],
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "on_notification" ],
"localized": ["name", "description"],
"name" :[{
"language_code": "en_us",
"string" : "When to run"
},
{
"language_code": "es_es",
"string" : "Cuando ejecuta"
}],
"description": [
{
"language_code": "en_us",
"string" : "Enable webhooks for notifications. Webhooks help you to connect to a lot of 3rd party tools, such as IFTTT, Zapier or <a href=\"https://n8n.io/\" target=\"_blank\">n8n</a> to name a few. Check out this simple <a href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md\" target=\"_blank\">n8n guide here</a> to get started. If enabled, configure related settings below."
},
{
"language_code": "es_es",
"string" : "Habilite webhooks para notificaciones. Los webhooks lo ayudan a conectarse a muchas herramientas de terceros, como IFTTT, Zapier o <a href=\"https://n8n.io/\" target=\"_blank\">n8n</a>, por nombrar algunas. Consulte esta sencilla <a href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_N8N.md\" target=\"_blank\">guía de n8n aquí</a> para obtener comenzó. Si está habilitado, configure los ajustes relacionados a continuación."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value":"python3 /home/pi/pialert/front/plugins/_publisher_webhook/webhook.py",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Command"
},
{
"language_code": "es_es",
"string" : "Comando"
}],
"description": [{
"language_code": "en_us",
"string" : "Command to run"
},
{
"language_code": "es_es",
"string" : "Comando a ejecutar"
}]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value": 10,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Run timeout"
},
{
"language_code": "es_es",
"string" : "Tiempo de espera de ejecución"
},
{
"language_code": "de_de",
"string" : "Wartezeit"
}],
"description": [{
"language_code": "en_us",
"string" : "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
},
{
"language_code": "es_es",
"string" : "Tiempo máximo en segundos para esperar a que finalice el script. Si se supera este tiempo, el script se cancela."
}]
},
{
"function": "URL",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Target URL"
},
{
"language_code": "es_es",
"string" : "URL de destino"
}],
"description": [{
"language_code": "en_us",
"string" : "Target URL starting with <code>http://</code> or <code>https://</code>."
},
{
"language_code": "es_es",
"string" : "URL de destino comienza con <code>http://</code> o <code>https://</code>."
}]
},
{
"function": "PAYLOAD",
"type": "text.select",
"default_value": "json",
"options": ["json", "html", "text"],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Payload type"
},
{
"language_code": "es_es",
"string" : "Tipo de carga"
}],
"description": [{
"language_code": "en_us",
"string" : "The Webhook payload data format for the <code>body</code> > <code>attachments</code> > <code>text</code> attribute in the payload json. See an example of the payload <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/back/webhook_json_sample.json\">here</a>. (e.g.: for discord use <code>text</code>)"
},
{
"language_code": "es_es",
"string" : "El formato de datos de carga de Webhook para el atributo <code>body</code> > <code>attachments</code> > <code>text</code> en el json de carga. Vea un ejemplo de la carga <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/back/webhook_json_sample.json\">aquí</a>. (por ejemplo: para discord use <code>text</code>)"
}]
},
{
"function": "REQUEST_METHOD",
"type": "text.select",
"default_value": "GET",
"options": ["GET", "POST", "PUT"],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Request method"
},
{
"language_code": "es_es",
"string" : "Método de solicitud"
}],
"description": [{
"language_code": "en_us",
"string" : "The HTTP request method to be used for the webhook call."
},
{
"language_code": "es_es",
"string" : "El método de solicitud HTTP que se utilizará para la llamada de webhook."
}]
},
{
"function": "SIZE",
"type": "integer",
"default_value": 1024,
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "Max payload size"
},
{
"language_code": "es_es",
"string" : "Tamaño máximo de carga útil"
}],
"description": [{
"language_code": "en_us",
"string" : "The maximum size of the webhook payload as number of characters in the passed string. If above limit, it will be truncated and a <code>(text was truncated)</code> message is appended."
},
{
"language_code": "es_es",
"string" : "El tamaño máximo de la carga útil del webhook como número de caracteres en la cadena pasada. Si supera el límite, se truncará y se agregará un mensaje <code>(text was truncated)</code>."
}]
},
{
"function": "SECRET",
"type": "text",
"default_value": "",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code": "en_us",
"string" : "HMAC Secret"
},
{
"language_code": "es_es",
"string" : ""
}],
"description": [{
"language_code": "en_us",
"string" : "When set, use this secret to generate the SHA256-HMAC hex digest value of the request body, which will be passed as the <code>X-Webhook-Signature</code> header to the request. You can find more information <a target=\"_blank\" href=\"https://github.com/jokob-sk/Pi.Alert/blob/main/docs/WEBHOOK_SECRET.md\">here</a>."
}]
}
]
}

View File

@@ -1 +0,0 @@
This plugin will not be loaded

View File

@@ -0,0 +1,190 @@
#!/usr/bin/env python
import json
import subprocess
import argparse
import os
import pathlib
import sys
import requests
from datetime import datetime
from base64 import b64encode
import hashlib
import hmac
# Replace these paths with the actual paths to your Pi.Alert directories
sys.path.extend(["/home/pi/pialert/front/plugins", "/home/pi/pialert/pialert"])
# pialert modules
import conf
from const import logPath
from plugin_helper import Plugin_Objects, handleEmpty
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value, hide_string, write_file
from notification import Notification_obj
from database import DB
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'WEBHOOK'
def main():
mylog('verbose', [f'[{pluginName}](publisher) In script'])
# Check if basic config settings supplied
if check_config() == False:
mylog('none', [f'[{pluginName}] Error: Publisher notification gateway not set up correctly. Check your pialert.conf {pluginName}_* variables.'])
return
# Create a database connection
db = DB() # instance of class DB
db.open()
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
# Create a Notification_obj instance
notifications = Notification_obj(db)
# Retrieve new notifications
new_notifications = notifications.getNew()
# Process the new notifications (see the Notifications DB table for structure or check the /api/table_notifications.json endpoint)
for notification in new_notifications:
# Send notification
response_stdout, response_stderr = send(notification["Text"], notification["HTML"], notification["JSON"])
# Log result
plugin_objects.add_object(
primaryId = pluginName,
secondaryId = timeNowTZ(),
watched1 = notification["GUID"],
watched2 = handleEmpty(response_stdout),
watched3 = handleEmpty(response_stderr),
watched4 = 'null',
extra = 'null',
foreignKey = notification["GUID"]
)
plugin_objects.write_result_file()
#-------------------------------------------------------------------------------
def check_config():
if get_setting_value('WEBHOOK_URL') == '':
return False
else:
return True
#-------------------------------------------------------------------------------
def send (text_data, html_data, json_data):
response_stderr = ''
response_stdout = ''
# limit = 1024 * 1024 # 1MB limit (1024 bytes * 1024 bytes = 1MB)
limit = get_setting_value('WEBHOOK_SIZE')
payloadType = get_setting_value('WEBHOOK_PAYLOAD')
endpointUrl = get_setting_value('WEBHOOK_URL')
secret = get_setting_value('WEBHOOK_SECRET')
requestMethod = get_setting_value('WEBHOOK_REQUEST_METHOD')
# use data type based on specified payload type
if payloadType == 'json':
# In this code, the truncate_json function is used to recursively traverse the JSON object
# and remove nodes that exceed the size limit. It checks the size of each node's JSON representation
# using json.dumps and includes only the nodes that are within the limit.
json_str = json.dumps(json_data)
if len(json_str) <= limit:
payloadData = json_data
else:
def truncate_json(obj):
if isinstance(obj, dict):
return {
key: truncate_json(value)
for key, value in obj.items()
if len(json.dumps(value)) <= limit
}
elif isinstance(obj, list):
return [
truncate_json(item)
for item in obj
if len(json.dumps(item)) <= limit
]
else:
return obj
payloadData = truncate_json(json_data)
if payloadType == 'html':
if len(html_data) > limit:
payloadData = html_data[:limit] + " <h1>(text was truncated)</h1>"
else:
payloadData = html_data
if payloadType == 'text':
if len(text_data) > limit:
payloadData = text_data[:limit] + " (text was truncated)"
else:
payloadData = text_data
# Define slack-compatible payload
_json_payload = { "text": payloadData } if payloadType == 'text' else {
"username": "Pi.Alert",
"text": "There are new notifications",
"attachments": [{
"title": "Pi.Alert Notifications",
"title_link": get_setting_value('REPORT_DASHBOARD_URL'),
"text": payloadData
}]
}
# DEBUG - Write the json payload into a log file for debugging
write_file (logPath + '/webhook_payload.json', json.dumps(_json_payload))
# Using the Slack-Compatible Webhook endpoint for Discord so that the same payload can be used for both
# Consider: curl has the ability to load in data to POST from a file + piping
if(endpointUrl.startswith('https://discord.com/api/webhooks/') and not endpointUrl.endswith("/slack")):
_WEBHOOK_URL = f"{endpointUrl}/slack"
curlParams = ["curl","-i","-H", "Content-Type:application/json" ,"-d", json.dumps(_json_payload), _WEBHOOK_URL]
else:
_WEBHOOK_URL = endpointUrl
curlParams = ["curl","-i","-X", requestMethod , "-H", "Content-Type:application/json", "-d", json.dumps(_json_payload), _WEBHOOK_URL]
# Add HMAC signature if configured
if(secret != ''):
h = hmac.new(secret.encode("UTF-8"), json.dumps(_json_payload, separators=(',', ':')).encode(), hashlib.sha256).hexdigest()
curlParams.insert(4,"-H")
curlParams.insert(5,f"X-Webhook-Signature: sha256={h}")
try:
# Execute CURL call
mylog('debug', [f'[{pluginName}] curlParams: ', curlParams])
result = subprocess.run(curlParams, capture_output=True, text=True)
response_stderr = result.stderr
response_stdout = result.stdout
# Write stdout and stderr into .log files for debugging if needed
mylog('debug', [f'[{pluginName}] stdout: ', response_stdout])
mylog('debug', [f'[{pluginName}] stderr: ', response_stderr])
except subprocess.CalledProcessError as e:
# An error occurred, handle it
mylog('none', [f'[{pluginName}] Error: ', e.output])
response_stderr = e.output
return response_stdout, response_stderr
# -------------------------------------------------------
if __name__ == '__main__':
sys.exit(main())