Files
docker-planefence/rootfs/usr/share/plane-alert/plane-alert.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

888 lines
44 KiB
Bash
Executable File

#!/bin/bash
# shellcheck shell=bash disable=SC2164,SC2015,SC2006,SC2002,SC2154,SC2076,SC2153,SC2086,SC2001,SC2016,SC2094,SC1091
# PLANE-ALERT - a Bash shell script to assess aircraft from a socket30003 render a HTML and CSV table with nearby aircraft
# based on socket30003
#
# Usage: ./plane-alert.sh <inputfile>
#
# Copyright 2021-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
# These packages may incorporate other software and license terms.
#
# Summary of License Terms
# This program is free software: you can redistribute it and/or modify it under the terms of
# the GNU General Public License as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program.
# If not, see https://www.gnu.org/licenses/.
# -----------------------------------------------------------------------------------
#
source /scripts/common
PLANEALERTDIR=/usr/share/plane-alert # the directory where this file and planefence.py are located
# -----------------------------------------------------------------------------------
#
# PLEASE EDIT PARAMETERS IN 'plane-alert.conf' BEFORE USING PLANE-ALERT !!!
##echo $0 invoked
#
# -----------------------------------------------------------------------------------
# Exit if there is no input file defined. The input file contains the socket30003 logs that we are searching in
[ "$1" == "" ] && { echo "No inputfile detected. Syntax: $0 <inputfile>"; exit 1; } || INFILE="$1"
function cleanup
{
# do some final clean-up before exiting - this function is called by a trap on receiving the EXIT signal
rm -f "${OUTFILE%.*}"*.diff >/dev/null 2>/dev/null
rm -f "${OUTFILE%.*}"*.old >/dev/null 2>/dev/null
rm -f "$TMPDIR"/plalert*.tmp >/dev/null 2>/dev/null
rm -f /tmp/pa-diff.csv /tmp/pa-old.csv /tmp/pa-new.csv /tmp/patmp
}
#
# Now make sure we call 'cleanup' upon exit:
trap cleanup EXIT
#
# -----------------------------------------------------------------------------------
# Let's see if there is a CONF file that defines some of the parameters
[ -f "$PLANEALERTDIR/plane-alert.conf" ] && source "$PLANEALERTDIR/plane-alert.conf" || echo "Warning - cannot stat $PLANEALERTDIR/plane-alert.conf"
# -----------------------------------------------------------------------------------
#
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
if chk_enabled "$TESTING"; then echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - no picture available (checked previously)" >> /tmp/getpi.log; fi
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")"
if chk_enabled "$TESTING"; then echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - picture was in cache" >> /tmp/getpi.log; fi
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"
touch -d "+$((HISTTIME+1)) days" "/usr/share/planefence/persist/planepix/cache/$1.link" "/usr/share/planefence/persist/planepix/cache/$1.thumb.link"
echo "$link"
if chk_enabled "$TESTING"; then echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - picture retrieved from planespotters.net" >> /tmp/getpi.log; fi
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"
if chk_enabled "$TESTING"; then echo "pfn - $(date) - $(( $(date +%s) - starttime )) secs - $1 - no picture available (new)" >> /tmp/getpi.log; fi
fi
}
# -----------------------------------------------------------------------------------
if [[ -z "$SCREENSHOT_TIMEOUT" ]]; then SCREENSHOT_TIMEOUT=45; fi
if [[ -n "$BASETIME" ]]; then echo "10a1. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: parse alert list into dictionary"; fi
#
# Now let's start
#
#
# Get the file with planes to monitor.
# The file is in CSV format with this syntax:
# ICAO,TailNr,Owner,PlaneDescription
# for example:
# 42001,3CONM,GovernmentofEquatorialGuinea,DassaultFalcon900B
#
"${s6wrap[@]}" echo "Checking traffic against the alert-list..."
# create an associative array / dictionary from the plane alert list
declare -A ALERT_DICT
while IFS="" read -r line; do
[[ -n "${line%%,*}" ]] && ALERT_DICT["${line%%,*}"]="$line" || "${s6wrap[@]}" echo "hey badger, bad alert-list entry: \"$line\""
done < "$PLANEFILE"
if [[ -n "$BASETIME" ]]; then echo "10a2. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: check input for hex numbers on alert list"; fi
# Now search through the input file to see if we detect any planes in the alert list
# note - we reverse the input file because later items have a higher chance to contain callsign and tail info
# the 'sort' command will put things back in order, but the '-u' option will make sure we keep the LAST item
# rather than the FIRST item
tac "$INFILE" | {
while IFS="" read -r line; do
if [[ -n ${ALERT_DICT["${line%%,*}"]} ]]; then
echo "${line}"
fi
done
} | sort -t',' -k1,1 -k5,5 -u `# Filter out only the unique combinations of fields 1 (ICAO) and 5 (date)` \
> "$TMPDIR"/plalert.out.tmp `# write the result to a tmp file`
# remove the SQUAWKS. We're not interested in them if they were picked up because of the list, and having them here
# will cause duplicate entries down the line
if [[ -f "$TMPDIR/plalert.out.tmp" ]]
then
rm -f "$TMPDIR"/patmp
awk -F "," 'OFS="," {$9="";print}' "$TMPDIR"/plalert.out.tmp > "$TMPDIR"/patmp
mv -f "$TMPDIR"/patmp "$TMPDIR"/plalert.out.tmp
fi
[ "$TESTING" == "true" ] && echo "2. $TMPDIR/plalert.out.tmp contains $(cat "$TMPDIR"/plalert.out.tmp | wc -l) lines"
# Now plalert.out.tmp contains SBS data
# Let's figure out if we also need to find SQUAWKS
rm -f "$TMPDIR"/patmp
touch "$TMPDIR"/patmp
if [[ -n "$SQUAWKS" ]]
then
IFS="," read -ra sq <<< "$SQUAWKS"
# add some zeros to the front, in case there are less than 4 chars
sq=( "${sq[@]/#/0000}" )
# Now go through $INFILE and look for each of the squawks. Put the SBS data in /tmp/patmp:
for ((i=0; i<"${#sq[@]}"; i++))
do
sq[i]="${sq[i]: -4}" # get the right-most 4 characters
sq[i]="${sq[i]//x/.}" # replace x with dot-wildcard
awk -F "," "{if(\$9 ~ /${sq[i]}/){print}}" "$INFILE" >>"$TMPDIR"/patmp
done
# Now remove any erroneous squawks. We will consider a squawk valid only if there is another
# message more than 15 seconds apart from the same plane with the same squawk
# First read all
# Get the first match and the last match of the ICAO + Squawk combo
read -d " " -r a <<< "$(wc -l "$TMPDIR"/patmp)"
if [[ "$a" != "0" ]]
then
rm -f "$TMPDIR"/patmp2
touch "$TMPDIR"/patmp2
while IFS="" read -r line
do
IFS="," read -ra record <<< "$line"
# find the first match with the same Hex ID and Squawk
starttime="$(date -d "$(cat "$INFILE" 2>/dev/null | awk -F "," -v "ICAO=${record[0]}" -v "SQ=${record[8]}" '{if ($1 == ICAO && $9 == SQ) {print $5 " " $6; exit;}}')" +%s)"
endtime="$(date -d "$(tac "$INFILE" 2>/dev/null | awk -F "," -v "ICAO=${record[0]}" -v "SQ=${record[8]}" '{if ($1 == ICAO && $9 == SQ) {print $5 " " $6; exit;}}')" +%s)"
#IFS=, read -ra firstrecord <<< $(awk -F "," -v ICAO="${record[0]}" -v SQ="${record[8]}" '$1==ICAO && $9==SQ {print;exit}' "$INFILE")
#IFS=, read -ra lastrecord <<< $(tac "$INFILE" | awk -F "," -v ICAO="${record[0]}" -v SQ="${record[8]}" '$1==ICAO && $9==SQ {print;exit}')
#(( $(date -d "${lastrecord[4]} ${lastrecord[5]}" +%s) - $(date -d "${firstrecord[4]} ${firstrecord[5]}" +%s) > SQUAWKTIME )) && printf "%s\n" $line >> $TMPDIR/patmp2 || echo "Pruned spurious Squawk: $line"
if (( endtime - starttime > SQUAWKTIME ))
then
printf "%s\n" "$line" >> "$TMPDIR"/patmp2
"${s6wrap[@]}" echo "Found acceptable Squawk (time diff=$(( endtime - starttime )) secs): $line"
else
"${s6wrap[@]}" echo "Pruned spurious Squawk (time diff=$(( endtime - starttime )) secs): $line"
fi
done < "$TMPDIR"/patmp
mv -f "$TMPDIR"/patmp2 "$TMPDIR"/patmp
fi
# clean up /tmp/patmp
tac "$TMPDIR"/patmp | sort -t',' -k1,1 -k9,9 -u >> "$TMPDIR"/plalert.out.tmp # sort this from the reverse of the file
sort -t',' -k5,5 -k6,6 "$TMPDIR"/plalert.out.tmp > "$TMPDIR"/patmp
mv -f "$TMPDIR"/patmp "$TMPDIR"/plalert.out.tmp
# Now plalert.out.tmp may contain duplicates if there's a match on BOTH the plane-alert-db AND the Squawk
# Going to assume that this is OK for now even though it may result in double tweets.
# Although -- twitter may reject the second tweet.
fi
# Create a backup of $OUTFILE so we can compare later on.
touch "$OUTFILE" # ensure it always exists, even is there's no $OUTFILE
cp -f "$OUTFILE" /tmp/pa-old.csv
# Process the intermediate file with the SBS data
# example:
# 0=hex_ident,1=altitude(feet),2=latitude,3=longitude,4=date,5=time,6=angle,7=distance(kilometer),8=squawk,9=ground_speed(knotph),10=track,11=callsign
# A0B674,750,42.29663,-71.00664,2021/03/17,16:43:52.598,122.36,30.2,0305,139,321,N145NE
if [[ -n "$BASETIME" ]]; then echo "10b. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: start processing new data"; fi
while IFS= read -r line
do
[ "$TESTING" == "true" ] && echo 3. Parsing line "$line"
IFS=',' read -ra pa_record <<< "$line" # load a single line into an array called $pa_record
# Skip the line if it's out of range
awk "BEGIN{ exit (${pa_record[7]} < $RANGE) }" && continue || true
PLANELINE="${ALERT_DICT[${pa_record[0]}]}"
IFS="," read -ra TAGLINE <<< "$PLANELINE"
# Parse this into a single line with syntax ICAO,TailNr,Owner,PlaneDescription,date,time,lat,lon,callsign,adsbx_url,squawk
ICAO="${pa_record[0]/ */}" # ICAO (stripped spaces)
outrec="${ICAO},"
TAIL="${TAGLINE[1]}"
#Get a tail number if we don't have one
if [[ $TAIL == "" ]]; then
TAIL="$(grep -i -w "$ICAO" /run/planefence/icao2plane.txt 2>/dev/null | head -1 | awk -F "," '{print $2}')"
fi
outrec+="${TAIL}," # tail
#Get an owner if there's none, we have a tail number and we are in the US
OWNER="${TAGLINE[2]}"
if [[ -z $OWNER ]] && [[ -n $TAIL ]]; then
#if [[ "${TAIL:0:1}" == "N" ]]; then
if [[ $TAIL =~ ^N[0-9][0-9a-zA-Z]+$ ]]; then
OWNER="$(/usr/share/planefence/airlinename.sh "$TAIL")"
fi
fi
#Get an owner if there's none and there is a flight number
if [[ -z $OWNER ]] && [[ -n ${pa_record[11]/ */} ]]; then
OWNER="$(/usr/share/planefence/airlinename.sh "${pa_record[11]/ */}")"
fi
outrec+="${OWNER}," # owner name
outrec+="${TAGLINE[3]}," # equipment
outrec+="${pa_record[4]}," # Date first heard
outrec+="${pa_record[5]:0:8}," # Time first heard
outrec+="${pa_record[2]}," # Latitude
outrec+="${pa_record[3]}," # Longitude
outrec+="${pa_record[11]/ */}," # callsign or flt nr (stripped spaces)
epoch_sec="$(date -d"${pa_record[4]} ${pa_record[5]}" +%s)"
if chk_enabled "$TRACK_FIRSTSEEN"; then TRACK_FIRSTSEEN="true"; else unset TRACK_FIRSTSEEN; fi
outrec+="https://$TRACKSERVICE/?icao=${pa_record[0]}&zoom=$MAPZOOM&lat=${pa_record[2]}&lon=${pa_record[3]}${TRACK_FIRSTSEEN:+&timestamp=${epoch_sec}&showTrace=$(date -u -d@"${epoch_sec}" "+%Y-%m-%d")}," # ICAO for insertion into ADSBExchange link
# only add squawk if its in the list
x=""
for ((i=0; i<"${#sq[@]}"; i++))
do
x+=$(awk "{if(\$1 ~ /${sq[i]}/){print}}" <<< "${pa_record[8]}")
done
[[ -n "$x" ]] && outrec+="${pa_record[8]}" # squawk
echo "$outrec" >> "$OUTFILE" # Append this line to $OUTWRITEFILE
done < "$TMPDIR"/plalert.out.tmp
# I like this better but the line below sorts nicer: awk -F',' '!seen[$1 $5)]++' "$OUTFILE" > /tmp/pa-new.csv
sort -t',' -k5,5 -k1,1 -k11,11 -u -o /tmp/pa-new.csv "$OUTFILE" # sort by field 5=date and only keep unique entries based on ICAO, date, and squawk. Use an intermediate file so we dont overwrite the file we are reading from
sort -t',' -k5,5 -k6,6 -o "$OUTFILE" /tmp/pa-new.csv # sort once more by date and time but keep all entries
# the log files are now done, but we want to figure out what is new
if [[ -n "$BASETIME" ]]; then echo "10c. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: done processing new data"; fi
# create some diff files
rm -f /tmp/pa-diff.csv
touch /tmp/pa-diff.csv
# compare the new csv file to the old one and only print the added entries
comm -23 <(sort < "$OUTFILE") <(sort < /tmp/pa-old.csv ) >/tmp/pa-diff.csv
[[ "$(wc -l < /tmp/pa-diff.csv)" -gt "0" ]] && [[ "$LOGLEVEL" != "ERROR" ]] && "${s6wrap[@]}" echo "Plane-Alert DIFF file has $(cat /tmp/pa-diff.csv | wc -l) lines and contains:" && cat /tmp/pa-diff.csv || true
# -----------------------------------------------------------------------------------
# Next, let's do some stuff with the newly acquired aircraft of interest
# but only if there are actually newly acquired records
#
# Read the header - we will need it a few times later:
# shellcheck disable=SC2001
[[ -n "$ALERTHEADER" ]] && IFS="," read -ra header <<< "$(sed 's/\#\$/$#/g' <<< "$ALERTHEADER")" || IFS="," read -ra header <<< "$(head -n1 "$PLANEFILE" | sed 's/\#\$/$#/g')"
# if ALERTHEADER is set, then use that one instead of
if [[ -n "$BASETIME" ]]; then echo "10d. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: start Tweet run"; fi
"${s6wrap[@]}" echo "Checking if notifications need to be sent..."
# If there's any new alerts send them out
if [[ "$(cat /tmp/pa-diff.csv | wc -l)" != "0" ]]
then
# Loop through the new planes and notify them. Initialize $ERRORCOUNT to capture the number of Tweet failures:
ERRORCOUNT=0
while IFS= read -r line
do
XX=$(echo -n "$line" | tr -d '[:cntrl:]')
line=$XX
unset pa_record images
IFS=',' read -ra pa_record <<< "$line"
if [[ -n "$BASETIME" ]]; then echo "10d1. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: processing ${pa_record[1]}"; fi
ICAO="${pa_record[0]}"
# Get a screenshot if there\'s one available!
snapfile="/tmp/pasnapshot.png"
rm -f $snapfile
GOTSNAP=false
readarray -t images <<< "$(find /usr/share/planefence/persist/planepix -iname "${ICAO}*.jpg" -print)"
if ! chk_disabled "${SCREENSHOTURL}" && curl -L -s --max-time $SCREENSHOT_TIMEOUT --fail "$SCREENSHOTURL"/snap/"${pa_record[0]#\#}" --clobber -o $snapfile
then
GOTSNAP=true
"${s6wrap[@]}" echo "Screenshot successfully retrieved at $SCREENSHOTURL for ${ICAO}; saved to $snapfile"
else
"${s6wrap[@]}" echo "Screenshot retrieval failed at $SCREENSHOTURL for ${ICAO}"
fi
# Special feature for Denis @degupukas -- if no screenshot was retrieved, see if there is a picture we can add
images+=("$(awk -F "," -v icao="${ICAO,,}" 'tolower($1) == icao { print "/usr/share/planefence/persist/planepix/" $2 ; exit }' /usr/share/planefence/persist/planepix.txt 2>/dev/null || true)")
# Send Discord alerts if that's enabled
if [[ "${PA_DISCORD,,}" != "false" ]] && [[ -n "$PA_DISCORD_WEBHOOKS" ]] && [[ -n "$DISCORD_FEEDER_NAME" ]]
then
[[ "$LOGLEVEL" != "ERROR" ]] && "${s6wrap[@]}" echo "PlaneAlert sending Discord notification" || true
timeout 120 python3 $PLANEALERTDIR/send-discord-alert.py "$line"
fi
# Build the message field:
[[ "${header[0]:0:1}" == "$" ]] && pa_record[0]="#${pa_record[0]}" # ICAO field
[[ "${header[1]:0:1}" == "$" ]] && [[ -n "${pa_record[1]}" ]] && pa_record[1]="#${pa_record[1]//[[:space:]-]/}" # tail field
[[ "${header[2]:0:1}" == "$" ]] && [[ -n "${pa_record[2]}" ]] && pa_record[2]="#${pa_record[2]}" # owner field
[[ "${header[3]:0:1}" == "$" ]] && [[ -n "${pa_record[2]}" ]] && pa_record[3]="#${pa_record[3]}" # equipment field
[[ "${header[1]:0:1}" == "$" ]] && [[ -n "${pa_record[8]}" ]] && pa_record[8]="#${pa_record[8]//[[:space:]-]/}" # flight nr field (connected to tail header)
[[ -n "${pa_record[10]}" ]] && pa_record[10]="#${pa_record[10]}" # # squawk
# First build the text of the tweet: reminder:
# 0-ICAO,1-TailNr,2-Owner,3-PlaneDescription,4-date,5-time,6-lat,7-lon
# 8-callsign,9-adsbx_url,10-squawk
TWITTEXT="#PlaneAlert "
TWITTEXT+="ICAO: ${pa_record[0]} "
[[ -n "${pa_record[1]}" ]] && TWITTEXT+="Tail: ${pa_record[1]} "
[[ -n "${pa_record[8]}" ]] && TWITTEXT+="Flt: ${pa_record[8]} "
[[ -n "${pa_record[10]}" ]] && TWITTEXT+="#Squawk: ${pa_record[10]}"
[[ "${pa_record[10]//#/}" == "7700 " ]] && TWITTEXT+=" #EMERGENCY!"
[[ -n "${pa_record[2]}" ]] && TWITTEXT+="\nOwner: ${pa_record[2]//[ &\']/}" # trailing ']}" for vim broken syntax
TWITTEXT+="\nAircraft: ${pa_record[3]}\n"
twdate="$(date -d "${pa_record[4]} ${pa_record[5]}" +"${NOTIF_DATEFORMAT:-%F %T %Z}")"
TWITTEXT+="${twdate//\//\\\/}\n"
PLANELINE="${ALERT_DICT["${ICAO}"]}"
IFS="," read -ra TAGLINE <<< "$PLANELINE"
# Add any hashtags and extract images:
counter=1
for i in {4..20}
do
(( i >= ${#header[@]} )) && break # don't print headers if they don't exist
tag="${TAGLINE[i]}"
if [[ "${header[i]:0:1}" == "$" ]] || [[ "${header[i]:0:2}" == '$#' ]]
then
if [[ "${tag:0:4}" == "http" ]]
then
TWITTEXT+="$(sed 's|/|\\/|g' <<< "$tag") "
elif [[ -n "$tag" ]]
then
TWITTEXT+="#$(tr -dc '[:alnum:]' <<< "$tag") "
fi
fi
if [[ " jpg jpeg png gif " =~ " ${tag##*.} " ]]; then
if [[ "${tag:0:4}" != "http" ]]; then tag="https://$tag"; fi
if [[ ! -f "/usr/share/planefence/persist/planepix/cache/$ICAO-$counter.${tag##*.}" ]]; then
if curl -sL -A "Mozilla/5.0 (X11; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "$tag" --clobber -o "/usr/share/planefence/persist/planepix/cache/$ICAO-$counter.${tag##*.}"; then
images+=("/usr/share/planefence/persist/planepix/cache/$ICAO-$counter.${tag##*.}")
touch -d "+$((HISTTIME+1)) days" "/usr/share/planefence/persist/planepix/cache/$ICAO-$counter.${tag##*.}"
else
# remove any potential left-overs of failed curl attempts
rm -f "/usr/share/planefence/persist/planepix/cache/$ICAO-$counter.${tag##*.}"
fi
fi
(( counter=counter+1 ))
fi
done
# see if we can get an image from PlaneSpotters.net - we'll do this always
# so we'll also get a link to the planespotters.net thumbnail and image pages to include in
# certain notifications
if $SHOWIMAGES && \
[[ -n "$(GET_PS_PHOTO "${pa_record[0]//#/}")" ]] && \
[[ -f "/usr/share/planefence/persist/planepix/cache/${pa_record[0]//#/}.jpg" ]]
then
images+=("/usr/share/planefence/persist/planepix/cache/${pa_record[0]//#/}.jpg")
fi
# shellcheck disable=SC2068
# shellcheck disable=SC2207
images=($(printf "%s\n" ${images[@]} | sort -u))
TWITTEXT+="\n$(sed 's|/|\\/|g' <<< "${pa_record[9]//globe.adsbexchange.com/"$TRACKSERVICE"}")"
TWITTEXT+="\n\n$ATTRIB"
TWITTEXT="${TWITTEXT//\'/}"
if [[ -n "$MASTODON_SERVER" ]] || [[ "$TWITTER" != "false" ]] || [[ -n "$BLUESKY_HANDLE" ]]; then
"${s6wrap[@]}" echo "Attempting to Tweet, Toot, or Post this message:"
"${s6wrap[@]}" echo "$(sed -e 's|\\/|/|g' -e 's|\\n| |g' -e 's|%0A| |g' <<< "${TWITTEXT}")"
fi
# Inject MQTT integration here:
if [[ -n "$MQTT_URL" ]]; then
# do some prep work:
PLANELINE="${ALERT_DICT["${ICAO}"]}"
IFS="," read -ra TAGLINE <<< "$PLANELINE"
unset msg_array
declare -A msg_array
# now put all relevant info into the associative array:
msg_array[icao]="${pa_record[0]//#/}"
msg_array[tail]="${pa_record[1]//#/}"
msg_array[squawk]="${pa_record[10]//#/}"
[[ "${msg_array[squawk]}" == "7700 " ]] && msg_array[emergency]=true || msg_array[emergency]=false
msg_array[flight]="${pa_record[8]//#/}"
if [[ -n "${pa_record[2]}" ]]; then
msg_array[operator]="${pa_record[2]//[\'\"]/ }"
msg_array[operator]="${msg_array[operator]//[&]/ and }"
msg_array[operator]="$(echo "${msg_array[operator]//#/}" | xargs)"
fi
msg_array[type]="${pa_record[3]//#/}"
msg_array[datetime]="$(date -d "${pa_record[4]} ${pa_record[5]}" "+${MQTT_DATETIME_FORMAT:-%s}")"
msg_array[tracklink]="${pa_record[9]//globe.adsbexchange.com/"$TRACKSERVICE"}"
msg_array[latitude]="${pa_record[6]}"
msg_array[longitude]="${pa_record[7]}"
# Add any hashtags:
for i in {4..13}; do
(( i >= ${#header[@]} )) && break # don't print headers if they don't exist
if [[ "${header[i]:0:1}" == "$" ]] || [[ "${header[i]:0:2}" == '$#' ]]; then
hdr="${header[i]//[#$]/}"
hdr="${hdr// /_}"
hdr="${hdr,,}"
msg_array[$hdr]="${TAGLINE[i]}"
fi
done
# add any image links
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
counter=0
for link in "${images[@],,}"; do
if [[ "${link:0:4}" == "http" ]]; then
msg_array[imglink-$((++counter))]="$link" || true
fi
done
# 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)/planealert}" # 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 "Username: ${MQTT_USERNAME}"; fi
if [[ -n "$MQTT_PASSWORD" ]]; then "${s6wrap[@]}" echo "MQTT Password: ${MQTT_PASSWORD}"; fi
if [[ -n "$MQTT_QOS" ]]; then "${s6wrap[@]}" echo "QOS: ${MQTT_QOS}"; fi
"${s6wrap[@]}" echo "MQTT Payload JSON Object: ${MQTT_JSON}"
# send the MQTT message:
# 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
fi
# Inject BlueSky integration here:
if [[ -n "$BLUESKY_HANDLE" ]] && [[ -n "$BLUESKY_APP_PASSWORD" ]]; then
# now send the BlueSky message:
# echo "DEBUG: posting to BlueSky: /scripts/post2bsky.sh \"$(sed -e 's|\\/|/|g' -e 's|\\n|\n|g' -e 's|%0A|\n|g' <<< "${TWITTEXT}")\" ${images[*]::4}"
# shellcheck disable=SC2206
if [[ "$GOTSNAP" == "true" ]]; then bsky_images=("$snapfile" ${images[@]}); else bsky_images=(${images[@]}); fi
# shellcheck disable=SC2068
/scripts/post2bsky.sh "$(sed -e 's|\\/|/|g' -e 's|\\n|\n|g' -e 's|%0A|\n|g' <<< "${TWITTEXT}")" ${bsky_images[@]::4} || true
fi
# Inject Telegram integration here:
if chk_enabled "$TELEGRAM_ENABLED"; then
# now send the Telegram message:
# echo "DEBUG: posting to Telegram: /scripts/post2telegram.sh \"$(sed -e 's|\\/|/|g' -e 's|\\n|\n|g' -e 's|%0A|\n|g' <<< "${TWITTEXT}")\" ${images[*]::4}"
# shellcheck disable=SC2206
if [[ "$GOTSNAP" == "true" ]]; then telegram_images=("$snapfile" ${images[@]}); else telegram_images=(${images[@]}); fi
# shellcheck disable=SC2068
/scripts/post2telegram.sh PA "$(sed -e 's|\\/|/|g' -e 's|\\n|\n|g' -e 's|%0A|\n|g' <<< "${TWITTEXT}")" ${telegram_images[@]::4} || true
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' <<< "${TWITTEXT}")"
# upload a map screenshot if one is available
if [[ "$GOTSNAP" == "true" ]]
then
response="$(curl -s -H "Authorization: Bearer ${MASTODON_ACCESS_TOKEN}" -H "Content-Type: multipart/form-data" -X POST "https://${MASTODON_SERVER}/api/v1/media" --form file="@${snapfile}")"
mast_id+=("$(jq '.id' <<< "$response"|xargs -0)")
fi
# upload all images
for img in "${images[@]}"; do
response="$(curl -s -H "Authorization: Bearer ${MASTODON_ACCESS_TOKEN}" -H "Content-Type: multipart/form-data" -X POST "https://${MASTODON_SERVER}/api/v1/media" --form file="@$img")"
if [[ "$(jq '.id' <<< "$response" | xargs -0)" != "null" ]]; then
mast_id+=("$(jq '.id' <<< "$response" | xargs -0)") || true
fi
done
#shellcheck disable=SC2068
if [[ -n "${mast_id[*]}" ]]; then
printf -v media_ids -- '-F media_ids[]=%s ' ${mast_id[@]}
"${s6wrap[@]}" echo "${#mast_id[@]} images uploaded to Mastodon"
else
media_ids=""
fi
# now send the Mastodon Toot.
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}")"
# check if there was an error
if [[ "$(jq '.error' <<< "$response"|xargs -0)" == "null" ]]
then
"${s6wrap[@]}" echo "Planefence post to Mastodon generated successfully with visibility=${MASTODON_VISIBILITY}. Mastodon post available at: $(jq '.url' <<< "$response"|xargs)"
else
"${s6wrap[@]}" echo "Mastodon post error. Mastodon returned this error: $(jq '.error' <<< "$response"|xargs -0)"
fi
fi
done < /tmp/pa-diff.csv
fi
if [[ -n "$BASETIME" ]]; then echo "10e. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: finished Tweet run, start building webpage"; fi
(( ERRORCOUNT > 0 )) && "${s6wrap[@]}" echo "There were $ERRORCOUNT tweet errors."
# We can also get rid of all-except-1 downloaded images from plane-alert-db.txt, as they are not needed anymore.
rm -f "/usr/share/planefence/persist/planepix/cache/*-{2,3,4}.jpg"
# Now everything is in place, let's update the website
"${s6wrap[@]}" echo "Writing plane-alert webpage..."
# read all planespotters.net thumbnail links into an array for fast access
unset THUMBS_ARRAY LINKS_ARRAY FILES_ARRAY
declare -A THUMBS_ARRAY LINKS_ARRAY FILES_ARRAY
while read -r filename; do
icao="${filename##*/}"
THUMBS_ARRAY[${icao%%.*}]="$(<"$filename")"
done <<< "$(find /usr/share/planefence/persist/planepix/cache/ -iname "*.thumb.link" -print)"
while read -r filename; do
icao="${filename##*/}"
LINKS_ARRAY[${icao%%.*}]="$(<"$filename")"
done <<< "$(find /usr/share/planefence/persist/planepix/cache/ -regex '.*/[0-9A-F]+\.link' -print)"
while read -r filename; do
icao="${filename##*/}"
if [[ -z "${FILES_ARRAY[${icao%%[-.]*}]}" ]]; then
FILES_ARRAY[${icao%%[-.]*}]="$filename"
fi
done <<< "$(find /usr/share/planefence/persist/planepix/cache/ -iname "*.jpg" -print)"
cp -f $PLANEALERTDIR/plane-alert.header.html "$TMPDIR"/plalert-index.tmp
#cat ${OUTFILE%.*}*.csv | tac > $WEBDIR/$CONCATLIST
# Create a FD for plalert-index.tml to reduce write cycles
exec 3>> "$TMPDIR"/plalert-index.tmp
# figure out if there are squawks:
awk -F "," '$12 != "" {rc = 1} END {exit !rc}' "$OUTFILE" && sqx="true" || sqx="false"
if [[ -n "$BASETIME" ]]; then echo "10e1. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: webpage - writing table headers"; fi
# first add the fixed part of the header:
cat <<EOF >&3
<table border="1" id="mytable" class="display" id="mytable" style="width: auto; align: left" align="left">
<thead border="1">
<tr>
<th style="text-align: center">No.</th>
<th>Image</th>
<th style="text-align: center">$(sed 's/^[#$]*\(.*\)/\1/g' <<< "${header[0]}")</th> <!-- ICAO -->
<th style="text-align: center">$(sed 's/^[#$]*\(.*\)/\1/g' <<< "${header[1]}")</th> <!-- tail -->
<th>$(sed 's/^[#$]*\(.*\)/\1/g' <<< "${header[2]}")</th> <!-- owner -->
<th>$(sed 's/^[#$]*\(.*\)/\1/g' <<< "${header[3]}")</th> <!-- equipment -->
<th style="text-align: center">Date/Time First Seen</th>
<th style="text-align: center">Lat/Lon First Seen</th>
<th>Flight No.</th>
$([[ "$sqx" == "true" ]] && echo "<th>Squawk</th>")
<!-- th>Flight Map</th -->
EOF
#print the variable headers:
ICAO_INDEX=-1
for i in {4..20}
do
(( i >= ${#header[@]} )) && break # don't print headers if they don't exist
[[ "${header[i]:0:1}" != "#" ]] && [[ "${header[i]:0:2}" != '$#' ]] && printf '<th>%s</th> <!-- custom header %d -->\n' "$(sed 's/^[#$]*\(.*\)/\1/g' <<< "${header[i]}")" "$i" >&3
[[ "${header[i]^^}" == "#ICAO TYPE" ]] || [[ "${header[i]^^}" == '$ICAO TYPE' ]] || [[ "${header[i]^^}" == '$#ICAO TYPE' ]] || [[ "${header[i]^^}" == "ICAO TYPE" ]] && ICAO_INDEX=$i
done
echo "</tr></thead><tbody border=\"1\">" >&3
if [[ -n "$BASETIME" ]]; then echo "10e2. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: webpage - writing table content" && TABLESTARTTIME="$(date +%s.%2N)"; fi
COUNTER=1
REFDATE=$(date -d "$HISTTIME days ago" '+%Y/%m/%d %H:%M:%S')
OUTSTRING="$(awk -F, -v d="$REFDATE" '$1 != "" && $5" "$6 > d' "$OUTFILE" | tr -d -c '[:print:]\n')" # get only those lines within the REFTIME timeframe
IMGBASE="silhouettes/"
if [[ -n "$BASETIME" ]]; then echo "10e2.0. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: ready to loop"; fi
while read -r line; do
if [[ -n "$line" ]]; then
IFS=',' read -ra pa_record <<< "$line"
if [[ -n "$BASETIME" ]] && ! (( COUNTER%10 )); then ITEMSTARTTIME="$(date +%s.%4N)" && echo "10e2a. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s / $(bc -l <<< "$(date +%s.%2N) - $TABLESTARTTIME")s / $(bc -l <<< "$(date +%s.%4N) - $ITEMSTARTTIME")s -- plane-alert.sh: wrote item number for ${pa_record[0]} ($COUNTER)"; fi
# prep-work for later use:
PLANELINE="${ALERT_DICT["${pa_record[0]}"]}"
IFS="," read -ra TAGLINE <<< "$PLANELINE"
if [[ "${pa_record[10]}" == "7700" ]]
then
printf "%s\n" "<tr style=\"vertical-align: middle; color:#D9EBF9; height:20px; line-height:20px; background:#7F0000;\">" >&3
else
printf "%s\n" "<tr>" >&3
fi
# print the row number
printf " %s%s%s\n" "<td style=\"text-align: center\">" "$((COUNTER++))" "</td><!-- item number -->" >&3 # column: Number
# determine which icon is to be used. If there's no ICAO Type field, or if there's no type in the field, or if the corresponding file doesn't exist, then replace it by BLANK.bmp
IMGURL="$IMGBASE"
# If there's a squawk, use it to determine the image:
if [[ -n "${pa_record[10]}" ]]
then
if [[ -f /usr/share/planefence/html/plane-alert/$IMGURL${pa_record[10]}.bmp ]]
then
IMGURL+="${pa_record[10]}.bmp"
else
IMGURL+="SQUAWK.bmp"
fi
else
# there is no squawk. If there's an ICAO_INDEX value, then try to get the silhouette URL
if [[ "$ICAO_INDEX" != "-1" ]]
then
if [[ -f /usr/share/planefence/html/plane-alert/$IMGURL${TAGLINE[$ICAO_INDEX]^^}.bmp ]]
then
IMGURL+=${TAGLINE[$ICAO_INDEX]^^}.bmp
else
IMGURL+="BLNK.bmp"
fi
else
# there is no squawk and no known silhouette, so use the blank
IMGURL+="BLNK.bmp"
fi
fi
if [[ -n "${pa_record[10]}" ]]
then
# print Squawk
# determine text color for squawk
case "${pa_record[10]}" in
"7700")
SQCOLOR="#7F0000"
;;
"7600")
SQCOLOR="#FF6A00"
;;
"7500")
SQCOLOR="#00194C"
;;
"7400")
SQCOLOR="#2D3F00"
;;
*)
SQCOLOR="#000000"
;;
esac
printf " %s%s%s\n" "<td style=\"padding:0;\"><div style=\"vertical-align: middle; font-weight:bold; color:#D9EBF9; height:20px; text-align:center; line-height:20px; background:$SQCOLOR;\">" "SQUAWK ${pa_record[10]}" "</div></td><!-- squawk instead of silhouette/image -->" >&3
else
IMG=""
# get an image if it exists and if SHOWIMAGES==true
if $SHOWIMAGES; then
# make sure we have an image to show
if [[ -z "${THUMBS_ARRAY[${pa_record[0]}]}" ]] && [[ -z "${FILES_ARRAY[${pa_record[0]}]}" ]]; then
# try to get at least 1 image. At this time, we need only 1 image for display only. This saves disk space
if [[ -z "$(GET_PS_PHOTO "${pa_record[0]}")" ]]; then
for tag in "${TAGLINE[@]}"; do
if [[ " jpg jpeg png gif " =~ " ${tag##*.} " ]]; then
if [[ "${tag:0:4}" != "http" ]]; then tag="https://$tag"; fi
if curl -sL -A "Mozilla/5.0 (X11; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0" "$tag" --clobber -o "/usr/share/planefence/persist/planepix/cache/${pa_record[0]}-1.${tag##*.}"; then
FILES_ARRAY[${pa_record[0]}]="/usr/share/planefence/persist/planepix/cache/${pa_record[0]}-1.${tag##*.}"
touch -d "+$((HISTTIME+1)) days" "/usr/share/planefence/persist/planepix/cache/${pa_record[0]}-1.${tag##*.}"
break
else
# remove any potential left-overs of failed curl attempts
rm -f "/usr/share/planefence/persist/planepix/cache/${pa_record[0]}-1.${tag##*.}"
fi
fi
done
else
if [[ -f "/usr/share/planefence/persist/planepix/cache/${pa_record[0]}.thumb.link" ]]; then THUMBS_ARRAY[${pa_record[0]}]="$(<"/usr/share/planefence/persist/planepix/cache/${pa_record[0]}.thumb.link")"; fi
if [[ -f "/usr/share/planefence/persist/planepix/cache/${pa_record[0]}.link" ]]; then LINKS_ARRAY[${pa_record[0]}]="$(<"/usr/share/planefence/persist/planepix/cache/${pa_record[0]}.link")"; fi
fi
fi
if [[ -n "${THUMBS_ARRAY[${pa_record[0]}]}" ]]; then
IMG="<a href=\"${LINKS_ARRAY[${pa_record[0]}]}\" target=\"_blank\"><img src=\"${THUMBS_ARRAY[${pa_record[0]}]}\" style=\"width: auto; height: 75px;\"></a>" >&3 # column: image
elif [[ -n "${FILES_ARRAY[${pa_record[0]}]}" ]]; then
IMG="<img src=\"imgcache/${FILES_ARRAY[${pa_record[0]}]##*/}\" style=\"width: auto; height: 75px;\">" >&3 # column: image
fi
fi
if [[ -z "$IMG" ]] && [[ -f /usr/share/planefence/html/plane-alert/$IMGURL ]]; then
IMG="<img src=\"$IMGURL\">"
fi
printf " %s%s%s\n" "<td style=\"padding: 0;\"><div style=\"vertical-align: middle; font-weight:bold; color:#D9EBF9; text-align:center; line-height:20px; background:none;\">" "$IMG" "</div></td><!-- image or silhouette -->" >&3
fi
if [[ -n "$BASETIME" ]] && ! (( COUNTER%10 )); then echo "10e2b. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s / $(bc -l <<< "$(date +%s.%2N) - $TABLESTARTTIME")s / $(bc -l <<< "$(date +%s.%4N) - $ITEMSTARTTIME")s -- plane-alert.sh: wrote silhouette/image for ${pa_record[0]} ($COUNTER)"; fi
printf " <td style=\"text-align: center\"><a href=\"%s\" target=\"_blank\">%s</a></td><!-- ICAO -->\n" "${pa_record[9]//globe.adsbexchange.com/"$TRACKSERVICE"}" "${pa_record[0]}" >&3 # column: ICAO
printf " <td style=\"text-align: center\"><a href=\"%s\" target=\"_blank\">%s</a></td><!-- tail -->\n" "https://flightaware.com/live/modes/${pa_record[0]}/ident/${pa_record[1]}/redirect" "${pa_record[1]}" >&3 # column: Tail
# printf " %s%s%s\n" "<td>" "${pa_record[0]}" "</td>" >&3 # column: ICAO
# printf " %s%s%s\n" "<td>" "${pa_record[1]}" "</td>" >&3 # column: Tail
printf " %s%s%s\n" "<td>" "${pa_record[2]}" "</td><!-- Owner -->" >&3 # column: Owner
printf " %s%s%s\n" "<td>" "${pa_record[3]}" "</td><!-- Plane type -->" >&3 # column: Plane Type
printf " %s%s%s\n" "<td style=\"text-align: center\">" "$(date -d "${pa_record[4]} ${pa_record[5]}" +"${NOTIF_DATEFORMAT:-%F %T %Z}")" "</td><!-- date/time -->" >&3 # column: Date Time
# printf " %s%s%s\n" "<td style=\"text-align: center\">" "<a href=\"http://www.openstreetmap.org/?mlat=${pa_record[6]}&mlon=${pa_record[7]}&zoom=$MAPZOOM\" target=\"_blank\">${pa_record[6]}N, ${pa_record[7]}E</a>" "</td>" >&3 # column: LatN, LonE
printf " %s%s%s\n" "<td style=\"text-align: center\">" "<a href=\"${pa_record[9]//globe.adsbexchange.com/"$TRACKSERVICE"}\" target=\"_blank\">${pa_record[6]}N, ${pa_record[7]}E</a>" "</td><!-- lat/lon with link to tracking service -->" >&3 # column: LatN, LonE with link to adsbexchange
printf " %s%s%s\n" "<td>" "${pa_record[8]}" "</td><!-- flight number -->" >&3 # column: Flight No
[[ "$sqx" == "true" ]] && printf " %s%s%s\n" "<td>" "${pa_record[10]}" "</td><!-- squawk -->" >&3 # column: Squawk
printf " %s%s%s\n" "<!-- td>" "<a href=\"${pa_record[9]}\" target=\"_blank\">ADSBExchange link</a>" "</td -->" >&3 # column: ADSBX link
if [[ -n "$BASETIME" ]]; then echo "10e2c. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s / $(bc -l <<< "$(date +%s.%2N) - $TABLESTARTTIME")s / $(bc -l <<< "$(date +%s.%4N) - $ITEMSTARTTIME")s -- plane-alert.sh: wrote up to lat/lon for ${pa_record[0]} ($COUNTER)"; fi
# get appropriate entry from dictionary
#for i in {4..13}
for (( i=4; i<${#header[@]}; i++ ))
do
#(( i >= ${#header[@]} )) && break # don't print headers if they don't exist
if [[ "${header[i]:0:1}" != "#" ]] && [[ "${header[i]:0:2}" != '$#' ]] && [[ "${TAGLINE[i]:0:4}" == "http" ]]
then
printf ' <td><a href=\"%s\" target=\"_blank\">%s</a></td> <!-- custom field %d -->\n' "${TAGLINE[i]}" "${TAGLINE[i]}" "$i" >&3
elif [[ "${header[i]:0:1}" != "#" ]] && [[ "${header[i]:0:2}" != '$#' ]]
then
printf ' <td>%s</td> <!-- custom field %d -->\n' "${TAGLINE[i]}" "$i" >&3
fi
done
printf "%s\n" "</tr>" >&3
if [[ -n "$BASETIME" ]] && ! (( COUNTER%10 )); then echo "10e2d. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s / $(bc -l <<< "$(date +%s.%2N) - $TABLESTARTTIME")s / $(bc -l <<< "$(date +%s.%4N) - $ITEMSTARTTIME")s -- plane-alert.sh: wrote remaining columns for ${pa_record[0]} ($COUNTER)"; fi
fi
done <<< "$OUTSTRING"
if [[ -n "$BASETIME" ]]; then echo "10e3. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: webpage - done writing table content"; fi
cat $PLANEALERTDIR/plane-alert.footer.html >&3
echo "<!-- ALERTLIST = $ALERTLIST -->" >&3
# Close the FD for $TMPDIR/plalert-index.tmp:
exec 3>&-
# Now the basics have been written, we need to replace some of the variables in the template with real data:
sed -i "s|##PA_MOTD##|$PA_MOTD|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##TRACKSERVICE##|$TRACKSERVICE|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##TABLESIZE##|$TABLESIZE|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##NAME##|$NAME|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##ADSBLINK##|$ADSBLINK|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##LASTUPDATE##|$LASTUPDATE|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##ALERTLIST##|$ALERTLIST|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##ALERTLISTUPDATE##|$(date -d "$(stat -c "%y" /usr/share/planefence/persist/.internal/plane-alert-db.txt)")|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##CONCATLIST##|$CONCATLIST|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##HISTTIME##|$HISTTIME|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##BUILD##|$([[ -f /usr/share/planefence/branch ]] && cat /usr/share/planefence/branch || cat /root/.buildtime)|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##VERSION##|$(sed -n 's/\(^\s*VERSION=\)\(.*\)/\2/p' /usr/share/planefence/planefence.conf)|g" "$TMPDIR"/plalert-index.tmp
if chk_enabled "${AUTOREFRESH,,}"; then
sed -i "s|##AUTOREFRESH##|meta http-equiv=\"refresh\" content=\"$(sed -n 's/\(^\s*PF_INTERVAL=\)\(.*\)/\2/p' /usr/share/planefence/persist/planefence.config)\"|g" "$TMPDIR"/plalert-index.tmp
else
sed -i "s|##AUTOREFRESH##|!-- autorefresh disabled--|g" "$TMPDIR"/plalert-index.tmp
fi
[[ -n "$PF_LINK" ]] && sed -i "s|##PFLINK##|<li> Additionally, click <a href=\"$PF_LINK\" target=\"_blank\">here</a> to visit Planefence: a list of aircraft heard that are within a short distance of the station.|g" "$TMPDIR"/plalert-index.tmp || sed -i "s|##PFLINK##||g" "$TMPDIR"/plalert-index.tmp
if [[ -n "$MASTODON_SERVER" && -n "$MASTODON_ACCESS_TOKEN" && -n "$MASTODON_NAME" ]]; then
sed -i "s|##MASTODONLINK##|<li>Get notified instantaneously of aircraft in range by following <a rel=\"me\" href=\"https://$MASTODON_SERVER/@$MASTODON_NAME\" target=\"_blank\">@$MASTODON_NAME</a> on the <a rel=\"me\" href=\"https://$MASTODON_SERVER/\" target=\"_blank\">$MASTODON_SERVER</a> Mastodon Server|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##MASTOHEADER##|<link href=\"https://$MASTODON_SERVER/@$MASTODON_NAME\" rel=\"me\">|g" "$TMPDIR"/plalert-index.tmp
else
sed -i "s|##MASTODONLINK##||g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##MASTOHEADER##||g" "$TMPDIR"/plalert-index.tmp
fi
if [[ -n "$BLUESKY_HANDLE" ]] && [[ -n "$BLUESKY_APP_PASSWORD" ]]; then
sed -i "s|##BLUESKYLINK##|<li>Plane-Alert notifications are sent to <a href=\"https://bsky.app/profile/$BLUESKY_HANDLE\" target=\"_blank\">@$BLUESKY_HANDLE</a> at BlueSky|g" "$TMPDIR"/plalert-index.tmp
else
sed -i "s|##BLUESKYLINK##||g" "$TMPDIR"/plalert-index.tmp
fi
if chk_enabled "$DARKMODE"; then
sed -i "s|##DARKMODE0##|class=\"dark\"|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##DARKMODE1##|background-color: black; color: white;|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##DARKMODE2##|background-color: black; color: white;|g" "$TMPDIR"/plalert-index.tmp
else
sed -i "s|##DARKMODE0##||g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##DARKMODE1##|background-image: url(\'pa_background.jpg\'); background-repeat: no-repeat; background-attachment: fixed; background-size: cover;|g" "$TMPDIR"/plalert-index.tmp
sed -i "s|##DARKMODE2##|background-color: #f0f6f6; color: black;|g" "$TMPDIR"/plalert-index.tmp
fi
if (( $(wc -l <<< "$OUTSTRING") > 1 )); then
# shellcheck disable=SC2046
sed -i "s|##MEGALINK##|<li>Click <a href=\"https://$TRACKSERVICE/?icaoFilter=$(printf "%s," $(awk -F, 'BEGIN {ORS="\n"} !seen[$1]++ {print $1}' <<< "$OUTSTRING" | tail -$TRACKLIMIT))\">here</a> for a map with the current locations of most recent $TRACKLIMIT unique aircraft|g" "$TMPDIR"/plalert-index.tmp
else
sed -i "s|##MEGALINK##||g" "$TMPDIR"/plalert-index.tmp
fi
#Finally, put the temp index into its place:
mv -f "$TMPDIR"/plalert-index.tmp "$WEBDIR"/index.html
if [[ -n "$BASETIME" ]]; then echo "10f. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- plane-alert.sh: done building webpage, finished Plane-Alert"; fi
"${s6wrap[@]}" echo "Plane-alert is done. Returning to Planefence"