From 108be98ec347f9cd1186b3162662c0d997bead95 Mon Sep 17 00:00:00 2001 From: hygienic-books Date: Sat, 8 Mar 2025 18:07:31 +0100 Subject: [PATCH] feat(script): Add script --- .gitignore | 2 + notify_signal_about_traccar_events.sh | 97 +++++++++++++++++++ ...ignal_about_traccar_events.sh.conf.example | 39 ++++++++ 3 files changed, 138 insertions(+) create mode 100644 .gitignore create mode 100755 notify_signal_about_traccar_events.sh create mode 100644 notify_signal_about_traccar_events.sh.conf.example diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59314b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +*.conf \ No newline at end of file diff --git a/notify_signal_about_traccar_events.sh b/notify_signal_about_traccar_events.sh new file mode 100755 index 0000000..567c2ab --- /dev/null +++ b/notify_signal_about_traccar_events.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# shellcheck disable=SC2059 + +declare message_geofence_enter message_geofence_exit message_device_online message_device_unknown message_device_offline message_event_other signal_sender_phone_number signal_recipients_array signal_api_endpoint_full_path allow_unique_device_ids_posix_ere device_name event_type battery_level message + +payload="${1}" + +function load_config() { + local this_script_call_cmd this_script_resolved_abs_path conf_file_resolved_abs_path conf_file_resolved_file_name + this_script_call_cmd="${BASH_SOURCE[0]}" + this_script_resolved_abs_path="$(readlink -f "${this_script_call_cmd}")" + conf_file_resolved_abs_path="${this_script_resolved_abs_path}"'.conf' + conf_file_resolved_file_name="$(basename "${conf_file_resolved_abs_path}")" + if [[ -r "${conf_file_resolved_abs_path}" ]]; then + # shellcheck source="${conf_file_resolved_abs_path}" + source "${conf_file_resolved_abs_path}" + fi + # Optional, will use default values if not given by user + : "${message_geofence_enter:='%s entered geofence %s.'}" + : "${message_geofence_exit:='%s left geofence %s.'}" + : "${message_device_online:='On %s Traccar Client app is now active.'}" + : "${message_device_unknown:='On %s Traccar Client app has not checked in for 10 minutes, the app is likely off now.'}" + : "${message_device_offline:='On %s Traccar Client app is now off.'}" + : "${message_device_unhandled:='For %s we do not want to send a message to Signal. Nothing to do.\n'}" + : "${message_event_other:='%s triggered event "%s" in Traccar server.'}" + : "${allow_unique_device_ids_posix_ere:=''}" + + # Mandatory, user must specify + : "${signal_sender_phone_number:?'Please specify the signal-cli-rest-api phone number of your sender Signal account. Place a file named '"'${conf_file_resolved_file_name}'"' next to this script (same dir) and define a variable signal_sender_phone_number='"'"'value'"'"' in it.'}" + : "${signal_recipients_array:?'Please specify recipients like so: ["group.ImxvbmdpdH=","recipient.21","+18005550111","i-am-a-uuid"]. Place a file named '"'${conf_file_resolved_file_name}'"' next to this script (same dir) and define a variable signal_recipients_array='"'"'value'"'"' in it.'}" + : "${signal_api_endpoint_full_path:?'Please specify the signal-cli-rest-api full URL for sending a message like so: https://fully.qualified.domain.name/v2/send. Place a file named '"'${conf_file_resolved_file_name}'"' next to this script (same dir) and define a variable signal_api_endpoint_full_path='"'"'value'"'"' in it.'}" +} + +function get_event_data() { + local device_unique_id + device_unique_id="$(<<<"${payload}" jq --raw-output '.device.uniqueId')" + + device_name="$(<<<"${payload}" jq --raw-output '.device.name')" + event_type="$(<<<"${payload}" jq --raw-output '.event.type')" + if [[ "${event_type}" = 'geofenceEnter' || "${event_type}" = 'geofenceExit' ]]; then + battery_level="$(<<<"${payload}" jq --raw-output '.position.attributes.batteryLevel')" + battery_level="$(echo "${battery_level}"' / 1' | bc)" + fi + + if ! [[ "${device_unique_id}" =~ ${allow_unique_device_ids_posix_ere} ]]; then + printf -- "${message_device_unhandled}" "${device_name}" + exit 0 + fi +} + +function craft_message() { + local geofence_name + case "${event_type}" in + 'geofenceEnter') + geofence_name="$(<<<"${payload}" jq --raw-output '.geofence.name')" + message="${message_geofence_enter}" + message="$(printf -- "${message}" "${device_name}" "${geofence_name}" "${battery_level}")" + ;; + 'geofenceExit') + geofence_name="$(<<<"${payload}" jq --raw-output '.geofence.name')" + message="${message_geofence_exit}" + message="$(printf -- "${message}" "${device_name}" "${geofence_name}" "${battery_level}")" + ;; + 'deviceOnline') + message="${message_device_online}" + message="$(printf -- "${message}" "${device_name}")" + ;; + 'deviceUnknown') + message="${message_device_unknown}" + message="$(printf -- "${message}" "${device_name}")" + ;; + 'deviceOffline') + message="${message_device_offline}" + message="$(printf -- "${message}" "${device_name}")" + ;; + *) + message="${message_event_other}" + message="$(printf -- "${message}" "${device_name}" "${event_type}")" + ;; + esac +} + +function send_to_signal() { + local signal_message + signal_message="${1}" + echo "${signal_message}" + curl \ + --request 'POST' \ + --header 'Content-Type: application/json' \ + --data '{"message":"'"${signal_message}"'","number":"'"${signal_sender_phone_number}"'","recipients":'"${signal_recipients_array}"'}' \ + "${signal_api_endpoint_full_path}" +} + +load_config +get_event_data +craft_message +send_to_signal "${message}" diff --git a/notify_signal_about_traccar_events.sh.conf.example b/notify_signal_about_traccar_events.sh.conf.example new file mode 100644 index 0000000..a05e698 --- /dev/null +++ b/notify_signal_about_traccar_events.sh.conf.example @@ -0,0 +1,39 @@ +# First positional argument '%s' is Traccar device name, second +# positional argument '%s' is geofence name, third one is battery level. +# This cannot be switched around, please word your message accordingly. +# Notice how a line break takes the form '\\n', that's two backslashes. +message_geofence_enter='%s entered geofence %s.\\n\\nBattery level %s%%.' +message_geofence_exit='%s left geofence %s.\\n\\nBattery level %s%%.' + +# Argument '%s' is Traccar device name +message_device_online='On %s Traccar Client app is now active.' +message_device_unknown='On %s Traccar Client app has not checked in for 10 minutes, the app is likely off now.' +message_device_offline='On %s Traccar Client app is now off.' + +# Argument '%s' is Traccar device name but this one unlike the three +# message strings before is used as stdout log output when the device ID +# that triggered an event doesn't match our allow list in +# allow_unique_device_ids_posix_ere, see below. Note a trailing '\n' +# (backslash-n) at the end of this string. This is a Bash printf +# replacement string. +message_device_unhandled='For %s we do not want to send Signal messages. Nothing to do.\n' + +# First positional argument '%s' is Traccar device name, second +# positional argument '%s' is event name. This cannot be switched +# around, please word your message accordingly. +message_event_other='%s triggered event "%s" in Traccar server.' + +# If this is a non-empty value we'll use it to only handle Traccar +# events for these unique device IDs. Events triggered by other device +# IDs will be discarded. If this is an empty string or if it's not +# declared at all we'll handle events for all device IDs. In Traccar +# server every device has a unique ID that's distinct from its +# pretty-printed device name. This is a Posix Extended Regular +# Expression (ERE). Most Perl-Compatible Regular Expression (PCRE) +# syntax works. Most notably lookarounds are unsupported in ERE. +allow_unique_device_ids_posix_ere='^(901874|81919333|9991212)$' + +# signal-cli-rest-api config +signal_sender_phone_number='+18005550199' +signal_recipients_array='["group.ImxvbmdpdH=","recipient.21","+18005550111","i-am-a-uuid"]' +signal_api_endpoint_full_path='https://fully.qualified.domain.name/v2/send'