#!/bin/bash ################################################################## # # File: processUpdates.sh # Note: DO NOT EDIT! this file will be overwritten on update # #### $$VERSION$$ v1.40-dev-18-g342a5da ################################################################## ############## # manage webhooks # $1 URL to sed updates to: https://host.dom[:port][/path], port and path are optional # port must be 443, 80, 88 8443, TOKEN will be added to URL for security # e.g. https://myhost.com -> https://myhost.com/12345678:azndfhbgdfbbbdsfg # $2 max connections 1-100 default 1 (because of bash ;-) set_webhook() { local url='"url": "'"$1/${BOTTOKEN}/"'"' local max=',"max_connections": 1' [[ "$2" =~ ^[0-9]+$ ]] && max=',"max_connections": '"$2"'' # shellcheck disable=SC2153 sendJson "" "${url}${max}" "${URL}/setWebhook" unset "BOTSENT[ID]" "BOTSENT[CHAT]" } get_webhook_info() { sendJson "" "" "${URL}/getWebhookInfo" if [ "${BOTSENT[OK]}" = "true" ]; then BOTSENT[URL]="${UPD[result,url]}" BOTSENT[COUNT]="${UPD[result,pending_update_count]}" BOTSENT[CERT]="${UPD[result,has_custom_certificate]}" BOTSENT[LASTERR]="${UPD[result,last_error_message]}" unset "BOTSENT[ID]" "BOTSENT[CHAT]" fi } # $1 drop pending updates true/false, default false delete_webhook() { local drop; [ "$1" = "true" ] && drop='"drop_pending_updates": true' sendJson "" "${drop}" "${URL}/deleteWebhook" unset "BOTSENT[ID]" "BOTSENT[CHAT]" } ################ # processing of updates starts here process_multi_updates() { local max num debug="$1" max="$(grep -F ',"update_id"]' <<< "${UPDATE}" | tail -1 | cut -d , -f 2 )" Json2Array 'UPD' <<<"${UPDATE}" for ((num=0; num<=max; num++)); do process_update "${num}" "${debug}" done } process_update() { local num="$1" debug="$2" pre_process_message "${num}" # log message on debug [[ -n "${debug}" ]] && log_message "New Message ==========\n$(grep -F '["result",'"${num}" <<<"${UPDATE}")" # check for users / groups to ignore jssh_updateArray_async "BASHBOTBLOCKED" "${BLOCKEDFILE}" [ -n "${USER[ID]}" ] && [[ -n "${BASHBOTBLOCKED[${USER[ID]}]}" || -n "${BASHBOTBLOCKED[${CHAT[ID]}]}" ]] && return # process per message type if [ -n "${iQUERY[ID]}" ]; then process_inline_query "${num}" "${debug}" printf "%(%c)T: Inline Query update received FROM=%s iQUERY=%s\n" -1\ "${iQUERY[USERNAME]:0:20} (${iQUERY[USER_ID]})" "${iQUERY[0]}" >>"${UPDATELOG}" elif [ -n "${iBUTTON[ID]}" ]; then process_inline_button "${num}" "${debug}" printf "%(%c)T: Inline Button update received FROM=%s CHAT=%s CALLBACK=%s DATA:%s \n" -1\ "${iBUTTON[USERNAME]:0:20} (${iBUTTON[USER_ID]})" "${iBUTTON[CHAT_ID]}" "${iBUTTON[ID]}" "${iBUTTON[DATA]}" >>"${UPDATELOG}" else if grep -qs -e '\["result",'"${num}"',"edited_message"' <<<"${UPDATE}"; then # edited message UPDATE="${UPDATE//,${num},\"edited_message\",/,${num},\"message\",}" Json2Array 'UPD' <<<"${UPDATE}" MESSAGE[0]="/_edited_message " fi process_message "${num}" "${debug}" printf "%(%c)T: update received FROM=%s CHAT=%s CMD=%s\n" -1 "${USER[USERNAME]:0:20} (${USER[ID]})"\ "${CHAT[USERNAME]:0:20}${CHAT[TITLE]:0:30} (${CHAT[ID]})"\ "${MESSAGE:0:30}${CAPTION:0:30}${URLS[*]:0:30}" >>"${UPDATELOG}" fi ##### # process inline and message events # first classic command dispatcher # shellcheck disable=SC2153,SC1090 { source "${COMMANDS}" "${debug}"; } & # then all registered addons if [ -z "${iQUERY[ID]}" ]; then event_message "${debug}" else event_inline "${debug}" fi # last count users jssh_countKeyDB_async "${CHAT[ID]}" "${COUNTFILE}" } pre_process_message(){ local num="$1" # unset everything to not have old values CMD=( ); iQUERY=( ); iBUTTON=(); MESSAGE=(); CHAT=(); USER=(); CONTACT=(); LOCATION=(); unset CAPTION REPLYTO=( ); FORWARD=( ); URLS=(); VENUE=( ); SERVICE=( ); NEWMEMBER=( ); LEFTMEMBER=( ); PINNED=( ); MIGRATE=( ) iQUERY[ID]="${UPD["result,${num},inline_query,id"]}" iBUTTON[ID]="${UPD["result,${num},callback_query,id"]}" CHAT[ID]="${UPD["result,${num},message,chat,id"]}" USER[ID]="${UPD["result,${num},message,from,id"]}" [ -z "${CHAT[ID]}" ] && CHAT[ID]="${UPD["result,${num},edited_message,chat,id"]}" [ -z "${USER[ID]}" ] && USER[ID]="${UPD["result,${num},edited_message,from,id"]}" # always true return 0 } process_inline_query() { local num="$1" iQUERY[0]="$(JsonDecode "${UPD["result,${num},inline_query,query"]}")" iQUERY[USER_ID]="${UPD["result,${num},inline_query,from,id"]}" iQUERY[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},inline_query,from,first_name"]}")" iQUERY[LAST_NAME]="$(JsonDecode "${UPD["result,${num},inline_query,from,last_name"]}")" iQUERY[USERNAME]="$(JsonDecode "${UPD["result,${num},inline_query,from,username"]}")" # always true return 0 } process_inline_button() { # debugging for impelemetation local num="$1" iBUTTON[DATA]="${UPD["result,${num},callback_query,data"]}" iBUTTON[CHAT_ID]="${UPD["result,${num},callback_query,message,chat,id"]}" iBUTTON[MESSAGE_ID]="${UPD["result,${num},callback_query,message,message_id"]}" iBUTTON[MESSAGE]="$(JsonDecode "${UPD["result,${num},callback_query,message,text"]}")" # XXX should we give back pressed button, all buttons or nothing? iBUTTON[USER_ID]="${UPD["result,${num},callback_query,from,id"]}" iBUTTON[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},callback_query,from,first_name"]}")" iBUTTON[LAST_NAME]="$(JsonDecode "${UPD["result,${num},callback_query,from,last_name"]}")" iBUTTON[USERNAME]="$(JsonDecode "${UPD["result,${num},callback_query,from,username"]}")" # always true return 0 } process_message() { local num="$1" # Message MESSAGE[0]+="$(JsonDecode "${UPD["result,${num},message,text"]}" | sed 's|\\/|/|g')" MESSAGE[ID]="${UPD["result,${num},message,message_id"]}" # Chat ID is now parsed when update is received CHAT[LAST_NAME]="$(JsonDecode "${UPD["result,${num},message,chat,last_name"]}")" CHAT[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},message,chat,first_name"]}")" CHAT[USERNAME]="$(JsonDecode "${UPD["result,${num},message,chat,username"]}")" # set real name as username if empty [ -z "${CHAT[USERNAME]}" ] && CHAT[USERNAME]="${CHAT[FIRST_NAME]} ${CHAT[LAST_NAME]}" CHAT[TITLE]="$(JsonDecode "${UPD["result,${num},message,chat,title"]}")" CHAT[TYPE]="$(JsonDecode "${UPD["result,${num},message,chat,type"]}")" CHAT[ALL_ADMIN]="${UPD["result,${num},message,chat,all_members_are_administrators"]}" # user ID is now parsed when update is received USER[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},message,from,first_name"]}")" USER[LAST_NAME]="$(JsonDecode "${UPD["result,${num},message,from,last_name"]}")" USER[USERNAME]="$(JsonDecode "${UPD["result,${num},message,from,username"]}")" # set real name as username if empty [ -z "${USER[USERNAME]}" ] && USER[USERNAME]="${USER[FIRST_NAME]} ${USER[LAST_NAME]}" # in reply to message from if [ -n "${UPD["result,${num},message,reply_to_message,from,id"]}" ]; then REPLYTO[UID]="${UPD["result,${num},message,reply_to_message,from,id"]}" REPLYTO[0]="$(JsonDecode "${UPD["result,${num},message,reply_to_message,text"]}")" REPLYTO[ID]="${UPD["result,${num},message,reply_to_message,message_id"]}" REPLYTO[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},message,reply_to_message,from,first_name"]}")" REPLYTO[LAST_NAME]="$(JsonDecode "${UPD["result,${num},message,reply_to_message,from,last_name"]}")" REPLYTO[USERNAME]="$(JsonDecode "${UPD["result,${num},message,reply_to_message,from,username"]}")" fi # forwarded message from if [ -n "${UPD["result,${num},message,forward_from,id"]}" ]; then FORWARD[UID]="${UPD["result,${num},message,forward_from,id"]}" FORWARD[ID]="${MESSAGE[ID]}" # same as message ID FORWARD[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},message,forward_from,first_name"]}")" FORWARD[LAST_NAME]="$(JsonDecode "${UPD["result,${num},message,forward_from,last_name"]}")" FORWARD[USERNAME]="$(JsonDecode "${UPD["result,${num},message,forward_from,username"]}")" fi # get file URL from telegram, check for any of them! if grep -qs -e '\["result",'"${num}"',"message","[avpsd].*,"file_id"\]' <<<"${UPDATE}"; then URLS[AUDIO]="$(get_file "${UPD["result,${num},message,audio,file_id"]}")" URLS[DOCUMENT]="$(get_file "${UPD["result,${num},message,document,file_id"]}")" URLS[PHOTO]="$(get_file "${UPD["result,${num},message,photo,0,file_id"]}")" URLS[STICKER]="$(get_file "${UPD["result,${num},message,sticker,file_id"]}")" URLS[VIDEO]="$(get_file "${UPD["result,${num},message,video,file_id"]}")" URLS[VOICE]="$(get_file "${UPD["result,${num},message,voice,file_id"]}")" fi # Contact, must have phone_number if [ -n "${UPD["result,${num},message,contact,phone_number"]}" ]; then CONTACT[USER_ID]="$(JsonDecode "${UPD["result,${num},message,contact,user_id"]}")" CONTACT[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},message,contact,first_name"]}")" CONTACT[LAST_NAME]="$(JsonDecode "${UPD["result,${num},message,contact,last_name"]}")" CONTACT[NUMBER]="${UPD["result,${num},message,contact,phone_number"]}" CONTACT[VCARD]="${UPD["result,${num},message,contact,vcard"]}" fi # venue, must have a position if [ -n "${UPD["result,${num},message,venue,location,longitude"]}" ]; then VENUE[TITLE]="$(JsonDecode "${UPD["result,${num},message,venue,title"]}")" VENUE[ADDRESS]="$(JsonDecode "${UPD["result,${num},message,venue,address"]}")" VENUE[LONGITUDE]="${UPD["result,${num},message,venue,location,longitude"]}" VENUE[LATITUDE]="${UPD["result,${num},message,venue,location,latitude"]}" VENUE[FOURSQUARE]="${UPD["result,${num},message,venue,foursquare_id"]}" fi # Caption CAPTION="$(JsonDecode "${UPD["result,${num},message,caption"]}")" # Location LOCATION[LONGITUDE]="${UPD["result,${num},message,location,longitude"]}" LOCATION[LATITUDE]="${UPD["result,${num},message,location,latitude"]}" # service messages, group or channel only! if [[ "${CHAT[ID]}" == "-"* ]] ; then # new chat member if [ -n "${UPD["result,${num},message,new_chat_member,id"]}" ]; then SERVICE[NEWMEMBER]="${UPD["result,${num},message,new_chat_member,id"]}" NEWMEMBER[ID]="${SERVICE[NEWMEMBER]}" NEWMEMBER[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},message,new_chat_member,first_name"]}")" NEWMEMBER[LAST_NAME]="$(JsonDecode "${UPD["result,${num},message,new_chat_member,last_name"]}")" NEWMEMBER[USERNAME]="$(JsonDecode "${UPD["result,${num},message,new_chat_member,username"]}")" NEWMEMBER[ISBOT]="${UPD["result,${num},message,new_chat_member,is_bot"]}" [ -z "${MESSAGE[0]}" ] &&\ MESSAGE[0]="/_new_chat_member ${NEWMEMBER[ID]} ${NEWMEMBER[USERNAME]:=${NEWMEMBER[FIRST_NAME]} ${NEWMEMBER[LAST_NAME]}}" fi # left chat member if [ -n "${UPD["result,${num},message,left_chat_member,id"]}" ]; then SERVICE[LEFTMEMBER]="${UPD["result,${num},message,left_chat_member,id"]}" LEFTMEMBER[ID]="${SERVICE[LEFTMEBER]}" LEFTMEMBER[FIRST_NAME]="$(JsonDecode "${UPD["result,${num},message,left_chat_member,first_name"]}")" LEFTMEMBER[LAST_NAME]="$(JsonDecode "${UPD["result,${num},message,left_chat_member,last_name"]}")" LEFTMEBER[USERNAME]="$(JsonDecode "${UPD["result,${num},message,left_chat_member,username"]}")" LEFTMEMBER[ISBOT]="${UPD["result,${num},message,left_chat_member,is_bot"]}" [ -z "${MESSAGE[0]}" ] &&\ MESSAGE[0]="/_left_chat_member ${LEFTMEMBER[ID]} ${LEFTMEMBER[USERNAME]:=${LEFTMEMBER[FIRST_NAME]} ${LEFTMEMBER[LAST_NAME]}}" fi # chat title / photo, check for any of them! if grep -qs -e '\["result",'"${num}"',"message","new_chat_[tp]' <<<"${UPDATE}"; then SERVICE[NEWTITLE]="$(JsonDecode "${UPD["result,${num},message,new_chat_title"]}")" [ -z "${MESSAGE[0]}" ] && [ -n "${SERVICE[NEWTITLE]}" ] &&\ MESSAGE[0]="/_new_chat_title ${USER[ID]} ${SERVICE[NEWTITLE]}" SERVICE[NEWPHOTO]="$(get_file "${UPD["result,${num},message,new_chat_photo,0,file_id"]}")" [ -z "${MESSAGE[0]}" ] && [ -n "${SERVICE[NEWPHOTO]}" ] &&\ MESSAGE[0]="/_new_chat_photo ${USER[ID]} ${SERVICE[NEWPHOTO]}" fi # pinned message if [ -n "${UPD["result,${num},message,pinned_message,message_id"]}" ]; then SERVICE[PINNED]="${UPD["result,${num},message,pinned_message,message_id"]}" PINNED[ID]="${SERVICE[PINNED]}" PINNED[MESSAGE]="$(JsonDecode "${UPD["result,${num},message,pinned_message,text"]}")" [ -z "${MESSAGE[0]}" ] &&\ MESSAGE[0]="/_new_pinned_message ${USER[ID]} ${PINNED[ID]} ${PINNED[MESSAGE]}" fi # migrate to super group if [ -n "${UPD["result,${num},message,migrate_to_chat_id"]}" ]; then MIGRATE[TO]="${UPD["result,${num},message,migrate_to_chat_id"]}" MIGRATE[FROM]="${UPD["result,${num},message,migrate_from_chat_id"]}" # CHAT is already migrated, so set new chat id [ "${CHAT[ID]}" = "${MIGRATE[FROM]}" ] && CHAT[ID]="${MIGRATE[FROM]}" SERVICE[MIGRATE]="${MIGRATE[FROM]} ${MIGRATE[TO]}" [ -z "${MESSAGE[0]}" ] &&\ MESSAGE[0]="/_migrate_group ${SERVICE[MIGRATE]}" fi # set SERVICE to yes if a service message was received [[ "${SERVICE[*]}" =~ ^[[:blank:]]*$ ]] || SERVICE[0]="yes" fi # split message in command and args [[ "${MESSAGE[0]}" == "/"* ]] && read -r CMD <<<"${MESSAGE[0]}" && CMD[0]="${CMD[0]%%@*}" # everything went well return 0 } ######################### # main get updates loop, should never terminate declare -A BASHBOTBLOCKED start_bot() { local DEBUGMSG # startup message DEBUGMSG="Start BASHBOT updates in Mode \"${1:-normal}\" ==========" log_update "${DEBUGMSG}" # redirect to Debug.log # shellcheck disable=SC2153 [[ "$1" == *"debug" ]] && exec &>>"${DEBUGLOG}" log_debug "${DEBUGMSG}"; DEBUGMSG="$1" [[ "${DEBUGMSG}" == "xdebug"* ]] && set -x # cleaup old pipes and empty logfiles find "${DATADIR}" -type p -delete find "${DATADIR}" -size 0 -name "*.log" -delete # load addons on startup for addons in "${ADDONDIR:-.}"/*.sh ; do # shellcheck disable=SC1090 [ -r "${addons}" ] && source "${addons}" "startbot" "${DEBUGMSG}" done # shellcheck disable=SC1090 source "${COMMANDS}" "startbot" # start timer events if [ -n "${BASHBOT_START_TIMER}" ] ; then # shellcheck disable=SC2064 trap "event_timer ${DEBUGMSG}" ALRM start_timer & # shellcheck disable=SC2064 trap "kill -9 $!; exit" EXIT INT HUP TERM QUIT fi # cleanup countfile on startup jssh_deleteKeyDB "CLEAN_COUNTER_DATABASE_ON_STARTUP" "${COUNTFILE}" [ -f "${COUNTFILE}.jssh.flock" ] && rm -f "${COUNTFILE}.jssh.flock" # store start time and cleanup botconfig on startup jssh_updateKeyDB "startup" "$(_date)" "${BOTCONFIG}" [ -f "${BOTCONFIG}.jssh.flock" ] && rm -f "${BOTCONFIG}.jssh.flock" # read blocked users jssh_readDB_async "BASHBOTBLOCKED" "${BLOCKEDFILE}" # inform botadmin about start send_normal_message "$(getConfigKey "botadmin")" "Bot $(getConfigKey "botname") started ..." & ########## # bot is ready, start processing updates ... get_updates "${DEBUGMSG}" } get_updates(){ local errsleep="200" DEBUG="$1" OFFSET=0 # adaptive sleep defaults local nextsleep="100" local stepsleep="${BASHBOT_SLEEP_STEP:-100}" local maxsleep="${BASHBOT_SLEEP:-5000}" while true; do # adaptive sleep in ms rounded to next 0.1 s sleep "$(_round_float "${nextsleep}e-3" "1")" # get next update # shellcheck disable=SC2153 UPDATE="$(getJson "${URL}/getUpdates?offset=${OFFSET}" 2>/dev/null | "${JSONSHFILE}" -b -n 2>/dev/null | iconv -f utf-8 -t utf-8 -c)" # did we get an response? if [ -n "${UPDATE}" ]; then # we got something, do processing [ "${OFFSET}" = "-999" ] && [ "${nextsleep}" -gt "$((maxsleep/2))" ] &&\ log_error "Recovered from timeout/broken/no connection, continue with telegram updates" # calculate next sleep interval ((nextsleep+= stepsleep , nextsleep= nextsleep>maxsleep ?maxsleep:nextsleep)) # escape bash $ expansion bug UPDATE="${UPDATE//$/\\$}" # warn if webhook is set if grep -q '^\["error_code"\] 409' <<<"${UPDATE}"; then OFFSET="-999"; errsleep="$(_round_float "$(( errsleep = 200 * nextsleep ))e-3" "1")" log_error "Warning conflicting webhook set, can't get updates until delete_webhook! Sleep ${errsleep}s ..." sleep "${errsleep}" continue fi # Offset OFFSET="$(grep <<<"${UPDATE}" '\["result",[0-9]*,"update_id"\]' | tail -1 | cut -f 2)" ((OFFSET++)) if [ "${OFFSET}" != "1" ]; then nextsleep="100" process_multi_updates "${DEBUG}" fi else # oops, something bad happened, wait maxsleep*10 (( nextsleep=nextsleep*2 , nextsleep= nextsleep>maxsleep*10 ?maxsleep*10:nextsleep )) # second time, report problem if [ "${OFFSET}" = "-999" ]; then log_error "Repeated timeout/broken/no connection on telegram update, sleep $(_round_float "${nextsleep}e-3")s" # try to recover if _is_function bashbotBlockRecover && [ -z "$(getJson "${ME_URL}")" ]; then log_error "Try to recover, calling bashbotBlockRecover ..." bashbotBlockRecover >>"${ERRORLOG}" fi fi OFFSET="-999" fi done } declare -Ax BASHBOT_EVENT_INLINE BASHBOT_EVENT_MESSAGE BASHBOT_EVENT_CMD BASHBOT_EVENT_REPLYTO BASHBOT_EVENT_FORWARD BASHBOT_EVENT_SEND declare -Ax BASHBOT_EVENT_CONTACT BASHBOT_EVENT_LOCATION BASHBOT_EVENT_FILE BASHBOT_EVENT_TEXT BASHBOT_EVENT_TIMER BASHBOT_BLOCKED start_timer(){ # send alarm every ~60 s while :; do sleep 59.5 kill -ALRM $$ done; } EVENT_TIMER="0" event_timer() { local key timer debug="$1" (( EVENT_TIMER++ )) # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_TIMER[@]}" do timer="${key##*,}" [[ ! "${timer}" =~ ^-*[1-9][0-9]*$ ]] && continue if [ "$(( EVENT_TIMER % timer ))" = "0" ]; then _exec_if_function "${BASHBOT_EVENT_TIMER[${key}]}" "timer" "${key}" "${debug}" [ "$(( EVENT_TIMER % timer ))" -lt "0" ] && \ unset BASHBOT_EVENT_TIMER["${key}"] fi done } event_inline() { local key debug="$1" # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_INLINE[@]}" do _exec_if_function "${BASHBOT_EVENT_INLINE[${key}]}" "inline" "${key}" "${debug}" done } event_message() { local key debug="$1" # ${MESSAEG[*]} event_message # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_MESSAGE[@]}" do _exec_if_function "${BASHBOT_EVENT_MESSAGE[${key}]}" "message" "${key}" "${debug}" done # ${TEXT[*]} event_text if [ -n "${MESSAGE[0]}" ]; then # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_TEXT[@]}" do _exec_if_function "${BASHBOT_EVENT_TEXT[${key}]}" "text" "${key}" "${debug}" done # ${CMD[*]} event_cmd if [ -n "${CMD[0]}" ]; then # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_CMD[@]}" do _exec_if_function "${BASHBOT_EVENT_CMD[${key}]}" "command" "${key}" "${debug}" done fi fi # ${REPLYTO[*]} event_replyto if [ -n "${REPLYTO[UID]}" ]; then # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_REPLYTO[@]}" do _exec_if_function "${BASHBOT_EVENT_REPLYTO[${key}]}" "replyto" "${key}" "${debug}" done fi # ${FORWARD[*]} event_forward if [ -n "${FORWARD[UID]}" ]; then # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_FORWARD[@]}" do _exec_if_function && "${BASHBOT_EVENT_FORWARD[${key}]}" "forward" "${key}" "${debug}" done fi # ${CONTACT[*]} event_contact if [ -n "${CONTACT[FIRST_NAME]}" ]; then # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_CONTACT[@]}" do _exec_if_function "${BASHBOT_EVENT_CONTACT[${key}]}" "contact" "${key}" "${debug}" done fi # ${VENUE[*]} event_location # ${LOCATION[*]} event_location if [ -n "${LOCATION[LONGITUDE]}" ] || [ -n "${VENUE[TITLE]}" ]; then # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_LOCATION[@]}" do _exec_if_function "${BASHBOT_EVENT_LOCATION[${key}]}" "location" "${key}" "${debug}" done fi # ${URLS[*]} event_file # NOTE: compare again #URLS -1 blanks! if [[ "${URLS[*]}" != " " ]]; then # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_FILE[@]}" do _exec_if_function "${BASHBOT_EVENT_FILE[${key}]}" "file" "${key}" "${debug}" done fi }