Files
docker-planefence/rootfs/usr/share/planefence/planefence_notify.sh
kx1t dd18d0cfa9 add Telegram notification support (#246)
Thanks to @smkrv in https://github.com/sdr-enthusiasts/docker-planefence/issues/244 for the base code that made the implementation process much easier! 

---------

Co-authored-by: @smkrv
2025-06-22 10:11:25 -04:00

545 lines
24 KiB
Bash
Executable File

#!/command/with-contenv bash
#shellcheck shell=bash
#shellcheck disable=SC2015,SC1091,SC2005,SC2006,SC2094,SC2154
# PLANETWEET - a Bash shell script to send a Tweet when a plane is detected in the
# user-defined fence area.
#
# Usage: ./planefence_notify.sh
#
# Note: this script is meant to be run as a daemon using SYSTEMD
# If run manually, it will continuously loop to listen for new planes
#
# This script is distributed as part of the Planefence package and is dependent
# on that package for its execution.
#
# Copyright 2020-2025 Ramon F. Kolb - licensed under the terms and conditions
# of GPLv3. The terms and conditions of this license are included with the Github
# distribution of this package, and are also available here:
# https://github.com/sdr-enthusiasts/docker-planefence
#
# The package contains parts of, and modifications or derivatives to the following:
# - Dump1090.Socket30003 by Ted Sluis: https://github.com/tedsluis/dump1090.socket30003
# - Twurl by Twitter: https://github.com/twitter/twurl and https://developer.twitter.com
# These packages may incorporate other software and license terms.
# -----------------------------------------------------------------------------------
# Feel free to make changes to the variables between these two lines. However, it is
# STRONGLY RECOMMENDED to RTFM! See README.md for explanation of what these do.
#
# -----------------------------------------------------------------------------------
# export all variables so send-discord-alert.py has access to the necessary params:
# set -a
#
# Let's see if there is a CONF file that overwrites some of the parameters already defined
source /scripts/common
[[ -z "$PLANEFENCEDIR" ]] && PLANEFENCEDIR=/usr/share/planefence
[[ -f "$PLANEFENCEDIR/planefence.conf" ]] && source "$PLANEFENCEDIR/planefence.conf"
#
# These are the input and output directories and file names
# HEADR determines the tags for each of the fields in the Tweet:
# 0 1 2 3 4 5 6 7 8 9 10 11
HEADR=("ICAO" "Flt" "Airline" "First seen" "End Time" "Min Alt" "Min Dist" "Link" "Loudness" "Peak Audio" "Org" "Dest")
# CSVFILE termines which file name we need to look in. We're using the 'date' command to
# get a filename in the form of 'planefence-200504.csv' where 200504 is yymmdd
#TODAYCSV=$(date -d today +"planefence-%y%m%d.csv")
#YSTRDAYCSV=$(date -d yesterday +"planefence-%y%m%d.csv")
# TWURLPATH is where we can find TWURL. This only needs to be filled in if you can't get it
# as part of the default PATH:
#[ ! `which twurl` ] && TWURLPATH="/root/.rbenv/shims/"
# If the VERBOSE variable is set to "1", then we'll write logs to LOGFILE.
# If you don't want logging, simply set the VERBOSE=1 line below to VERBOSE=0
LOGFILE=/tmp/planetweet.log
#TMPFILE=/tmp/planetweet.tmp
[[ "$PLANETWEET" != "" ]] && TWEETON=yes || TWEETON=no
CSVDIR=$OUTFILEDIR
CSVNAMEBASE=$CSVDIR/planefence-
CSVNAMEEXT=".csv"
VERBOSE=1
CSVTMP=/tmp/planetweet2-tmp.csv
PLANEFILE=/usr/share/planefence/persist/plane-alert-db.txt
# MINTIME is the minimum time we wait before sending a tweet
# to ensure that at least $MINTIME of audio collection (actually limited to the Planefence update runs in this period) to get a more accurste Loudness.
((TWEET_MINTIME > 0)) && MINTIME=$TWEET_MINTIME || MINTIME=100
# $ATTRIB contains the attribution line at the bottom of the tweet
ATTRIB="${ATTRIB:-#adsb #planefence by kx1t - https://sdr-e.com/docker-planefence}"
if [ "$SOCKETCONFIG" != "" ]; then
case "$(grep "^distanceunit=" "$SOCKETCONFIG" | sed "s/distanceunit=//g")" in
nauticalmile)
DISTUNIT="nm"
;;
kilometer)
DISTUNIT="km"
;;
mile)
DISTUNIT="mi"
;;
meter)
DISTUNIT="m"
;;
esac
fi
# get ALTITUDE unit:
ALTUNIT="ft"
if [ "$SOCKETCONFIG" != "" ]; then
case "$(grep "^altitudeunit=" "$SOCKETCONFIG" | sed "s/altitudeunit=//g")" in
feet)
ALTUNIT="ft"
;;
meter)
ALTUNIT="m"
;;
esac
fi
# determine if altitude is ASL or AGL
((ALTCORR > 0)) && ALTPARAM="AGL" || ALTPARAM="MSL"
# -----------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
#
# First create an function to write to the log
LOG() {
if [ "$VERBOSE" != "" ]; then
printf "%s-%s[%s]v%s: %s\n" "$(date +"%Y%m%d-%H%M%S")" "$PROCESS_NAME" "$CURRENT_PID" "$VERSION" "$1" >>$LOGFILE
fi
}
getRoute() {
# Uses MrJackWills's API to look up flight information based on Callsign. See https://github.com/mrjackwills/adsbdb
# Usage: routeString="$(getRoute "$CallSign")"
#
# Returns a string:
# if both Origin and Destination airport are available: "#BOS-#JFK"
# if only either Origin or Destination airport are known: "org: #BOS" or "dest: #JFK"
# if neither is available: empty string
#
# Prerequisites/dependencies: JQ, CURL
# first make sure we have an argument
if [[ -z "$1" ]]; then
return
fi
# Now get the results object from the API:
routeObj="$(curl -sL "https://api.adsbdb.com/v0/callsign/$1")"
# Unknown Call -> return empty
if [[ "$(jq '.response' <<<"$routeObj")" == "\"unknown callsign\"" ]]; then
return
fi
# Get origin/dest:
origin="$(jq '.response.flightroute.origin.iata_code' 2>/dev/null <<<"$routeObj" | tr -d '\"')"
destination="$(jq '.response.flightroute.destination.iata_code' 2>/dev/null <<<"$routeObj" | tr -d '\"')"
response=""
if [[ -n "$origin" ]] && [[ -n "$destination" ]]; then
response="#$origin - #$destination"
elif [[ -n "$origin" ]]; then
response="org: #$origin"
elif [[ -n "$destination" ]]; then
response="dest: #$destination"
fi
# print the result - this will be captured by the caller
echo "$response"
}
GET_PS_PHOTO () {
# Function to get a photo from PlaneSpotters.net
# Usage: GET_PS_PHOTO ICAO
# Returns: link to photo page (and a cached image should become available)
# First, let's see if we have a cache file for the photos
local link
local json
local starttime
starttime="$(date +%s)"
if chk_disabled "$SHOWIMAGES"; then return 0; fi
if [[ -f "/usr/share/planefence/persist/planepix/cache/$1.notavailable" ]]; then
echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - no picture available (checked previously)" >> /tmp/getpi.log
return 0
fi
if [[ -f "/usr/share/planefence/persist/planepix/cache/$1.jpg" ]] && \
[[ -f "/usr/share/planefence/persist/planepix/cache/$1.link" ]] && \
[[ -f "/usr/share/planefence/persist/planepix/cache/$1.thumb.link" ]]; then
echo "$(<"/usr/share/planefence/persist/planepix/cache/$1.link")"
echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - picture was in cache" >> /tmp/getpi.log
return 0
fi
# If we don't have a cache file, let's see if we can get one from PlaneSpotters.net
if json="$(curl -ssL --fail "https://api.planespotters.net/pub/photos/hex/$1")" && \
link="$(jq -r 'try .photos[].link | select( . != null )' <<< "$json")" && \
thumb="$(jq -r 'try .photos[].thumbnail_large.src | select( . != null )' <<< "$json")" && \
[[ -n "$link" ]] && [[ -n "$thumb" ]]; then
# If we have a link, let's download the photo
curl -ssL --fail --clobber "$thumb" -o "/usr/share/planefence/persist/planepix/cache/$1.jpg"
echo "$link" > "/usr/share/planefence/persist/planepix/cache/$1.link"
echo "$thumb" > "/usr/share/planefence/persist/planepix/cache/$1.thumb.link"
echo "$link"
echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - picture retrieved from planespotters.net" >> /tmp/getpi.log
else
# If we don't have a link, let's clear the cache and return an empty string
rm -f "/usr/share/planefence/persist/planepix/cache/$1.*"
touch "/usr/share/planefence/persist/planepix/cache/$1.notavailable"
echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - no picture available (new)" >> /tmp/getpi.log
fi
}
if [ "$1" != "" ] && [ "$1" != "reset" ]; then # $1 contains the date for which we want to run Planefence
TWEETDATE=$(date --date="$1" '+%y%m%d')
else
TWEETDATE=$(date --date="today" '+%y%m%d')
fi
[[ ! -f "$AIRLINECODES" ]] && AIRLINECODES=""
CSVFILE=$CSVNAMEBASE$TWEETDATE$CSVNAMEEXT
#CSVFILE=/tmp/planefence-200526.csv
# make sure there's no stray TMP file around, so we can directly append
[ -f "$CSVTMP" ] && rm "$CSVTMP"
#Now iterate through the CSVFILE:
LOG "------------------------------"
LOG "Starting PLANEFENCE_NOTIFY"
LOG "CSVFILE=$CSVFILE"
# Get the hashtaggable headers, and figure out of there is a field with a
# custom "$tag" header
[[ -f "$PLANEFILE" ]] && IFS="," read -ra hashtag <$PLANEFILE || unset hashtag
tagfield=""
for ((i = 0; i < ${#hashtag[@]}; i++)); do
if [[ "${hashtag[i],,}" == "\$tag" ]] || [[ "${hashtag[i],,}" == "#\$tag" ]]; then
tagfield=$((i + 1)) # number tagfield from 1 instead of 0 as we will use AWK to get it
break
fi
done
if [ -f "$CSVFILE" ]; then
while read -r CSVLINE; do
XX=$(echo -n "$CSVLINE" | tr -d '[:cntrl:]')
CSVLINE=$XX
unset RECORD
# Read the line, but first clean it up as it appears to have a newline in it
IFS="," read -ra RECORD <<<"$CSVLINE"
# LOG "${#RECORD[*]} records in the current line: (${RECORD[*]})"
# $TIMEDIFF contains the difference in seconds between the current record and "now".
# We want this to be at least $MINDIFF to avoid tweeting before all noise data is captured
# $TWEET_BEHAVIOR determines if we are looking at the end time (POST -> RECORD[3]) or at the
# start time (not POST -> RECORD[2]) of the observation time
[[ "$TWEET_BEHAVIOR" == "POST" ]] && TIMEDIFF=$(($(date +%s) - $(date -d "${RECORD[3]}" +%s))) || TIMEDIFF=$(($(date +%s) - $(date -d "${RECORD[2]}" +%s)))
# shellcheck disable=SC2126
# shellcheck disable=SC2094
if [[ "${RECORD[1]:0:1}" != "@" ]] && [[ $TIMEDIFF -gt $MINTIME ]] && [[ ("$(grep "${RECORD[0]},@${RECORD[1]}" "$CSVFILE" | wc -l)" == "0") || "$TWEETEVERY" == "true" ]]; then # ^not tweeted before^ ^older than $MINTIME^ ^No previous occurrence that was tweeted^ ...or... ^$TWEETEVERY is true^
AIRLINE=$(/usr/share/planefence/airlinename.sh "${RECORD[1]#@}" "${RECORD[0]}")
AIRLINETAG="#"
if [[ "${RECORD[1]}" != "" ]]; then
AIRLINETAG+="$(echo "$AIRLINE" | tr -d '[:space:]-')"
ROUTE="$(getRoute "${RECORD[1]}")"
fi
# Create a Tweet with the first 6 fields, each of them followed by a Newline character
[[ "${hashtag[0]:0:1}" == "$" ]] && TWEET="${HEADR[0]}: #${RECORD[0]}%0A" || TWEET="${HEADR[0]}: ${RECORD[0]}%0A" # ICAO
if [[ "${RECORD[1]}" != "" ]]; then
[[ "${hashtag[1]:0:1}" == "$" ]] && TWEET+="${HEADR[1]}: #${RECORD[1]//-/}" || TWEET+="${HEADR[1]}: ${RECORD[1]}" # Flight
fi
[[ "$AIRLINETAG" != "#" ]] && TWEET+=" ${AIRLINETAG//[&\'-]/_}" || true
[[ -n "$ROUTE" ]] && TWEET+=" $ROUTE" || true
TWEET+="%0A${HEADR[3]}: $(date -d "${RECORD[2]}" +"${NOTIF_DATEFORMAT:-%F %T %Z}")%0A"
TWEET+="${HEADR[5]}: ${RECORD[4]} $ALTUNIT $ALTPARAM%0A"
TWEET+="${HEADR[6]}: ${RECORD[5]} $DISTUNIT%0A"
# If there is sound level data, then add a Loudness factor (peak RMS - 1 hr avg) to the tweet.
# There is more data we could tweet, but we're a bit restricted in real estate on twitter.
((RECORD[7] < 0)) && TWEET+="${HEADR[9]}: ${RECORD[7]} dBFS%0A${HEADR[8]}: $((RECORD[7] - RECORD[11])) dB%0A"
# figure out of there are custom tags that apply to this ICAO:
[[ "$tagfield" != "" ]] && customtag="$(awk -F "," -v field="$tagfield" -v icao="${RECORD[0]}" '$1 == icao {print $field; exit;}' "$PLANEFILE")" || customtag=""
[[ "$customtag" != "" ]] && TWEET+="#$customtag "
TWEET+="%0A${RECORD[6]}"
# Add attribution to the tweet:
TWEET+="%0A$ATTRIB"
# swap adsbexchange for the $TRACKSERVICE:
TWEET="${TWEET//globe.adsbexchange.com/"$TRACKSERVICE"}"
LOG "Assessing ${RECORD[0]}: ${RECORD[1]:0:1}; diff=$TIMEDIFF secs; Tweeting... msg body: $TWEET" 1
# Before anything else, let's add the "tweeted" flag to the flight number:
XX="@${RECORD[1]}"
RECORD[1]=$XX
# First, let's get a screenshot if there's one available!
rm -f /tmp/snapshot.png
GOTSNAP=false
GOTIMG=false
snapfile="/tmp/snapshot.png"
# img will contain the path to an image file:
# - first prio is one that is stored in the planepix directory
# - second prio is one that is stored in the planepix/cache directory
imgfile="$(find /usr/share/planefence/persist/planepix -iname "${RECORD[0]}.jpg" -print -quit 2>/dev/null || true)"
# echo "-0- in planetweet: newsnap=\"$newsnap\" (find /usr/share/planefence/persist/planepix -iname ${RECORD[0]}.jpg -print -quit)"
if [[ -n "$imgfile" ]]; then
GOTIMG=true
"${s6wrap[@]}" echo "Using picture from $imgfile"
else
imglink=$(awk -F "," -v icao="${RECORD[0],,}" 'tolower($1) == icao { print $2 ; exit }' /usr/share/planefence/persist/planepix.txt 2>/dev/null || true)
if [[ -n "$imglink" ]] && curl -A "Mozilla/5.0 (X11; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" -s -L --fail "$imglink" --clobber -o $snapfile 2>/dev/stdout; then
"${s6wrap[@]}" echo "Got picture from $link"
GOTIMG=true
cp -n "$snapfile" "/usr/share/planefence/persist/planepix/cache/${RECORD[0]}.jpg"
rm -f "$snapfile"
imgfile="/usr/share/planefence/persist/planepix/cache/${RECORD[0]}.jpg"
elif [[ -n "$imglink" ]]; then
"${s6wrap[@]}" echo "Failed attempt to get picture from planepix.txt link $link"
fi
fi
# If there's no image, let's see if we can get one from planespotters.net
if ! $GOTIMG && [[ -n "$(GET_PS_PHOTO "${RECORD[0]}")" ]]; then
imgfile="/usr/share/planefence/persist/planepix/cache/${RECORD[0]}.jpg"
GOTIMG=true
"${s6wrap[@]}" echo "Got picture from PlaneSpotters.net"
fi
"${s6wrap[@]}" echo "Getting screenshot for ${RECORD[0]}..."
if curl -s -L --fail --max-time "$SCREENSHOT_TIMEOUT" "$SCREENSHOTURL/snap/${RECORD[0]#\#}" --clobber -o "/tmp/snapshot.png"; then
GOTSNAP=true
"${s6wrap[@]}" echo "Screenshot successfully retrieved at $SCREENSHOTURL for ${RECORD[0]}"
fi
if ! $GOTSNAP; then "${s6wrap[@]}" echo "Screenshot retrieval unsuccessful at $SCREENSHOTURL for ${RECORD[0]}"; fi
# Inject the Discord integration in here so it doesn't have to worry about state management
if [[ "${PF_DISCORD,,}" == "on" || "${PF_DISCORD,,}" == "true" ]] && [[ "x$PF_DISCORD_WEBHOOKS" != "x" ]] && [[ "x$DISCORD_FEEDER_NAME" != "x" ]]; then
LOG "Planefence sending Discord notification"
timeout 120 python3 "$PLANEFENCEDIR"/send-discord-alert.py "$CSVLINE" "$AIRLINE"
fi
# log the message we will try to tweet or toot:
if [[ -n "$MASTODON_SERVER" ]]; then
"${s6wrap[@]}" echo "Attempting to tweet or toot: $(sed -e 's|\\/|/|g' -e 's|\\n| |g' -e 's|%0A| |g' <<<"${TWEET}")"
fi
# Inject Mastodon integration here:
if [[ -n "$MASTODON_SERVER" ]]; then
mast_id=()
MASTTEXT="$(sed -e 's|\\/|/|g' -e 's|\\n|\n|g' -e 's|%0A|\n|g' <<<"${TWEET}")"
if $GOTSNAP && response="$(curl -sS --fail -H "Authorization: Bearer ${MASTODON_ACCESS_TOKEN}" -H "Content-Type: multipart/form-data" -X POST "https://${MASTODON_SERVER}/api/v1/media" --form file="@${snapfile}")"; then
# we upload an screenshot
mast_id+=("$(jq '.id' <<<"$response" | xargs)")
fi
if $GOTIMG && response="$(curl -sS -H "Authorization: Bearer ${MASTODON_ACCESS_TOKEN}" -H "Content-Type: multipart/form-data" -X POST "https://${MASTODON_SERVER}/api/v1/media" --form file="@${imgfile}")"; then
# we upload an image file
mast_id+=("$(jq '.id' <<<"$response" | xargs)")
fi
# now send the message. API is different if text-only vs text+image:
if [[ -z "${mast_id[*]}" ]]; then
# send without image(s)
response="$(curl -H "Authorization: Bearer ${MASTODON_ACCESS_TOKEN}" -sS "https://${MASTODON_SERVER}/api/v1/statuses" -X POST -F "status=${MASTTEXT}" -F "language=eng" -F "visibility=${MASTODON_VISIBILITY}")"
else
# send with image(s)
# shellcheck disable=SC2068
printf -v media_ids -- '-F media_ids[]=%s ' ${mast_id[@]}
# shellcheck disable=SC2086
response="$(curl -H "Authorization: Bearer ${MASTODON_ACCESS_TOKEN}" -s "https://${MASTODON_SERVER}/api/v1/statuses" -X POST $media_ids -F "status=${MASTTEXT}" -F "language=eng" -F "visibility=${MASTODON_VISIBILITY}")"
fi
# check if there was an error
if [[ "$(jq '.error' <<<"$response" | xargs)" == "null" ]]; then
"${s6wrap[@]}" echo "Planefence post to Mastodon generated successfully with visibility=${MASTODON_VISIBILITY}. Mastodon post available at: $(jq '.url' <<<"$response" | xargs)"
LINK="$(jq '.url' <<<"${response}" | xargs)"
else
"${s6wrap[@]}" echo "Mastodon post error. Mastodon returned this error: $(jq '.url' <<<"$response" | xargs)"
fi
fi
# Inject MQTT notification here:
if [[ -n "$MQTT_URL" ]]; then
unset msg_array
declare -A msg_array
msg_array[icao]="${RECORD[0]}"
msg_array[flight]="${RECORD[1]#@}"
msg_array[operator]="${AIRLINE//[\'\"]/ }"
msg_array[operator]="${msg_array[operator]//[&]/ and }"
msg_array[operator]="$(echo "${msg_array[operator]//#/}" | xargs)"
if [[ -n "$ROUTE" ]]; then
if [[ "${ROUTE:0:4}" == "org:" ]]; then
msg_array[origin]="${ROUTE:6}"
elif [[ "${ROUTE:0:5}" == "dest:" ]]; then
msg_array[destination]="${ROUTE:7}"
else
msg_array[origin]="${ROUTE:1:3}"
msg_array[destination]="${ROUTE: -3}"
fi
fi
msg_array[first_seen]="$(date -d "${RECORD[2]}" "+${MQTT_DATETIME_FORMAT:-%s}")"
msg_array[last_seen]="$(date -d "${RECORD[3]}" "+${MQTT_DATETIME_FORMAT:-%s}")"
msg_array[min_alt]="${RECORD[4]} $ALTUNIT $ALTPARAM"
msg_array[timezone]="$(date +%Z)"
msg_array[min_dist]="${RECORD[5]} $DISTUNIT"
msg_array[link]="${RECORD[6]//globe.adsbexchange.com/$TRACKSERVICE}"
if ((RECORD[7] < 0)); then
msg_array[peak_audio]="${RECORD[7]} dBFS"
msg_array[loudness]="$((RECORD[7] - RECORD[11])) dB"
fi
if [[ -f "/usr/share/planefence/persist/planepix/cache/${msg_array[icao]}.thumb.link" ]]; then
msg_array[thumbnail]="$(<"/usr/share/planefence/persist/planepix/cache/${msg_array[icao]}.thumb.link")"
fi
if [[ -f "/usr/share/planefence/persist/planepix/cache/${msg_array[icao]}.link" ]]; then
msg_array[planespotters_link]="$(<"/usr/share/planefence/persist/planepix/cache/${msg_array[icao]}.link")"
fi
# convert $msg_array[@] into a JSON object:
MQTT_JSON="$(for i in "${!msg_array[@]}"; do printf '{"%s":"%s"}\n' "$i" "${msg_array[$i]}"; done | jq -sc add)"
# prep the MQTT host, port, etc
unset MQTT_TOPIC MQTT_PORT MQTT_USERNAME MQTT_PASSWORD MQTT_HOST
MQTT_HOST="${MQTT_URL,,}"
MQTT_HOST="${MQTT_HOST##*:\/\/}" # strip protocol header (mqtt:// etc)
while [[ "${MQTT_HOST: -1}" == "/" ]]; do MQTT_HOST="${MQTT_HOST:0:-1}"; done # remove any trailing / from the HOST
if [[ $MQTT_HOST == *"/"* ]]; then MQTT_TOPIC="${MQTT_TOPIC:-${MQTT_HOST#*\/}}"; fi # if there's no explicitly defined topic, then use the URL's topic if that exists
MQTT_TOPIC="${MQTT_TOPIC:-$(hostname)/planefence}" # add default topic if there is still none defined
MQTT_HOST="${MQTT_HOST%%/*}" # remove everything from the first / onward
if [[ $MQTT_HOST == *"@"* ]]; then
MQTT_USERNAME="${MQTT_USERNAME:-${MQTT_HOST%@*}}"
MQTT_PASSWORD="${MQTT_PASSWORD:-${MQTT_USERNAME#*:}}"
MQTT_USERNAME="${MQTT_USERNAME%:*}"
MQTT_HOST="${MQTT_HOST#*@}"
fi
if [[ $MQTT_HOST == *":"* ]]; then MQTT_PORT="${MQTT_PORT:-${MQTT_HOST#*:}}"; fi
MQTT_HOST="${MQTT_HOST%:*}" # finally strip the host so there's only a hostname or ip address
# log the message we are going to send:
"${s6wrap[@]}" echo "Attempting to send a MQTT notification:"
"${s6wrap[@]}" echo "MQTT Host: $MQTT_HOST"
"${s6wrap[@]}" echo "MQTT Port: ${MQTT_PORT:-1883}"
"${s6wrap[@]}" echo "MQTT Topic: $MQTT_TOPIC"
"${s6wrap[@]}" echo "MQTT Client ID: ${MQTT_CLIENT_ID:-$(hostname)}"
if [[ -n "$MQTT_USERNAME" ]]; then "${s6wrap[@]}" echo "MQTT Username: $MQTT_USERNAME"; fi
if [[ -n "$MQTT_PASSWORD" ]]; then "${s6wrap[@]}" echo "MQTT Password: $MQTT_PASSWORD"; fi
if [[ -n "$MQTT_QOS" ]]; then "${s6wrap[@]}" echo "MQTT QOS: $MQTT_QOS"; fi
"${s6wrap[@]}" echo "MQTT Payload JSON Object: $MQTT_JSON"
# send the MQTT message:
mqtt_string=(--broker "$MQTT_HOST")
if [[ -n "$MQTT_PORT" ]]; then mqtt_string+=(--port "$MQTT_PORT"); fi
mqtt_string+=(--topic \""$MQTT_TOPIC"\")
if [[ -n "$MQTT_QOS" ]]; then mqtt_string+=(--qos "$MQTT_QOS"); fi
mqtt_string+=(--client_id \""${MQTT_CLIENT_ID:-$(hostname)}"\")
if [[ -n "$MQTT_USERNAME" ]]; then mqtt_string+=(--username "$MQTT_USERNAME"); fi
if [[ -n "$MQTT_PASSWORD" ]]; then mqtt_string+=(--password "$MQTT_PASSWORD"); fi
mqtt_string+=(--message "'${MQTT_JSON}'")
# shellcheck disable=SC2068
outputmsg="$(echo ${mqtt_string[@]} | xargs mqtt)"
if [[ "${outputmsg:0:6}" == "Failed" ]] || [[ "${outputmsg:0:5}" == "usage" ]]; then
"${s6wrap[@]}" echo "MQTT Delivery Error: ${outputmsg//$'\n'/ }"
else
"${s6wrap[@]}" echo "MQTT Delivery successful!"
if chk_enabled "$MQTT_DEBUG"; then "${s6wrap[@]}" echo "Results string: ${outputmsg//$'\n'/ }"; fi
fi
LINK="${LINK:-mqtt}"
fi
# Insert Telegram notifications here:
if chk_enabled "$TELEGRAM_ENABLED"; then
/scripts/post2telegram.sh PF "#Planefence $(sed -e 's|\\/|/|g' -e 's|\\n|\n|g' -e 's|%0A|\n|g' <<<"${TWEET}")" "$(if $GOTSNAP; then echo "$snapfile"; fi)" "$(if $GOTIMG; then echo "$imgfile"; fi)" || true
if [[ -f /tmp/telegram.link ]]; then
LINK="$(</tmp/telegram.link)"
rm -f /tmp/telegram.link
fi
fi
# Insert BlueSky notifications here:
if [[ -n "$BLUESKY_HANDLE" ]] && [[ -n "$BLUESKY_APP_PASSWORD" ]]; then
/scripts/post2bsky.sh "#Planefence $(sed -e 's|\\/|/|g' -e 's|\\n|\n|g' -e 's|%0A|\n|g' <<<"${TWEET}")" "$(if $GOTSNAP; then echo "$snapfile"; fi)" "$(if $GOTIMG; then echo "$imgfile"; fi)" || true
if [[ -f /tmp/bsky.link ]]; then
LINK="$(</tmp/bsky.link)"
rm -f /tmp/bsky.link
fi
fi
# And now, let's tweet!
if [ "$TWEETON" == "yes" ]; then
TWIMG="false"
if [[ "$GOTSNAP" == "true" ]]; then
# If the curl call succeeded, we have a snapshot.png file saved!
TW_MEDIA_ID=$(twurl -X POST -H upload.twitter.com "/1.1/media/upload.json" -f $snapfile -F media | sed -n 's/.*\"media_id\":\([0-9]*\).*/\1/p')
((TW_MEDIA_ID > 0)) && TWIMG="true" || TW_MEDIA_ID=""
fi
[[ "$TWIMG" == "true" ]] && "${s6wrap[@]}" echo "Twitter Media ID=$TW_MEDIA_ID" || "${s6wrap[@]}" echo "Twitter screenshot upload unsuccessful for ${RECORD[0]}"
# send a tweet and read the link to the tweet into ${LINK[1]}
if [[ "$TWIMG" == "true" ]]; then
LINK="$(echo "$(twurl -r "status=$TWEET&media_ids=$TW_MEDIA_ID" /1.1/statuses/update.json)" | tee -a /tmp/tweets.log | jq '.entities."urls" | .[] | .url' | tr -d '\"')"
else
LINK="$(echo "$(twurl -r "status=$TWEET" /1.1/statuses/update.json)" | tee -a /tmp/tweets.log | jq '.entities."urls" | .[] | .url' | tr -d '\"')"
fi
# shellcheck disable=SC2028
[[ "${LINK:0:12}" == "https://t.co" ]] && "${s6wrap[@]}" echo "Planefence post to Twitter generated successfully. Tweet available at: $LINK" || "${s6wrap[@]}" echo "Planefence Tweet error. Twitter returned:\n$(tail -1 /tmp/tweets.log)"
rm -f $snapfile
else
LOG "(A tweet would have been sent but \$TWEETON=\"$TWEETON\")"
fi
# Add a reference to the tweet to RECORD[7] (if no audio is available) or RECORD[11] (if audio is available)
if [[ -n "$LINK" ]]; then
if [[ -n "${RECORD[7]}" ]]; then
RECORD[12]="$LINK"
else
RECORD[7]="$LINK"
fi
fi
# LOG "Tweet sent!"
LOG "TWURL results: $LINK"
else
LOG "Assessing ${RECORD[0]}: ${RECORD[1]:0:1}; diff=$TIMEDIFF secs; Skipping: either already tweeted, or within $MINTIME secs."
fi
# Now write everything back to $CSVTMP
(
IFS=','
echo "${RECORD[*]}" >>"$CSVTMP"
)
LOG "The record now contains $(
IFS=','
echo "${RECORD[*]}"
)"
done <"$CSVFILE"
# last, copy the TMP file back to the CSV file
[ -f "$CSVTMP" ] && mv -f "$CSVTMP" "$CSVFILE"
else
LOG "$CSVFILE doesn't exist. Nothing to do..."
fi
LOG "Done!"
LOG "------------------------------"