diff --git a/bashbot.sh b/bashbot.sh index e48be1a..78090fd 100755 --- a/bashbot.sh +++ b/bashbot.sh @@ -30,7 +30,7 @@ BOTCOMMANDS="-h help init start stop status suspendback resumeback killb # 8 - curl/wget missing # 10 - not bash! # -#### $$VERSION$$ v1.35-dev-31-ga3eec98 +#### $$VERSION$$ v1.40-dev-3-g7c74824 ################################################################## # emmbeded system may claim bash but it is not @@ -467,6 +467,7 @@ sendJson(){ [ -n "${BASHBOT_EVENT_SEND[*]}" ] && event_send "send" "${@}" & } + # # curl / wget specific functions # @@ -662,77 +663,8 @@ JsonDecode() { } -################ -# processing of updates starts here -process_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_client "${num}" "${debug}" - done -} - -process_client() { - 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 source=./commands.sh - { 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}" -} - -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_SEND="0" +declare -Ax BASHBOT_EVENT_SEND event_send() { # max recursion level 5 to avoid fork bombs (( EVENT_SEND++ )); [ "${EVENT_SEND}" -gt "5" ] && return @@ -743,364 +675,6 @@ event_send() { 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 - -} -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 OFFSET=0 - # adaptive sleep defaults - local nextsleep="100" - local stepsleep="${BASHBOT_SLEEP_STEP:-100}" - local maxsleep="${BASHBOT_SLEEP:-5000}" - # startup message - DEBUGMSG="Start BASHBOT updates in Mode \"${1:-normal}\" ==========" - log_update "${DEBUGMSG}" - # redirect to Debug.log - [[ "$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 source=./modules/aliases.sh - [ -r "${addons}" ] && source "${addons}" "startbot" "${DEBUGMSG}" - done - # shellcheck source=./commands.sh - 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 ... - while true; do - # adaptive sleep in ms rounded to next 0.1 s - sleep "$(_round_float "${nextsleep}e-3" "1")" - # get next update - 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" - # escape bash $ expansion bug - ((nextsleep+= stepsleep , nextsleep= nextsleep>maxsleep ?maxsleep:nextsleep)) - UPDATE="${UPDATE//$/\\$}" - # Offset - OFFSET="$(grep <<< "${UPDATE}" '\["result",[0-9]*,"update_id"\]' | tail -1 | cut -f 2)" - ((OFFSET++)) - - if [ "${OFFSET}" != "1" ]; then - nextsleep="100" - process_updates "${DEBUGMSG}" - 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 -} # fallback version, full version is in bin/bashbot_init.in.sh # initialize bot environment, user and permissions @@ -1178,9 +752,9 @@ if [ -z "${SOURCE}" ]; then debug_checks "end outproc" "$@" exit ;; - # finally starts the read update loop, internal use only1 + # finally starts the read update loop, internal use only "startbot" ) - start_bot "$2" + _exec_if_exist start_bot "$2" debug_checks "end startbot" "$@" exit ;; @@ -1221,14 +795,18 @@ if [ -z "${SOURCE}" ]; then # start bot as background job and check if bot is running "start") - # shellcheck disable=SC2086 - SESSION="${ME:-_bot}-startbot" - BOTPID="$(proclist "${SESSION}")" - # shellcheck disable=SC2086 - [ -n "${BOTPID}" ] && kill ${BOTPID} - nohup "${SCRIPT}" "startbot" "$2" "${SESSION}" &>/dev/null & - printf "Session Name: %s\n" "${SESSION}" - sleep 1 + if _is_function process_updates; then + # shellcheck disable=SC2086 + SESSION="${ME:-_bot}-startbot" + BOTPID="$(proclist "${SESSION}")" + # shellcheck disable=SC2086 + [ -n "${BOTPID}" ] && kill ${BOTPID} + nohup "${SCRIPT}" "startbot" "$2" "${SESSION}" &>/dev/null & + printf "Session Name: %s\n" "${SESSION}" + sleep 1 + else + printf "${ORANGE}Update processing disabled, bot can only send messages.${NN}" + fi if [ -n "$(proclist "${SESSION}")" ]; then printf "${GREEN}Bot started successfully.${NN}" else diff --git a/modules/jsonDB.sh b/modules/jsonDB.sh index 9169aa8..5a78bb3 100644 --- a/modules/jsonDB.sh +++ b/modules/jsonDB.sh @@ -5,7 +5,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.31-dev-13-g127cc85 +#### $$VERSION$$ v1.40-dev-3-g7c74824 # # source from commands.sh to use jsonDB functions # diff --git a/modules/processUpdates.sh b/modules/processUpdates.sh new file mode 100644 index 0000000..52afda4 --- /dev/null +++ b/modules/processUpdates.sh @@ -0,0 +1,446 @@ +#!/bin/bash +################################################################## +# +# File: processUpdates.sh +# Note: DO NOT EDIT! this file will be overwritten on update +# +#### $$VERSION$$ v1.40-dev-3-g7c74824 +################################################################## + +################ +# processing of updates starts here +process_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_client "${num}" "${debug}" + done +} + +process_client() { + 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 OFFSET=0 + # adaptive sleep defaults + local nextsleep="100" + local stepsleep="${BASHBOT_SLEEP_STEP:-100}" + local maxsleep="${BASHBOT_SLEEP:-5000}" + # 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 ... + 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" + # escape bash $ expansion bug + ((nextsleep+= stepsleep , nextsleep= nextsleep>maxsleep ?maxsleep:nextsleep)) + UPDATE="${UPDATE//$/\\$}" + # Offset + OFFSET="$(grep <<< "${UPDATE}" '\["result",[0-9]*,"update_id"\]' | tail -1 | cut -f 2)" + ((OFFSET++)) + + if [ "${OFFSET}" != "1" ]; then + nextsleep="100" + process_updates "${DEBUGMSG}" + 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 + +} +