"
+ },
+ {
+ "equals": "watched-changed",
+ "replacement": "
"
+ },
+ {
+ "equals": "new",
+ "replacement": "
"
+ },
+ {
+ "equals": "missing-in-last-scan",
+ "replacement": "
"
+ }
+ ],
+ "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
n8n to name a few. Check out this simple
n8n guide here 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
n8n, por nombrar algunas. Consulte esta sencilla
guía de n8n aquí 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
http:// or
https://."
+ },
+ {
+ "language_code": "es_es",
+ "string" : "URL de destino comienza con
http:// o
https://."
+ }]
+ },
+ {
+ "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
body >
attachments >
text attribute in the payload json. See an example of the payload
here. (e.g.: for discord use
text)"
+ },
+ {
+ "language_code": "es_es",
+ "string" : "El formato de datos de carga de Webhook para el atributo
body >
attachments >
text en el json de carga. Vea un ejemplo de la carga
aquí. (por ejemplo: para discord use
text)"
+ }]
+ },
+ {
+ "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
(text was truncated) 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
(text was truncated)."
+ }]
+ },
+ {
+ "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
X-Webhook-Signature header to the request. You can find more information
here."
+ }]
+ }
+ ]
+}
diff --git a/front/plugins/_publisher_webhook/ignore_plugin b/front/plugins/_publisher_webhook/ignore_plugin
deleted file mode 100755
index 77ffa1c1..00000000
--- a/front/plugins/_publisher_webhook/ignore_plugin
+++ /dev/null
@@ -1 +0,0 @@
-This plugin will not be loaded
\ No newline at end of file
diff --git a/front/plugins/_publisher_webhook/webhook.py b/front/plugins/_publisher_webhook/webhook.py
new file mode 100755
index 00000000..8b66beee
--- /dev/null
+++ b/front/plugins/_publisher_webhook/webhook.py
@@ -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] + "
(text was truncated)
"
+ 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())
+
diff --git a/pialert/README.md b/pialert/README.md
index 2b0a3858..1a8d6d4a 100755
--- a/pialert/README.md
+++ b/pialert/README.md
@@ -21,18 +21,7 @@ The original pilaert.py code is now moved to this new folder and split into diff
|```reporting.py```| Reporting generates the email, html and json reports to be sent by the publishers |
|```scheduler.py```| All things scheduling |
-## publishers
-publishers generally have a check_config method as well as a send method.
-
-| Module | Description |
-|--------|-----------|
-|```__init__.py```| an empty init file|
-|```apprise.py```| use apprise to integrate to "everywhere" https://github.com/caronc/apprise |
-|```email.py```| Configure and send the reports and notifications via email |
-|```mqtt.py```| integrate with a MQTT broker and even make the devices automatically discoverable in Home-Assistant |
-|```ntfy.py```| integrate with ntfy |
-|```pushsafer.py```| integrate with pushsafer |
-|```webhook.py```| integrate via webhook |
+
diff --git a/pialert/README_ES.md b/pialert/README_ES.md
index 664ce689..b2856f55 100755
--- a/pialert/README_ES.md
+++ b/pialert/README_ES.md
@@ -23,24 +23,4 @@ El código pilaert.py original ahora se mueve a esta nueva carpeta y se divide e
|```reporting.py```| La generación de informes genera los informes de correo electrónico, html y json que deben enviar los editores |
|```scheduler.py```| Todo sobre la planificación |
-## Editores
-Los editores suelen tener un método check_config además de un método send.
-| Módulo | Descripción |
-|--------|-----------|
-|```__init__.py```| Un archivo init vacío|
-|```apprise.py```| Utilice apprise para integrarse en "todas partes" ([Github Apprise](https://github.com/caronc/apprise)). |
-|```email.py```| Configurar y enviar los informes y notificaciones por correo electrónico |
-|```mqtt.py```| Integrar con un broker MQTT e incluso hacer que los dispositivos sean automáticamente detectables en Home-Assistant |
-|```ntfy.py```| Integración con ntfy |
-|```pushsafer.py```| Integrar con pushsafer |
-|```webhook.py```| Integración mediante webhook |
-
-## Escáneres
-Diferentes métodos para escanear la red en busca de dispositivos o para encontrar más detalles sobre los dispositivos descubiertos
-
-| Módulo | Descripción |
-|--------|-----------|
-|```__init__.py```| Un archivo init vacío (oops falta en el repo)|
-|```internet.py```| Descubra la interfaz de Internet y verifique la IP externa y también administre DNS dinámico |
-|```nmapscan.py```| Utilice Nmap para descubrir más sobre los dispositivos |
diff --git a/pialert/conf.py b/pialert/conf.py
index 7d82039b..2e8e95ee 100755
--- a/pialert/conf.py
+++ b/pialert/conf.py
@@ -41,22 +41,6 @@ INCLUDED_SECTIONS = ['new_devices', 'down_devices', 'events']
DAYS_TO_KEEP_EVENTS = 90
REPORT_DASHBOARD_URL = 'http://pi.alert/'
-# -------------------------------------------
-# Notification gateways
-# -------------------------------------------
-
-# Webhooks
-REPORT_WEBHOOK = False
-WEBHOOK_URL = ''
-WEBHOOK_PAYLOAD = 'json'
-WEBHOOK_REQUEST_METHOD = 'GET'
-WEBHOOK_SECRET = ''
-
-
-# PUSHSAFER
-REPORT_PUSHSAFER = False
-PUSHSAFER_TOKEN = 'ApiKey'
-
# -------------------------------------------
# Misc
# -------------------------------------------
diff --git a/pialert/helper.py b/pialert/helper.py
index f56bfd6d..57718e7f 100755
--- a/pialert/helper.py
+++ b/pialert/helper.py
@@ -510,6 +510,14 @@ def hide_email(email):
return email
+#-------------------------------------------------------------------------------
+def hide_string(input_string):
+ if len(input_string) < 3:
+ return input_string # Strings with 2 or fewer characters remain unchanged
+ else:
+ return input_string[0] + "*" * (len(input_string) - 2) + input_string[-1]
+
+
#-------------------------------------------------------------------------------
def removeDuplicateNewLines(text):
if "\n\n\n" in text:
diff --git a/pialert/initialise.py b/pialert/initialise.py
index 1369bfde..4cf4f76f 100755
--- a/pialert/initialise.py
+++ b/pialert/initialise.py
@@ -117,17 +117,6 @@ def importConfigs (db):
# ARPSCAN (+ more settings are provided by the ARPSCAN plugin)
conf.SCAN_SUBNETS = ccd('SCAN_SUBNETS', ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0'] , c_d, 'Subnets to scan', 'subnets', '', 'ARPSCAN')
- # Notification gateways
- # ----------------------------------------
-
- # Webhooks
- conf.REPORT_WEBHOOK = ccd('REPORT_WEBHOOK', False , c_d, 'Enable Webhooks', 'boolean', '', 'Webhooks', ['test'])
- conf.WEBHOOK_URL = ccd('WEBHOOK_URL', '' , c_d, 'Target URL', 'text', '', 'Webhooks')
- conf.WEBHOOK_PAYLOAD = ccd('WEBHOOK_PAYLOAD', 'json' , c_d, 'Payload type', 'text.select', "['json', 'html', 'text']", 'Webhooks')
- conf.WEBHOOK_REQUEST_METHOD = ccd('WEBHOOK_REQUEST_METHOD', 'GET' , c_d, 'Req type', 'text.select', "['GET', 'POST', 'PUT']", 'Webhooks')
- conf.WEBHOOK_SIZE = ccd('WEBHOOK_SIZE', 1024 , c_d, 'Payload size', 'integer', '', 'Webhooks')
- conf.WEBHOOK_SECRET = ccd('WEBHOOK_SECRET', '' , c_d, 'Secret', 'text', '', 'Webhooks')
-
# Init timezone in case it changed
conf.tz = timezone(conf.TIMEZONE)
diff --git a/pialert/publishers/webhook.py b/pialert/publishers/webhook.py
index 7397e714..9c1c4669 100755
--- a/pialert/publishers/webhook.py
+++ b/pialert/publishers/webhook.py
@@ -53,7 +53,7 @@ def send (msg: noti_obj):
payloadData = truncate_json(json_data)
if conf.WEBHOOK_PAYLOAD == 'html':
if len(msg.html) > limit:
- payloadData = msg.html[:limit] + "
(text was truncated)
"
+ payloadData = msg.html[:limit] + "
(text was truncated)
"
else:
payloadData = msg.html
if conf.WEBHOOK_PAYLOAD == 'text':
diff --git a/pialert/reporting.py b/pialert/reporting.py
index 1d2091ba..1dd98c5c 100755
--- a/pialert/reporting.py
+++ b/pialert/reporting.py
@@ -26,8 +26,7 @@ from logger import logResult, mylog, print_log
from publishers.webhook import (check_config as webhook_check_config,
send as send_webhook)
-from publishers.pushsafer import (check_config as pushsafer_check_config,
- send as send_pushsafer)
+
#===============================================================================
@@ -258,21 +257,6 @@ def get_notifications (db):
return noti_obj(final_json, final_text, final_html)
-
- # if conf.REPORT_WEBHOOK and check_config('webhook'):
- # updateState("Send: Webhook")
- # mylog('minimal', ['[Notification] Sending report by Webhook'])
- # send_webhook (msg)
- # else :
- # mylog('verbose', ['[Notification] Skip webhook'])
-
- # if conf.REPORT_PUSHSAFER and check_config('pushsafer'):
- # updateState("Send: PUSHSAFER")
- # mylog('minimal', ['[Notification] Sending report by PUSHSAFER'])
- # send_pushsafer (msg)
- # else :
- # mylog('verbose', ['[Notification] Skip PUSHSAFER'])
-
#-------------------------------------------------------------------------------
# Replacing table headers