diff --git a/README.html b/README.html index b89a8c7..4d7dfc4 100644 --- a/README.html +++ b/README.html @@ -11,14 +11,15 @@ -

bashbot

-

A Telegram bot written in bash.

+

+ Bashbot - A Telegram bot written in bash. +

Written by Drew (@topkecleon), Daniil Gentili (@danogentili), and Kay M (@gnadelwartz).

Contributions by JuanPotato, BigNerd95, TiagoDanin, and iicc1.

Released to the public domain wherever applicable. Elsewhere, consider it released under the WTFPLv2.

Prerequisites

Depends on tmux. Uses JSON.sh.

-

Most complete UTF-8 support for bashbot is availible if phyton is installed (optional).

+

Even bashbot is written in bash, it depends on commands typically availible in a Unix/Linux Environment. More concret on the common commands provided by coreutils, busybox or toybox, see Developer Notes

Bashbot Documentation and Downloads are availible on www.github.com

Documentation

+

You don’t like the many bashbot files?

+

At the beginning bashbot was simply the file bashbot.sh I can copy everywhere and run the bot. Now we have ‘commands.sh’, ‘mycommands.sh’, ’modules/*.sh’ and much more.

+

Hey no Problem, if you are finished with your cool bot simply run dev/make-standalone.sh to create a stripped down Version containing only ‘bashbot.sh’ and ‘commands.sh’! For more information see Create a stripped down Version of your Bot

Security Considerations

Running a Telegram Bot means it is connected to the public and you never know whats send to your Bot.

Bash scripts in general are not designed to be bullet proof, so consider this Bot as a proof of concept. Bash programmers often struggle with ‘quoting hell’ and globbing, see Implications of wrong quoting

@@ -103,6 +107,6 @@

@Gnadelwartz

That’s it!

If you feel that there’s something missing or if you found a bug, feel free to submit a pull request!

-


VERSION
v0.72-1-g67c47ac

+


VERSION
v0.76-1-ge8a1fd0

diff --git a/README.md b/README.md index 933f838..b27c8ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# bashbot -A Telegram bot written in bash. - +

+Bashbot - A Telegram bot written in bash. +

Written by Drew (@topkecleon), Daniil Gentili (@danogentili), and Kay M (@gnadelwartz). Contributions by JuanPotato, BigNerd95, TiagoDanin, and iicc1. @@ -12,7 +12,9 @@ Elsewhere, consider it released under the [WTFPLv2](http://www.wtfpl.net/txt/cop Depends on [tmux](http://github.com/tmux/tmux). Uses [JSON.sh](http://github.com/dominictarr/JSON.sh). -Most complete [UTF-8 support for bashbot](doc/4_expert.md#Bashbot-UTF-8-Support) is availible if phyton is installed (optional). +Even bashbot is written in bash, it depends on commands typically availible in a Unix/Linux Environment. +More concret on the common commands provided by [coreutils](https://en.wikipedia.org/wiki/List_of_GNU_Core_Utilities_commands), [busybox](https://en.wikipedia.org/wiki/BusyBox#Commands) or [toybox](https://landley.net/toybox/help.html), see [Developer Notes](doc/7_develop.md#common-commands) + Bashbot [Documentation](https://github.com/topkecleon/telegram-bot-bash) and [Downloads](https://github.com/topkecleon/telegram-bot-bash/releases) are availible on www.github.com @@ -57,6 +59,13 @@ Bashbot [Documentation](https://github.com/topkecleon/telegram-bot-bash) and [Do * [Customize bashbot environment](doc/8_custom.md) * [Examples](examples/README.md) + +#### You don't like the many bashbot files? +At the beginning bashbot was simply the file ```bashbot.sh``` I can copy everywhere and run the bot. Now we have 'commands.sh', 'mycommands.sh', 'modules/*.sh' and much more. + +Hey no Problem, if you are finished with your cool bot simply run ```dev/make-standalone.sh``` to create a stripped down Version containing only +'bashbot.sh' and 'commands.sh'! For more information see [Create a stripped down Version of your Bot](doc/7_develop.md) + ## Security Considerations Running a Telegram Bot means it is connected to the public and you never know whats send to your Bot. @@ -98,4 +107,4 @@ Well, thats a damn good question ... may be because I'm an Unix/Linux admin from If you feel that there's something missing or if you found a bug, feel free to submit a pull request! -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/README.txt b/README.txt index 288c2d4..b394f5b 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,8 @@ -# bashbot -A Telegram bot written in bash. - +

+Bashbot - A Telegram bot written in bash. +

Written by Drew (@topkecleon), Daniil Gentili (@danogentili), and Kay M (@gnadelwartz). @@ -14,9 +16,14 @@ Elsewhere, consider it released under the Depends on [tmux](http://github.com/tmux/tmux). Uses [JSON.sh](http://github.com/dominictarr/JSON.sh). -Most complete [UTF-8 support for -bashbot](doc/4_expert.md#Bashbot-UTF-8-Support) is availible if phyton is -installed (optional). +Even bashbot is written in bash, it depends on commands typically availible in +a Unix/Linux Environment. +More concret on the common commands provided by +[coreutils](https://en.wikipedia.org/wiki/List_of_GNU_Core_Utilities_commands), +[busybox](https://en.wikipedia.org/wiki/BusyBox#Commands) or +[toybox](https://landley.net/toybox/help.html), see [Developer +Notes](doc/7_develop.md#common-commands) + Bashbot [Documentation](https://github.com/topkecleon/telegram-bot-bash) and [Downloads](https://github.com/topkecleon/telegram-bot-bash/releases) are @@ -64,6 +71,17 @@ all](https://core.telegram.org/bots#3-how-do-i-create-a-bot) * [Customize bashbot environment](doc/8_custom.md) * [Examples](examples/README.md) + +#### You don't like the many bashbot files? +At the beginning bashbot was simply the file ```bashbot.sh``` I can copy +everywhere and run the bot. Now we have 'commands.sh', 'mycommands.sh', +'modules/*.sh' and much more. + +Hey no Problem, if you are finished with your cool bot simply run +```dev/make-standalone.sh``` to create a stripped down Version containing only +'bashbot.sh' and 'commands.sh'! For more information see [Create a stripped +down Version of your Bot](doc/7_develop.md) + ## Security Considerations Running a Telegram Bot means it is connected to the public and you never know whats send to your Bot. @@ -137,4 +155,4 @@ health status If you feel that there's something missing or if you found a bug, feel free to submit a pull request! -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/bashbot.rc b/bashbot.rc index 309696a..19d1837 100755 --- a/bashbot.rc +++ b/bashbot.rc @@ -1,7 +1,7 @@ #!/bin/sh # description: Start or stop telegram-bash-bot # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # shellcheck disable=SC2009 # shellcheck disable=SC2181 diff --git a/bashbot.sh b/bashbot.sh index b501600..3e7410e 100755 --- a/bashbot.sh +++ b/bashbot.sh @@ -12,7 +12,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # # Exit Codes: # - 0 sucess (hopefully) @@ -32,6 +32,7 @@ if [ -t 1 ] && [ "$TERM" != "" ]; then fi # get location and name of bashbot.sh +export SCRIPT SCRIPTDIR MODULEDIR RUNDIR RUNUSER SCRIPT="$0" SCRIPTDIR="$(dirname "$0")" MODULEDIR="${SCRIPTDIR}/modules" @@ -65,16 +66,6 @@ if [ ! -f "${TOKENFILE}" ]; then fi fi -JSONSHFILE="${BASHBOT_JSONSH:-${RUNDIR}/JSON.sh/JSON.sh}" -[[ "${JSONSHFILE}" != *"/JSON.sh" ]] && echo -e "${RED}ERROR: \"${JSONSHFILE}\" ends not with \"JSONS.sh\".${NC}" && exit 3 - -if [ ! -f "${JSONSHFILE}" ]; then - echo "Seems to be first run, Downloading ${JSONSHFILE}..." - [[ "${JSONSHFILE}" = "${RUNDIR}/JSON.sh/JSON.sh" ]] && mkdir "JSON.sh" 2>/dev/null; - curl -sL -o "${JSONSHFILE}" "https://cdn.jsdelivr.net/gh/dominictarr/JSON.sh/JSON.sh" - chmod +x "${JSONSHFILE}" -fi - BOTADMIN="${BASHBOT_ETC:-.}/botadmin" if [ ! -f "${BOTADMIN}" ]; then if [ "${CLEAR}" = "" ]; then @@ -118,33 +109,17 @@ elif [ ! -w "${COUNTFILE}" ]; then fi -BOTTOKEN="$(cat "${TOKENFILE}")" -URL='https://api.telegram.org/bot'$BOTTOKEN +BOTTOKEN="$(< "${TOKENFILE}")" +URL="${BASHBOT_URL:-https://api.telegram.org/bot}${BOTTOKEN}" -MSG_URL=$URL'/sendMessage' -LEAVE_URL=$URL'/leaveChat' -KICK_URL=$URL'/kickChatMember' -UNBAN_URL=$URL'/unbanChatMember' -PHO_URL=$URL'/sendPhoto' -AUDIO_URL=$URL'/sendAudio' -DOCUMENT_URL=$URL'/sendDocument' -STICKER_URL=$URL'/sendSticker' -VIDEO_URL=$URL'/sendVideo' -VOICE_URL=$URL'/sendVoice' -LOCATION_URL=$URL'/sendLocation' -VENUE_URL=$URL'/sendVenue' -ACTION_URL=$URL'/sendChatAction' -FORWARD_URL=$URL'/forwardMessage' ME_URL=$URL'/getMe' -DELETE_URL=$URL'/deleteMessage' -GETMEMBER_URL=$URL'/getChatMember' UPD_URL=$URL'/getUpdates?offset=' GETFILE_URL=$URL'/getFile' unset USER -declare -A BOTSENT USER MESSAGE URLS CONTACT LOCATION CHAT FORWARD REPLYTO VENUE -export BOTSENT USER MESSAGE URLS CONTACT LOCATION CHAT FORWARD REPLYTO VENUE +declare -A BOTSENT USER MESSAGE URLS CONTACT LOCATION CHAT FORWARD REPLYTO VENUE iQUERY +export res BOTSENT USER MESSAGE URLS CONTACT LOCATION CHAT FORWARD REPLYTO VENUE iQUERY CAPTION NAME COMMANDS="${BASHBOT_ETC:-.}/commands.sh" if [ "$1" != "source" ]; then @@ -159,126 +134,60 @@ if [ "$1" != "source" ]; then fi -send_normal_message() { - local text="${2}" - until [ -z "${text}" ]; do - sendJson "${1}" '"text":"'"${text:0:4096}"'"' "${MSG_URL}" - text="${text:4096}" - done +# returns true if command exist +_exists() +{ + [ "$(LC_ALL=C type -t "$1")" = "file" ] } -send_markdown_message() { - local text="${2}" - until [ -z "${text}" ]; do - sendJson "${1}" '"text":"'"${text:0:4096}"'","parse_mode":"markdown"' "${MSG_URL}" - text="${text:4096}" - done -} - -send_html_message() { - local text="${2}" - until [ -z "${text}" ]; do - sendJson "${1}" '"text":"'"${text:0:4096}"'","parse_mode":"html"' "${MSG_URL}" - text="${text:4096}" - done +# returns true if function exist +_is_function() +{ + [ "$(LC_ALL=C type -t "$1")" = "function" ] } +DELETE_URL=$URL'/deleteMessage' delete_message() { sendJson "${1}" 'message_id: '"${2}"'' "${DELETE_URL}" } -# usage: status="$(get_chat_member_status "chat" "user")" -get_chat_member_status() { - sendJson "$1" 'user_id: '"$2"'' "$GETMEMBER_URL" - JsonGetString '"result","status"' <<< "$res" +get_file() { + [ "$1" = "" ] && return + local JSON='"file_id": '"${1}" + sendJson "" "${JSON}" "${GETFILE_URL}" + jsonGetString <<< "${URL}/""${res}" '"result","file_path"' } -kick_chat_member() { - sendJson "$1" 'user_id: '"$2"'' "$KICK_URL" -} - -unban_chat_member() { - sendJson "$1" 'user_id: '"$2"'' "$UNBAN_URL" -} - -leave_chat() { - sendJson "$1" "" "$LEAVE_URL" -} - -user_is_creator() { - if [ "${1:--}" = "${2:-+}" ] || [ "$(get_chat_member_status "$1" "$2")" = "creator" ]; then return 0; fi - return 1 -} - -user_is_admin() { - local me; me="$(get_chat_member_status "$1" "$2")" - if [ "${me}" = "creator" ] || [ "${me}" = "administrator" ]; then return 0; fi - return 1 -} - -user_is_botadmin() { - local admin; admin="$(head -n 1 "${BOTADMIN}")" - [ "${admin}" = "${1}" ] && return 0 - [[ "${admin}" = "@*" ]] && [[ "${admin}" = "${2}" ]] && return 0 - if [ "${admin}" = "?" ]; then echo "${1:-?}" >"${BOTADMIN}"; return 0; fi - return 1 -} - -user_is_allowed() { - local acl="$1" - [ "$1" = "" ] && return 1 - grep -F -xq "${acl}:*:*" <"${BOTACL}" && return 0 - [ "$2" != "" ] && acl="${acl}:$2" - grep -F -xq "${acl}:*" <"${BOTACL}" && return 0 - [ "$3" != "" ] && acl="${acl}:$3" - grep -F -xq "${acl}" <"${BOTACL}" -} - -old_send_keyboard() { - local text='"text":"'"${2}"'"' - shift 2 - local keyboard=init - OLDIFS=$IFS - IFS=$(echo -en "\"") - for f in "$@" ;do [ "$f" != " " ] && keyboard="$keyboard, [\"$f\"]";done - IFS=$OLDIFS - keyboard=${keyboard/init, /} - sendJson "${1}" "${text}"', "reply_markup": {"keyboard": [ '"${keyboard}"' ],"one_time_keyboard": true}' "$MSG_URL" -} - -ISEMPTY="ThisTextIsEmptyAndWillBeDeleted" -send_keyboard() { - if [[ "$3" != *'['* ]]; then old_send_keyboard "$@"; return; fi - local text='"text":"'"${2}"'"'; [ "${2}" = "" ] && text='"text":"'"${ISEMPTY}"'"' - local one_time=', "one_time_keyboard":true' && [ "$4" != "" ] && one_time="" - sendJson "${1}" "${text}"', "reply_markup": {"keyboard": [ '"${3}"' ] '"${one_time}"'}' "$MSG_URL" - # '"text":"$2", "reply_markup": {"keyboard": [ ${3} ], "one_time_keyboard": true}' -} - -remove_keyboard() { - local text='"text":"'"${2}"'"'; [ "${2}" = "" ] && text='"text":"'"${ISEMPTY}"'"' - sendJson "${1}" "${text}"', "reply_markup": {"remove_keyboard":true}' "$MSG_URL" - #JSON='"text":"$2", "reply_markup": {"remove_keyboard":true}' -} -send_inline_keyboard() { - local text='"text":"'"${2}"'"'; [ "${2}" = "" ] && text='"text":"'"${ISEMPTY}"'"' - sendJson "${1}" "${text}"', "reply_markup": {"inline_keyboard": [ '"${3}"' ]}' "$MSG_URL" - # JSON='"text":"$2", "reply_markup": {"inline_keyboard": [ $3->[{"text":"text", "url":"url"}]<- ]}' -} -send_button() { - send_inline_keyboard "${1}" "${2}" '[ {"text":"'"${3}"'", "url":"'"${4}"'"}]' -} - -# usage: sendJson "chat" "JSON" "URL" -sendJson(){ +# curl is preffered, but may not availible on ebedded systems +if [ "${BASHBOT_WGET}" = "" ] && _exists curl ; then + # simple curl or wget call, output to stdout + getJson(){ + curl -sL "$1" + } + # usage: sendJson "chat" "JSON" "URL" + sendJson(){ local chat=""; [ "${1}" != "" ] && chat='"chat_id":'"${1}"',' res="$(curl -s -d '{'"${chat} $2"'}' -X POST "${3}" \ -H "Content-Type: application/json" | "${JSONSHFILE}" -s -b -n )" BOTSENT[OK]="$(JsonGetLine '"ok"' <<< "$res")" BOTSENT[ID]="$(JsonGetValue '"result","message_id"' <<< "$res")" - [[ "${2}" = *"${ISEMPTY}"* ]] && delete_message "${1}" "${BOTSENT[ID]}" -} + } +else + # simple curl or wget call outputs result to stdout + getJson(){ + wget -qO - "$1" + } + # usage: sendJson "chat" "JSON" "URL" + sendJson(){ + local chat=""; + [ "${1}" != "" ] && chat='"chat_id":'"${1}"',' + res="$(wget -qO - --post-data='{'"${chat} $2"'}' \ + --header='Content-Type:application/json' "${3}" | "${JSONSHFILE}" -s -b -n )" + BOTSENT[OK]="$(JsonGetLine '"ok"' <<< "$res")" + BOTSENT[ID]="$(JsonGetValue '"result","message_id"' <<< "$res")" + } +fi # convert common telegram entities to JSON # title caption description markup inlinekeyboard @@ -292,94 +201,37 @@ title2Json(){ echo "${title}${caption}${desc}${markup}${keyboard}" } -get_file() { - [ "$1" = "" ] && return - local JSON='"file_id": '"${1}" - sendJson "" "${JSON}" "${GETFILE_URL}" - echo "${URL}/$(echo "${res}" | jsonGetString '"result","file_path"')" +# get bot name +getBotName() { + sendJson "" "" "$ME_URL" + JsonGetString '"result","username"' <<< "$res" } -send_file() { - [ "$2" = "" ] && return - local CAPTION - local chat_id=$1 - local file=$2 - echo "$file" | grep -qE "$FILE_REGEX" || return - local ext="${file##*.}" - case $ext in - mp3|flac) - CUR_URL=$AUDIO_URL - WHAT=audio - STATUS=upload_audio - CAPTION="$3" - ;; - png|jpg|jpeg|gif) - CUR_URL=$PHO_URL - WHAT=photo - STATUS=upload_photo - CAPTION="$3" - ;; - webp) - CUR_URL=$STICKER_URL - WHAT=sticker - STATUS= - ;; - mp4) - CUR_URL=$VIDEO_URL - WHAT=video - STATUS=upload_video - CAPTION="$3" - ;; - - ogg) - CUR_URL=$VOICE_URL - WHAT=voice - STATUS= - ;; - *) - CUR_URL=$DOCUMENT_URL - WHAT=document - STATUS=upload_document - CAPTION="$3" - ;; - esac - send_action "$chat_id" "$STATUS" - res="$(curl -s "$CUR_URL" -F "chat_id=$chat_id" -F "$WHAT=@$file" -F "caption=$CAPTION")" +# pure bash implementaion, done by KayM (@gnadelwartz) +# see https://stackoverflow.com/a/55666449/9381171 +JsonDecode() { + local out="$1" remain="" U="" + local regexp='(.*)\\u[dD]([0-9a-fA-F]{3})\\u[dD]([0-9a-fA-F]{3})(.*)' + while [[ "${out}" =~ $regexp ]] ; do + U=$(( ( (0xd${BASH_REMATCH[2]} & 0x3ff) <<10 ) | ( 0xd${BASH_REMATCH[3]} & 0x3ff ) + 0x10000 )) + remain="$(printf '\\U%8.8x' "${U}")${BASH_REMATCH[4]}${remain}" + out="${BASH_REMATCH[1]}" + done + echo -e "${out}${remain}" } -# typing for text messages, upload_photo for photos, record_video or upload_video for videos, record_audio or upload_audio for audio files, upload_document for general files, find_location for location - -send_action() { - [ "$2" = "" ] && return - sendJson "${1}" '"action": "'"${2}"'"' "$ACTION_URL" +JsonGetString() { + sed -n -e '0,/\['"$1"'\]/ s/\['"$1"'\][ \t]"\(.*\)"$/\1/p' +} +JsonGetLine() { + sed -n -e '0,/\['"$1"'\]/ s/\['"$1"'\][ \t]//p' +} +JsonGetValue() { + sed -n -e '0,/\['"$1"'\]/ s/\['"$1"'\][ \t]\([0-9.,]*\).*/\1/p' } -send_location() { - [ "$3" = "" ] && return - sendJson "${1}" '"latitude": '"${2}"', "longitude": '"${3}"'' "$LOCATION_URL" -} - -send_venue() { - local add="" - [ "$5" = "" ] && return - [ "$6" != "" ] && add=', "foursquare_id": '"$6"'' - sendJson "${1}" '"latitude": '"${2}"', "longitude": '"${3}"', "address": "'"${5}"'", "title": "'"${4}"'"'"${add}" "$VENUE_URL" -} - - -forward_message() { - [ "$3" = "" ] && return - sendJson "${1}" '"from_chat_id": '"${2}"', "message_id": '"${3}"'' "$FORWARD_URL" -} -forward() { # backward compatibility - forward_message "$@" || return -} - -# returns true if function exist -_is_function() -{ - [ "$(LC_ALL=C type -t "$1")" = "function" ] -} +################ +# processing of updates starts here process_updates() { MAX_PROCESS_NUMBER="$(sed <<< "${UPDATE}" '/\["result",[0-9]*\]/!d' | tail -1 | sed 's/\["result",//g;s/\].*//g')" for ((PROCESS_NUMBER=0; PROCESS_NUMBER<=MAX_PROCESS_NUMBER; PROCESS_NUMBER++)); do @@ -397,19 +249,19 @@ process_client() { fi # Tmux copname="$ME"_"${CHAT[ID]}" - source "${COMMANDS}" + # shellcheck source=./commands.sh + source "${COMMANDS}" "$1" tmpcount="COUNT${CHAT[ID]}" grep -q "$tmpcount" <"${COUNTFILE}" >/dev/null 2>&1 || echo "$tmpcount">>"${COUNTFILE}" # To get user count execute bash bashbot.sh count } -JsonGetString() { - sed -n -e '0,/\['"$1"'\]/ s/\['"$1"'\][ \t]"\(.*\)"$/\1/p' -} -JsonGetLine() { - sed -n -e '0,/\['"$1"'\]/ s/\['"$1"'\][ \t]//p' -} -JsonGetValue() { - sed -n -e '0,/\['"$1"'\]/ s/\['"$1"'\][ \t]\([0-9.,]*\).*/\1/p' +process_inline() { + local num="${1}" + iQUERY[0]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",0,"inline_query","query"')")" + iQUERY[USER_ID]="$(JsonGetValue <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","id"')" + iQUERY[FIRST_NAME]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","first_name"')")" + iQUERY[LAST_NAME]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","last_name"')")" + iQUERY[USERNAME]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","username"')")" } process_message() { local num="$1" @@ -490,6 +342,8 @@ process_message() { rm "$TMP" } + +######################### # main get updates loop, should never terminate start_bot() { local DEBUG="$1" @@ -500,13 +354,12 @@ start_bot() { [[ "${DEBUG}" = *"debug" ]] && exec &>>"DEBUG.log" [ "${DEBUG}" != "" ] && date && echo "Start BASHBOT in Mode \"${DEBUG}\"" [[ "${DEBUG}" = "xdebug"* ]] && set -x - while true; do { - - UPDATE="$(curl -s "$UPD_URL$OFFSET" | "${JSONSHFILE}" -s -b -n)" + while true; do + UPDATE="$(getJson "$UPD_URL$OFFSET" | "${JSONSHFILE}" -s -b -n)" # Offset OFFSET="$(grep <<< "${UPDATE}" '\["result",[0-9]*,"update_id"\]' | tail -1 | cut -f 2)" - OFFSET=$((OFFSET+1)) + ((OFFSET++)) if [ "$OFFSET" != "1" ]; then mysleep="100" @@ -514,21 +367,23 @@ start_bot() { fi # adaptive sleep in ms rounded to next lower second [ "${mysleep}" -gt "999" ] && sleep "${mysleep%???}" - mysleep=$((mysleep+addsleep)); [ "${mysleep}" -gt "${maxsleep}" ] && mysleep="${maxsleep}" - } + # bash aritmetic + ((mysleep+= addsleep , mysleep= mysleep>maxsleep ?maxsleep:mysleep)) done } # initialize bot environment, user and permissions bot_init() { - # move tmpdir to datadir + # upgrade from old version local OLDTMP="${BASHBOT_VAR:-.}/tmp-bot-bash" [ -d "${OLDTMP}" ] && { mv -n "${OLDTMP}/"* "${TMPDIR}"; rmdir "${OLDTMP}"; } + [ -f "modules/inline.sh" ] && rm -f "modules/inline.sh" + #setup bashbot [[ "$(id -u)" -eq "0" ]] && RUNUSER="nobody" echo -n "Enter User to run basbot [$RUNUSER]: " read -r TOUSER [ "$TOUSER" = "" ] && TOUSER="$RUNUSER" - if ! compgen -u "$TOUSER" >/dev/null 2>&1; then + if ! id "$TOUSER" >/dev/null 2>&1; then echo -e "${RED}User \"$TOUSER\" not found!${NC}" exit 3 else @@ -543,15 +398,19 @@ bot_init() { fi } -# get bot name -getBotName() { - sendJson "" "" "$ME_URL" - JsonGetString '"result","username"' <<< "$res" -} +JSONSHFILE="${BASHBOT_JSONSH:-${RUNDIR}/JSON.sh/JSON.sh}" +[[ "${JSONSHFILE}" != *"/JSON.sh" ]] && echo -e "${RED}ERROR: \"${JSONSHFILE}\" ends not with \"JSONS.sh\".${NC}" && exit 3 + +if [ ! -f "${JSONSHFILE}" ]; then + echo "Seems to be first run, Downloading ${JSONSHFILE}..." + [[ "${JSONSHFILE}" = "${RUNDIR}/JSON.sh/JSON.sh" ]] && mkdir "JSON.sh" 2>/dev/null && chmod +w "JSON.sh" + getJson "https://cdn.jsdelivr.net/gh/dominictarr/JSON.sh/JSON.sh" >"${JSONSHFILE}" + chmod +x "${JSONSHFILE}" +fi ME="$(getBotName)" if [ "$ME" = "" ]; then - if [ "$(cat "${TOKENFILE}")" = "bashbottestscript" ]; then + if [ "$(< "${TOKENFILE}")" = "bashbottestscript" ]; then ME="bashbottestscript" else echo -e "${RED}ERROR: Can't connect to Telegram Bot! May be your TOKEN is invalid ...${NC}" @@ -559,30 +418,6 @@ if [ "$ME" = "" ]; then fi fi -# use phyton JSON to decode JSON UFT-8, provide bash implementaion as fallback -if [ "${BASHBOT_DECODE}" != "" ] && which python >/dev/null 2>&1 ; then - JsonDecode() { - printf '"%s\\n"' "${1//\"/\\\"}" | python -c 'import json, sys; sys.stdout.write(json.load(sys.stdin).encode("utf-8"))' - } -else - # pure bash implementaion, done by KayM (@gnadelwartz) - # see https://stackoverflow.com/a/55666449/9381171 - JsonDecode() { - local out="$1" - local remain="" - local regexp='(.*)\\u[dD]([0-9a-fA-F]{3})\\u[dD]([0-9a-fA-F]{3})(.*)' - while [[ "${out}" =~ $regexp ]] ; do - # match 2 \udxxx hex values, calculate new U, then split and replace - local W1=$(( ( 0xd${BASH_REMATCH[2]} & 0x3ff) <<10 )) - local W2=$(( 0xd${BASH_REMATCH[3]} & 0x3ff )) - local U=$(( ( W1 | W2 ) + 0x10000 )) - remain="$(printf '\\U%8.8x' "${U}")${BASH_REMATCH[4]}${remain}" - out="${BASH_REMATCH[1]}" - done - echo -e "${out}${remain}" - } -fi - # source the script with source as param to use functions in other scripts # do not execute if read from other scripts @@ -592,6 +427,8 @@ if [ "$1" != "source" ]; then # internal options only for use from bashbot and developers case "$1" in "outproc") # forward output from interactive and jobs to chat + [ "$3" = "" ] && echo "No file to read from" && exit 3 + [ "$2" = "" ] && echo "No chat to send to" && exit 3 until [ "$line" = "imprettydarnsuredatdisisdaendofdacmd" ];do line="" read -r -t 10 line @@ -637,12 +474,10 @@ if [ "$1" != "source" ]; then tmux kill-session -t "$ME" &>/dev/null tmux new-session -d -s "$ME" "bash $SCRIPT startbot" && echo -e "${GREEN}Bot started successfully.${NC}" echo "Tmux session name $ME" || echo -e "${RED}An error occurred while starting the bot. ${NC}" - send_markdown_message "${CHAT[ID]}" "*Bot started*" ;; "kill") ${CLEAR} tmux kill-session -t "$ME" &>/dev/null - send_markdown_message "${CHAT[ID]}" "*Bot stopped*" echo -e "${GREEN}OK. Bot stopped successfully.${NC}" ;; "background" | "resumeback") diff --git a/commands.sh b/commands.sh index c3d3e9f..2654af7 100644 --- a/commands.sh +++ b/commands.sh @@ -5,10 +5,8 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # -# shellcheck disable=SC2154 -# shellcheck disable=SC2034 # adjust your language setting here, e.g.when run from other user or cron. # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment @@ -37,47 +35,42 @@ bashbot_help='*Available commands*: Written by Drew (@topkecleon), Daniil Gentili (@danogentili) and KayM(@gnadelwartz). Get the code in my [GitHub](http://github.com/topkecleon/telegram-bot-bash) ' +# we don't know whom we are ... +copname="" -if [ "${1}" != "source" ]; then - # load modules needed for commands.sh only - # shellcheck source=./modules/aliases.sh - [ -r "${MODULEDIR:-.}/aliases.sh" ] && source "${MODULEDIR:-.}/aliases.sh" - # shellcheck source=./modules/background.sh - [ -r "${MODULEDIR:-.}/background.sh" ] && source "${MODULEDIR:-.}/background.sh" -else - # defaults to no inline and nonsense home dir - INLINE="0" - FILE_REGEX='/home/user/allowed/.*' - # load modules needed for bashbot.sh also - # shellcheck source=./modules/background.sh - [ -r "${MODULEDIR:-.}/inline.sh" ] && source "${MODULEDIR:-.}/inline.sh" +# load modues on startup and always on on debug +if [ "${1}" = "source" ] || [[ "${1}" = *"debug"* ]] ; then + # load all readable modules + for modules in ${MODULEDIR:-.}/*.sh ; do + # shellcheck source=./modules/aliases.sh + [ -r "${modules}" ] && source "${modules}" "${1}" + done fi +# defaults to no inline and nonsense home dir +export INLINE="0" +export FILE_REGEX='/home/user/allowed/.*' + + # load mycommands # shellcheck source=./commands.sh [ -r "${BASHBOT_ETC:-.}/mycommands.sh" ] && source "${BASHBOT_ETC:-.}/mycommands.sh" "${1}" if [ "${1}" != "source" ];then - if ! tmux ls 2>/dev/null | grep -v send | grep -q "$copname"; then - # interactive running? - [ ! -z "${URLS[*]}" ] && { - curl -s "${URLS[*]}" -o "$NAME" - send_file "${CHAT[ID]}" "$NAME" "$CAPTION" - rm -f "$NAME" - } - [ ! -z "${LOCATION[*]}" ] && send_location "${CHAT[ID]}" "${LOCATION[LATITUDE]}" "${LOCATION[LONGITUDE]}" - - fi - + # detect inline commands.... + # no default commands, all processing is done in myinlines() if [ "$INLINE" != "0" ] && [ "${iQUERY[ID]}" != "" ]; then if _is_function process_inline; then # forward iinline query to optional dispatcher _is_function myinlines && myinlines fi + + # regular (gobal) commands ... + # your commands are in mycommands() else - + case "${MESSAGE}" in ################################################ # GLOBAL commands start here, edit messages only diff --git a/dev/all-tests.sh b/dev/all-tests.sh index c0c1199..bc99493 100755 --- a/dev/all-tests.sh +++ b/dev/all-tests.sh @@ -1,12 +1,16 @@ #!/usr/bin/env bash # this has to run once atfer git clone # and every time we create new hooks -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir) -cd "${GIT_DIR}/.." || exit 1 +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ "$GIT_DIR" != "" ] ; then + cd "$GIT_DIR/.." || exit 1 +else + echo "Sorry, no git repository $(pwd)" && exit 1 +fi # create test environment TESTENV="/tmp/bashbot.test$$" diff --git a/dev/git-add.sh b/dev/git-add.sh index 5127114..aeb99aa 100755 --- a/dev/git-add.sh +++ b/dev/git-add.sh @@ -3,12 +3,16 @@ # # works together with git pre-push.sh and ADD all changed files since last push -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir) -cd "$GIT_DIR/.." || exit 1 +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ "$GIT_DIR" != "" ] ; then + cd "$GIT_DIR/.." || exit 1 +else + echo "Sorry, no git repository $(pwd)" && exit 1 +fi [ ! -f .git/.lastpush ] && echo "No push or hooks not installed, use \"git add\" instead ... Abort" && exit diff --git a/dev/hooks/pre-commit.sh b/dev/hooks/pre-commit.sh index 17d42e6..bf19b43 100755 --- a/dev/hooks/pre-commit.sh +++ b/dev/hooks/pre-commit.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 ############ # NOTE: you MUST run install-hooks.sh again when updating this file! diff --git a/dev/hooks/pre-push.sh b/dev/hooks/pre-push.sh index 49a2982..e1e1610 100755 --- a/dev/hooks/pre-push.sh +++ b/dev/hooks/pre-push.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 ############ # NOTE: you MUST run install-hooks.sh again when updating this file! diff --git a/dev/install-hooks.sh b/dev/install-hooks.sh index 0abb9d0..d510bf2 100755 --- a/dev/install-hooks.sh +++ b/dev/install-hooks.sh @@ -1,12 +1,16 @@ #!/usr/bin/env bash # this has to run once atfer git clone # and every time we create new hooks -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir) -cd "$GIT_DIR/.." || exit 1 +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ "$GIT_DIR" != "" ] ; then + cd "$GIT_DIR/.." || exit 1 +else + echo "Sorry, no git repository $(pwd)" && exit 1 +fi HOOKDIR="dev/hooks" diff --git a/dev/make-dist.sh b/dev/make-distribution.sh similarity index 83% rename from dev/make-dist.sh rename to dev/make-distribution.sh index 65a3812..cec7784 100755 --- a/dev/make-dist.sh +++ b/dev/make-distribution.sh @@ -1,12 +1,17 @@ #!/usr/bin/env bash -# this has to run once atfer git clone -# and every time we create new hooks -#### $$VERSION$$ v0.72-1-g67c47ac +# file: make-distribution.sh +# creates files and arcchives to dirtribute bashbot +# +#### $$VERSION$$ v0.76-1-ge8a1fd0 # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir) -cd "$GIT_DIR/.." || exit 1 +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ "$GIT_DIR" != "" ] ; then + cd "$GIT_DIR/.." || exit 1 +else + echo "Sorry, no git repository $(pwd)" && exit 1 +fi VERSION="$(git describe --tags | sed -e 's/-[0-9].*//' -e 's/v//')" @@ -18,6 +23,7 @@ DISTFILES="bashbot.rc bashbot.sh commands.sh mycommands.sh doc examples modu for test in "dev/all-tests.sh" do + [ ! -x ""${test} ] && continue if ! "${test}" ; then echo "Test ${test} failed, can't create dist!" exit 1 @@ -31,6 +37,7 @@ cp -r ${DISTFILES} "${DISTDIR}" cd "${DISTDIR}" || exit 1 # additional stuff +mv "bashbot.rc" "bashbot.rc.dist" mv "commands.sh" "commands.sh.dist" mv "mycommands.sh" "mycommands.sh.dist" diff --git a/dev/make-standalone.sh b/dev/make-standalone.sh new file mode 100755 index 0000000..7fe348f --- /dev/null +++ b/dev/make-standalone.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# file: make-standalone.sh +# even after make-distribution.sh bashbot is not self contained as it was in the past. +# +# If you your bot is finished you can use make-standalone.sh to create the +# the old all-in-one bashbot: bashbot.sh and commands.sh only! +# +#### $$VERSION$$ v0.76-1-ge8a1fd0 + +# magic to ensure that we're always inside the root of our application, +# no matter from which directory we'll run script +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ "$GIT_DIR" != "" ] ; then + cd "$GIT_DIR/.." || exit 1 +else + [ ! -f "bashbot.sh" ] && echo "bashbot.sh not found in $(pwd)" && exit 1 +fi + +#DISTNAME="telegram-bot-bash" +DISTDIR="./standalone/${DISTNAME}" +DISTFILES="bashbot.sh commands.sh mycommands.sh modules LICENSE README.txt token count botacl botadmin" + +# run tests first! + +for test in "dev/all-tests.sh" +do + [ ! -x "${test}" ] && continue + if ! "${test}" ; then + echo "Test ${test} failed, can't create standalone!" + exit 1 + fi +done + +# create dir for distribution and copy files +mkdir -p "${DISTDIR}" 2>/dev/null +# shellcheck disable=SC2086 +cp -r ${DISTFILES} "${DISTDIR}" 2>/dev/null +cd "${DISTDIR}" || exit 1 + +####################### +# here the magic starts +# create all in one bashbot.sh file + +echo "OK, noe lets do the magic ..." +echo " ... create unified commands.sh" + +{ + # first head of commands.sh + sed -n '0,/^if / p' commands.sh | head -n -2 | grep -v 'mycommands.sh' + + # then mycommands from first non comment line on + printf '\n##############################\n# my commands starts here ...\n' + sed -n '/^$/,$ p' mycommands.sh + + # last tail of commands.sh + printf '\n##############################\n# default commands starts here ...\n' + sed -n '/\/mycommands.sh"/,$ p' commands.sh | tail -n +2 + +} >>$$commands.sh + +mv $$commands.sh commands.sh +rm -f mycommands.sh + +echo " ... create unified bashbot.sh" + +{ + # first head of bashbot.sh + sed -n '0,/\/commands.sh"/ p' bashbot.sh | head -n -2 + + # then mycommands from first non comment line on + printf '\n##############################\n# bashbot modules starts here ...\n' + cat modules/*.sh | sed -e 's/^#\!\/bin\/bash.*//' + + # last tail of commands.sh + printf '\n##############################\n# bashbot internal functions starts here ...\n\n' + sed -n '/\/commands.sh"/,$ p' bashbot.sh + +} >>$$bashbot.sh + +mv $$bashbot.sh bashbot.sh +chmod +x bashbot.sh + +rm -rf modules + +echo "Done!" + +cd .. || exit 1 + +echo -e "\\nStandalone bashbot files are now availible in \"${DISTDIR}\":\\n" +ls -l "${DISTDIR}"* + diff --git a/dev/shellcheck.files b/dev/shellcheck.files index 4cc2c8b..0b3c8a0 100644 --- a/dev/shellcheck.files +++ b/dev/shellcheck.files @@ -1,3 +1,3 @@ # list of additional files to check from shellcheck -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 bashbot.rc diff --git a/dev/version.sh b/dev/version.sh index 8c04f1d..7340d72 100755 --- a/dev/version.sh +++ b/dev/version.sh @@ -1,6 +1,6 @@ #!/bin/bash # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # shellcheck disable=SC2016 # # Easy Versioning in git: @@ -36,8 +36,12 @@ # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir) -cd "$GIT_DIR/.." || exit 1 +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [ "$GIT_DIR" != "" ] ; then + cd "$GIT_DIR/.." || exit 1 +else + echo "Sorry, no git repository $(pwd)" && exit 1 +fi unset IFS # set -f # if you are paranoid use set -f to disable globbing diff --git a/doc/0_install.md b/doc/0_install.md index eeb8762..674b542 100644 --- a/doc/0_install.md +++ b/doc/0_install.md @@ -34,13 +34,20 @@ As an alternative to download the zip files, you can clone the github repository 2. [Download latest release zip from github](https://github.com/topkecleon/telegram-bot-bash/releases) 3. Extract all files to your existing bashbot dir **Note: all files execpt 'mycommands.sh' and 'commands.sh' may overwritten!** -4. Run ```sudo ./bashbot.sh init``` to setup your environment after the update +4. Save your your current 'commands.sh' and run ```cp commands.sh.dist commands.sh``` +5. Run ```sudo ./bashbot.sh init``` to setup your environment after the update + +If you modified 'commands.sh' re apply all changes to the new 'commands.sh'. To avoid this all your modifications +must be done in 'mycommands.sh' only. ### Notes on Updates #### Location of tmp / data dir From version 0.70 on the tmp dir is renamed to 'data-bot-bash' to reflect the fact that not only temporary files are stored. an existing 'tmp-bot-bash' will be automatically renamed after update. +From version 0.60 on your commands must be placed in 'mycommands.sh'. If you update from a version with your commands +in 'commands.sh' move all your commands and functions to 'mycommands.sh'. + From version 0.50 on the temporary files are no more placed in '/tmp'. instead a dedicated tmp dir is used. #### Changes to send_keyboard in v0.6 @@ -63,5 +70,5 @@ The old format is supported for backward compatibility, but may fail for corner #### [Next Create Bot](1_firstbot.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/1_firstbot.md b/doc/1_firstbot.md index 1743943..1884726 100644 --- a/doc/1_firstbot.md +++ b/doc/1_firstbot.md @@ -65,5 +65,5 @@ group. This step is up to you actually. #### [Prev Installation](0_install.md) #### [Next Getting started](2_usage.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/2_usage.md b/doc/2_usage.md index fda5b85..28aa312 100644 --- a/doc/2_usage.md +++ b/doc/2_usage.md @@ -182,5 +182,5 @@ send_action "${CHAT[ID]}" "action" #### [Prev Create Bot](1_firstbot.md) #### [Next Advanced Usage](3_advanced.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/3_advanced.md b/doc/3_advanced.md index a383c1d..1360d1c 100644 --- a/doc/3_advanced.md +++ b/doc/3_advanced.md @@ -180,5 +180,5 @@ See also [answer_inline_multi, answer_inline_compose](6_reference.md#answer_inli #### [Prev Getting started](2_usage.md) #### [Next Expert Use](4_expert.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/4_expert.md b/doc/4_expert.md index a41dcf0..adace28 100644 --- a/doc/4_expert.md +++ b/doc/4_expert.md @@ -104,5 +104,5 @@ An example crontab is provided in ```examples/bashbot.cron```. #### [Prev Expert Use](4_expert.md) #### [Next Best Practice](5_practice.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/5_practice.md b/doc/5_practice.md index 864620f..9e717e5 100644 --- a/doc/5_practice.md +++ b/doc/5_practice.md @@ -153,5 +153,5 @@ The second warning is about an unused variable, this is true because in our exam #### [Prev Best Practice](5_practice.md) #### [Next Functions Reference](6_reference.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/6_reference.md b/doc/6_reference.md index 7b88700..a3359f0 100644 --- a/doc/6_reference.md +++ b/doc/6_reference.md @@ -592,5 +592,5 @@ Send Input from Telegram to waiting Interactive Chat. #### [Prev Best Practice](5_practice.md) #### [Next Notes for Developers](7_develop.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/7_develop.md b/doc/7_develop.md index 545ed1a..55bc350 100644 --- a/doc/7_develop.md +++ b/doc/7_develop.md @@ -4,7 +4,7 @@ This section is about help and best practices for new bashbot developers. The ma bashbot development is done on github. If you want to provide fixes or new features [fork bashbot on githup](https://help.github.com/en/articles/fork-a-repo) and provide changes as [pull request on github](https://help.github.com/en/articles/creating-a-pull-request). -### Debuging Bashbot +### Debugging Bashbot In normal mode of operation all bashbot output is discarded one more correct sent to TMUX console. To get these messages (and more) you can start bashbot in the current shell ```./bashbot.sh startbot```. Now you can see all output or erros from bashbot. In addition you can change the change the level of verbosity by adding a third argument after startbot. @@ -16,7 +16,19 @@ In addition you can change the change the level of verbosity by adding a third a "xdebugterm" same as xdebug but output and errors are sent to terminal ``` +### Create a stripped down Version of your Bot +Currently bashbot is more a bot development environment than a bot, containing examples, developer scripts, modules, documentation and more. +You don't need all these files after you're finished with your cool new bot. +Let's create a stripped down version: + +- delete all modules you do not need from 'modules', e.g. 'modules/inline.sh' if you don't use inline queries +- delete not needed standard commands and messages from 'commands.sh' +- delete not needed commands and functions from 'mycommands.sh' +- run ```dev/make-standalone.sh``` to create a a stripped down version of your bo + +Now have a look at the directory 'standalone', here you find the files 'bashbot.sh' and 'commands.sh' containing everything to run your bot. +[Download make-standalone.sh](https://github.com/topkecleon/telegram-bot-bash/blob/master/dev/make-standalone.sh) from github. ### Setup your develop environment @@ -39,6 +51,49 @@ A typical bashbot develop loop looks as follow: **If you setup your dev environment with hooks and use the scripts above, versioning, addding and testing is done automatically.** +### common commands +We state bashbot is a bash only bot, but this is not true. bashbot is a bash script using bash features PLUS external commands. +Usually bash is used in a unix/linux environment where many (GNU) commands are availible, but if commands are missing, bashbot may not work. + +To avoid this and make bashbot working on as many platforms as possible - from embedded linux to mainframe - I recommed to restrict +ourself to the common commands provided by bash and coreutils/busybox/toybox. +See [Bash Builtins](https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html), +[coreutils](https://en.wikipedia.org/wiki/List_of_GNU_Core_Utilities_commands), +[busybox](https://en.wikipedia.org/wiki/BusyBox#Commands) and [toybox](https://landley.net/toybox/help.html) + +Availible commands in bash, coreutils, busybox and toybox. Do you find curl on the list? +```bash + .*, [*, [[*, basename, break, builtin*, bzcat, caller*, cat, cd*, chattr, + chgrp, chmod, chown, clear, command*, continue *, cp, cut, date, declare*, + dc, dd, df, diff, dirname, du, echo*, eval*, exec*, exit *, expr*, find, + fuser, getopt*, grep, hash*, head, hexdump, id, kill, killall, last, length, + less, let*, ln, local*, logname, ls, lsattr, lsmod, man, mapfile*, md5sum, mkdir, + mkfifo, mknod, more, mv, nice, nohup, passwd, patch, printf*, ps, pwd*, read*, + readarray*, readonly* return*, rm, rmdir, sed, seq, sha1sum, shift*, sleep, + source*, sort, split, stat, strings, su, sync, tail, tar, tee, test, + time, times*, timeout, touch, tr, trap*, true, umask*, usleep, uudecode, + uuencode, wc, wget, which, who, whoami, xargs, yes +``` +commands marked with \* are bash builtins, all others are external programms. Calling an external programm is more expensive then using bulitins +or using an internal replacement. Here are some examples of internal replacement for external commands: +```bash +HOST="$(hostname)" -> HOST="$HOSTNAME" + +seq 1 100 -> {0..100} + +data="$(cat file)" -> data="$(<"file")" + +DIR="$(dirname $0) -> DIR=""${0%/*}/"" + +IAM="($basename $0)" -> IAM="${0##*/}* + +VAR="$(( 1 + 2 ))" -> (( var=1+2 )) + +INDEX="$(( ${INDEX} + 1 ))" -> (( INDEX++ )) + +``` +For more examples see [Pure bash bible](https://github.com/dylanaraps/pure-bash-bible) + ### Prepare a new version After some development it may time to create a new version for the users. a new version can be in sub version upgrade, e.g. for fixes and smaller additions or a new release version for new features. To mark a new version use ```git tag NEWVERSION``` and run ```dev/version.sh``` to update all version strings. @@ -48,6 +103,8 @@ Usually I start with pre versions and when everything looks good I push out a re v0.x-devx -> v0.x-prex -> v0.x-rc -> v0.x ... 0.x+1-dev ... ``` +If you release a new Version run ```dev/make-distribution.sh``` to create the zip and tar.gz archives in the dist directory and attach them to the github release. Do not forget to delete directory dist afterwards. + ### Versioning Bashbot is tagged with version numbers. If you start a new development cycle you can tag your fork with a version higher than the current version. @@ -131,5 +188,5 @@ fi #### [Prev Function Reference](6_reference.md) #### [Next Bashbot Environment](8_custom.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/doc/8_custom.md b/doc/8_custom.md index 83ff64e..04db87a 100644 --- a/doc/8_custom.md +++ b/doc/8_custom.md @@ -48,17 +48,31 @@ Full path to JSON.sh script, default: './JSON.sh/JSON.sh', must end with '/JSON. ### Change config values -#### BASHBOT_DECODE -Bashbot offers two variants for decoding JSON UTF format to UTF-8. By default bashbot uses 'json.encode' if python is installed. -If 'BASHBOT_DECODE' is set to any value (not undefined or not empty) the bash only implementation will be used. -```bash - unset BASHBOT_DECODE # autodetect python (default) - export BASHBOT_DECODE "" # autodetect python +#### BASHBOT_URL +Uses given URL instead of offical telegram API URL, useful if you have your own telegram server or for testing. + +```bash + unset BASHBOT_URL # use Telegram URL https://api.telegram.org/bot (default) + + export BASHBOT_URL "" # use use Telegram https://api.telegram.org/bot + + export BASHBOT_URL "https://my.url.com/bot" # use your URL https://my.url.com/bot - export BASHBOT_DECODE "yes" # force internal - export BASHBOT_DECODE "no" # also force internal! ``` +#### BASHBOT_TOKEN + +#### BASHBOT_WGET +Bashbot uses ```curl``` to communicate with telegram server. if ```curl``` is not availible ```wget``` is used. +If 'BASHBOT_WGET' is set to any value (not undefined or not empty) wget is used even is curl is availible. +```bash + unset BASHBOT_WGET # use curl (default) + export BASHBOT_WGET "" # use curl + + export BASHBOT_WGET "yes" # use wget + export BASHBOT_WGET "no" # use wget! + +``` #### BASHBOT_SLEEP Instead of polling permanently or with a fixed delay, bashbot offers a simple adaptive polling. @@ -117,5 +131,5 @@ for every poll until the maximum of BASHBOT_SLEEP ms. #### [Prev Notes for Developers](7_develop.md) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/examples/README.md b/examples/README.md index ea1bea1..45fa8eb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -55,6 +55,6 @@ convert existing bots. **external-use** will contain some examples on how to send messages from external scripts to Telegram chats or users. -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 diff --git a/examples/background-scripts/run_diskusage.sh b/examples/background-scripts/run_diskusage.sh index dc39707..4c756f3 100755 --- a/examples/background-scripts/run_diskusage.sh +++ b/examples/background-scripts/run_diskusage.sh @@ -4,7 +4,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment diff --git a/examples/background-scripts/run_filecontent.sh b/examples/background-scripts/run_filecontent.sh index ac9dfb1..fa5ab5c 100755 --- a/examples/background-scripts/run_filecontent.sh +++ b/examples/background-scripts/run_filecontent.sh @@ -2,7 +2,7 @@ # file: run_filename # background job to display content of all new files in WATCHDIR # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment diff --git a/examples/background-scripts/run_filename.sh b/examples/background-scripts/run_filename.sh index b67d8e8..3c070fb 100755 --- a/examples/background-scripts/run_filename.sh +++ b/examples/background-scripts/run_filename.sh @@ -2,7 +2,7 @@ # file: run_filename # background job to display all new files in WATCHDIR # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment diff --git a/examples/background-scripts/run_notify.sh b/examples/background-scripts/run_notify.sh index a8fe754..e019204 100755 --- a/examples/background-scripts/run_notify.sh +++ b/examples/background-scripts/run_notify.sh @@ -4,7 +4,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment diff --git a/examples/bashbot-multi.sh b/examples/bashbot-multi.sh index a9d8a31..61cdde6 100755 --- a/examples/bashbot-multi.sh +++ b/examples/bashbot-multi.sh @@ -2,7 +2,7 @@ # file. multibot.sh # description: run multiple telegram bots from one installation # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 if [ "${2}" = "" ] || [ "${2}" = "-h" ]; then echo "Usage: $0 botname command" diff --git a/examples/bashbot.cron b/examples/bashbot.cron index 9095f6c..df9b95f 100644 --- a/examples/bashbot.cron +++ b/examples/bashbot.cron @@ -7,7 +7,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 SHELL=/bin/sh diff --git a/examples/calc.sh b/examples/calc.sh index 89c9778..4c47dd0 100755 --- a/examples/calc.sh +++ b/examples/calc.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$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment diff --git a/examples/notify.sh b/examples/notify.sh index a8fe754..976548f 100755 --- a/examples/notify.sh +++ b/examples/notify.sh @@ -4,7 +4,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment @@ -27,6 +27,7 @@ else fi # output current time every $1 seconds +date "+* It's %k:%M:%S o' clock ..." while sleep $SLEEP do date "+* It's %k:%M:%S o' clock ..." diff --git a/examples/question.sh b/examples/question.sh index ce7dc61..ee74b57 100755 --- a/examples/question.sh +++ b/examples/question.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$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment diff --git a/examples/send-system-status/botacl b/examples/send-system-status/botacl index 9a75940..9f41c1f 100644 --- a/examples/send-system-status/botacl +++ b/examples/send-system-status/botacl @@ -1,7 +1,7 @@ # file: botacl # a user not listed here, will return false from 'user_is_allowed' # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # Format: # user:ressource:chat diff --git a/examples/send-system-status/mycommands.sh b/examples/send-system-status/mycommands.sh index 0acb579..64b5180 100644 --- a/examples/send-system-status/mycommands.sh +++ b/examples/send-system-status/mycommands.sh @@ -5,7 +5,7 @@ # to show how you can customize bashbot by only editing mycommands.sh # NOTE: this is not tested, simply copied from original source and reworked! # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # # shellcheck disable=SC2154 # shellcheck disable=SC2034 diff --git a/modules/aliases.sh b/modules/aliases.sh index 050c1ac..3ead78f 100644 --- a/modules/aliases.sh +++ b/modules/aliases.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$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # # source from commands.sh to use the aliases diff --git a/modules/inline.sh b/modules/answerInline.sh similarity index 86% rename from modules/inline.sh rename to modules/answerInline.sh index 2af5718..3fde398 100644 --- a/modules/inline.sh +++ b/modules/answerInline.sh @@ -5,23 +5,11 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # source from commands.sh to use the inline functions INLINE_QUERY=$URL'/answerInlineQuery' -declare -A iQUERY -export iQUERY - -process_inline() { - local num="${1}" - iQUERY[0]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",0,"inline_query","query"')")" - iQUERY[USER_ID]="$(JsonGetValue <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","id"')" - iQUERY[FIRST_NAME]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","first_name"')")" - iQUERY[LAST_NAME]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","last_name"')")" - iQUERY[USERNAME]="$(JsonDecode "$(JsonGetString <<<"${UPDATE}" '"result",'"${num}"',"inline_query","from","username"')")" -} - answer_inline_query() { answer_inline_multi "${1}" "$(shift; inline_query_compose "$RANDOM" "$@")" diff --git a/modules/background.sh b/modules/background.sh index 783cf3d..d1145d6 100644 --- a/modules/background.sh +++ b/modules/background.sh @@ -5,78 +5,10 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # source from commands.sh if you want ro use interactive or background jobs -## to statisfy shellcheck -export res - -#### -# I placed send_message here because main use case is interactive chats and background jobs -send_message() { - [ "$2" = "" ] && return - local text keyboard btext burl no_keyboard file lat long title address sent - text="$(sed <<< "${2}" 's/ mykeyboardend.*//;s/ *my[kfltab][a-z]\{2,13\}startshere.*//')$(sed <<< "${2}" -n '/mytextstartshere/ s/.*mytextstartshere//p')" - text="$(sed <<< "${text}" 's/ *mynewlinestartshere */\r\n/g')" - [ "$3" != "safe" ] && { - no_keyboard="$(sed <<< "${2}" '/mykeyboardendshere/!d;s/.*mykeyboardendshere.*/mykeyboardendshere/')" - keyboard="$(sed <<< "${2}" '/mykeyboardstartshere /!d;s/.*mykeyboardstartshere *//;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - btext="$(sed <<< "${2}" '/mybtextstartshere /!d;s/.*mybtextstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - burl="$(sed <<< "${2}" '/myburlstartshere /!d;s/.*myburlstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//g;s/ *mykeyboardendshere.*//g')" - file="$(sed <<< "${2}" '/myfilelocationstartshere /!d;s/.*myfilelocationstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - lat="$(sed <<< "${2}" '/mylatstartshere /!d;s/.*mylatstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - long="$(sed <<< "${2}" '/mylongstartshere /!d;s/.*mylongstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - title="$(sed <<< "${2}" '/mytitlestartshere /!d;s/.*mytitlestartshere //;s/ *my[kfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - address="$(sed <<< "${2}" '/myaddressstartshere /!d;s/.*myaddressstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - } - if [ "$no_keyboard" != "" ]; then - remove_keyboard "$1" "$text" - sent=y - fi - if [ "$keyboard" != "" ]; then - if [[ "$keyboard" != *"["* ]]; then # pre 0.60 style - keyboard="[ ${keyboard//\" \"/\" \] , \[ \"} ]" - fi - send_keyboard "$1" "$text" "$keyboard" - sent=y - fi - if [ "$btext" != "" ] && [ "$burl" != "" ]; then - send_button "$1" "$text" "$btext" "$burl" - sent=y - fi - if [ "$file" != "" ]; then - send_file "$1" "$file" "$text" - sent=y - fi - if [ "$lat" != "" ] && [ "$long" != "" ]; then - if [ "$address" != "" ] && [ "$title" != "" ]; then - send_venue "$1" "$lat" "$long" "$title" "$address" - else - send_location "$1" "$lat" "$long" - fi - sent=y - fi - if [ "$sent" != "y" ];then - send_text "$1" "$text" - fi - -} - -send_text() { - case "$2" in - html_parse_mode*) - send_html_message "$1" "${2//html_parse_mode}" - ;; - markdown_parse_mode*) - send_markdown_message "$1" "${2//markdown_parse_mode}" - ;; - *) - send_normal_message "$1" "$2" - ;; - esac -} - ###### # interactive and background functions @@ -99,7 +31,9 @@ checkback() { } checkproc() { - tmux ls | grep -q "$1${copname}"; res=$?; return $? + tmux ls | grep -q "$1${copname}" + # shellcheck disable=SC2034 + res=$?; return $? } killback() { diff --git a/modules/chatMember.sh b/modules/chatMember.sh new file mode 100644 index 0000000..09148df --- /dev/null +++ b/modules/chatMember.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# file: modules/chatMember.sh +# do not edit, this file will be overwritten on update + +# This file is public domain in the USA and all free countries. +# Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) +# +#### $$VERSION$$ v0.76-1-ge8a1fd0 + +# source from commands.sh to use the member functions + +LEAVE_URL=$URL'/leaveChat' +KICK_URL=$URL'/kickChatMember' +UNBAN_URL=$URL'/unbanChatMember' +GETMEMBER_URL=$URL'/getChatMember' + +# usage: status="$(get_chat_member_status "chat" "user")" +get_chat_member_status() { + sendJson "$1" 'user_id: '"$2"'' "$GETMEMBER_URL" + # shellcheck disable=SC2154 + JsonGetString '"result","status"' <<< "$res" +} + +kick_chat_member() { + sendJson "$1" 'user_id: '"$2"'' "$KICK_URL" +} + +unban_chat_member() { + sendJson "$1" 'user_id: '"$2"'' "$UNBAN_URL" +} + +leave_chat() { + sendJson "$1" "" "$LEAVE_URL" +} + +user_is_creator() { + if [ "${1:--}" = "${2:-+}" ] || [ "$(get_chat_member_status "$1" "$2")" = "creator" ]; then return 0; fi + return 1 +} + +user_is_admin() { + local me; me="$(get_chat_member_status "$1" "$2")" + if [ "${me}" = "creator" ] || [ "${me}" = "administrator" ]; then return 0; fi + return 1 +} + +user_is_botadmin() { + local admin; admin="$(head -n 1 "${BOTADMIN}")" + [ "${admin}" = "${1}" ] && return 0 + [[ "${admin}" = "@*" ]] && [[ "${admin}" = "${2}" ]] && return 0 + if [ "${admin}" = "?" ]; then echo "${1:-?}" >"${BOTADMIN}"; return 0; fi + return 1 +} + +user_is_allowed() { + local acl="$1" + [ "$1" = "" ] && return 1 + grep -F -xq "${acl}:*:*" <"${BOTACL}" && return 0 + [ "$2" != "" ] && acl="${acl}:$2" + grep -F -xq "${acl}:*" <"${BOTACL}" && return 0 + [ "$3" != "" ] && acl="${acl}:$3" + grep -F -xq "${acl}" <"${BOTACL}" +} diff --git a/modules/sendMessage.sh b/modules/sendMessage.sh new file mode 100644 index 0000000..59e57e5 --- /dev/null +++ b/modules/sendMessage.sh @@ -0,0 +1,232 @@ +#!/bin/bash +# file: modules/message.sh +# do not edit, this file will be overwritten on update + +# This file is public domain in the USA and all free countries. +# Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) +# +#### $$VERSION$$ v0.76-1-ge8a1fd0 + +# source from commands.sh to use the sendMessage functions + +MSG_URL=$URL'/sendMessage' +PHO_URL=$URL'/sendPhoto' +AUDIO_URL=$URL'/sendAudio' +DOCUMENT_URL=$URL'/sendDocument' +STICKER_URL=$URL'/sendSticker' +VIDEO_URL=$URL'/sendVideo' +VOICE_URL=$URL'/sendVoice' +LOCATION_URL=$URL'/sendLocation' +VENUE_URL=$URL'/sendVenue' +ACTION_URL=$URL'/sendChatAction' +FORWARD_URL=$URL'/forwardMessage' + +send_normal_message() { + local text="${2}" + until [ -z "${text}" ]; do + sendJson "${1}" '"text":"'"${text:0:4096}"'"' "${MSG_URL}" + text="${text:4096}" + done +} + +send_markdown_message() { + local text="${2}" + until [ -z "${text}" ]; do + sendJson "${1}" '"text":"'"${text:0:4096}"'","parse_mode":"markdown"' "${MSG_URL}" + text="${text:4096}" + done +} + +send_html_message() { + local text="${2}" + until [ -z "${text}" ]; do + sendJson "${1}" '"text":"'"${text:0:4096}"'","parse_mode":"html"' "${MSG_URL}" + text="${text:4096}" + done +} + +old_send_keyboard() { + local text='"text":"'"${2}"'"' + shift 2 + local keyboard=init + OLDIFS=$IFS + IFS=$(echo -en "\"") + for f in "$@" ;do [ "$f" != " " ] && keyboard="$keyboard, [\"$f\"]";done + IFS=$OLDIFS + keyboard=${keyboard/init, /} + sendJson "${1}" "${text}"', "reply_markup": {"keyboard": [ '"${keyboard}"' ],"one_time_keyboard": true}' "$MSG_URL" +} + +ISEMPTY="ThisTextIsEmptyAndWillBeDeleted" +sendEmpty() { + sendJson "${@}" + [[ "${2}" = *"${ISEMPTY}"* ]] && delete_message "${1}" "${BOTSENT[ID]}" +} +send_keyboard() { + if [[ "$3" != *'['* ]]; then old_send_keyboard "${@}"; return; fi + local text='"text":"'"${2}"'"'; [ "${2}" = "" ] && text='"text":"'"${ISEMPTY}"'"' + local one_time=', "one_time_keyboard":true' && [ "$4" != "" ] && one_time="" + sendEmpty "${1}" "${text}"', "reply_markup": {"keyboard": [ '"${3}"' ] '"${one_time}"'}' "$MSG_URL" + # '"text":"$2", "reply_markup": {"keyboard": [ ${3} ], "one_time_keyboard": true}' +} + +remove_keyboard() { + local text='"text":"'"${2}"'"'; [ "${2}" = "" ] && text='"text":"'"${ISEMPTY}"'"' + sendEmpty "${1}" "${text}"', "reply_markup": {"remove_keyboard":true}' "$MSG_URL" + #JSON='"text":"$2", "reply_markup": {"remove_keyboard":true}' +} +send_inline_keyboard() { + local text='"text":"'"${2}"'"'; [ "${2}" = "" ] && text='"text":"'"${ISEMPTY}"'"' + sendEmpty "${1}" "${text}"', "reply_markup": {"inline_keyboard": [ '"${3}"' ]}' "$MSG_URL" + # JSON='"text":"$2", "reply_markup": {"inline_keyboard": [ $3->[{"text":"text", "url":"url"}]<- ]}' +} +send_button() { + send_inline_keyboard "${1}" "${2}" '[ {"text":"'"${3}"'", "url":"'"${4}"'"}]' +} + + +UPLOADDIR="${BASHBOT_UPLOAD:-${TMPDIR}/upload}" + +send_file() { + [ "$2" = "" ] && return + local file="$2" + local CAPTION=',"caption":"'$3'"'; [ "$3" = "" ] && CAPTION="" + # file access checks ... + [[ "$file" = *'..'* ]] && return # no directory traversal + [[ "$file" = '.'* ]] && return # no hidden or relative files + if [[ "$file" = '/'* ]] ; then + [[ "$file" =~ $FILE_REGEX ]] || return # absulute must match REGEX + else + file="${UPLOADDIR:-NOUPLOADDIR}/${file}" # othiers must be in UPLOADDIR + fi + [ -r "$file" ] || return # and file must exits of course + + local ext="${file##*.}" + case $ext in + mp3|flac) + CUR_URL="$AUDIO_URL" + WHAT="audio" + STATUS="upload_audio" + ;; + png|jpg|jpeg|gif) + CUR_URL="$PHO_URL" + WHAT="photo" + STATUS="upload_photo" + ;; + webp) + CUR_URL="$STICKER_URL" + WHAT="sticker" + STATUS="upload_photo" + ;; + mp4) + CUR_URL="$VIDEO_URL" + WHAT="video" + STATUS="upload_video" + ;; + + ogg) + CUR_URL="$VOICE_URL" + WHAT="voice" + STATUS="upload_audio" + ;; + *) + CUR_URL="$DOCUMENT_URL" + WHAT="document" + STATUS="upload_document" + ;; + esac + send_action "${1}" "$STATUS" + # shellcheck disable=SC2034 + sendJson "${1}" '"'"$WHAT"'":"'"$2"'"'"$CAPTION"'"' "$CUR_URL" +} + +# typing for text messages, upload_photo for photos, record_video or upload_video for videos, record_audio or upload_audio for audio files, upload_document for general files, find_location for location +send_action() { + [ "$2" = "" ] && return + sendJson "${1}" '"action": "'"${2}"'"' "$ACTION_URL" +} + +send_location() { + [ "$3" = "" ] && return + sendJson "${1}" '"latitude": '"${2}"', "longitude": '"${3}"'' "$LOCATION_URL" +} + +send_venue() { + local add="" + [ "$5" = "" ] && return + [ "$6" != "" ] && add=', "foursquare_id": '"$6"'' + sendJson "${1}" '"latitude": '"${2}"', "longitude": '"${3}"', "address": "'"${5}"'", "title": "'"${4}"'"'"${add}" "$VENUE_URL" +} + + +forward_message() { + [ "$3" = "" ] && return + sendJson "${1}" '"from_chat_id": '"${2}"', "message_id": '"${3}"'' "$FORWARD_URL" +} +forward() { # backward compatibility + forward_message "$@" || return +} + +send_message() { + [ "$2" = "" ] && return + local text keyboard btext burl no_keyboard file lat long title address sent + text="$(sed <<< "${2}" 's/ mykeyboardend.*//;s/ *my[kfltab][a-z]\{2,13\}startshere.*//')$(sed <<< "${2}" -n '/mytextstartshere/ s/.*mytextstartshere//p')" + text="$(sed <<< "${text}" 's/ *mynewlinestartshere */\r\n/g')" + [ "$3" != "safe" ] && { + no_keyboard="$(sed <<< "${2}" '/mykeyboardendshere/!d;s/.*mykeyboardendshere.*/mykeyboardendshere/')" + keyboard="$(sed <<< "${2}" '/mykeyboardstartshere /!d;s/.*mykeyboardstartshere *//;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + btext="$(sed <<< "${2}" '/mybtextstartshere /!d;s/.*mybtextstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + burl="$(sed <<< "${2}" '/myburlstartshere /!d;s/.*myburlstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//g;s/ *mykeyboardendshere.*//g')" + file="$(sed <<< "${2}" '/myfilelocationstartshere /!d;s/.*myfilelocationstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + lat="$(sed <<< "${2}" '/mylatstartshere /!d;s/.*mylatstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + long="$(sed <<< "${2}" '/mylongstartshere /!d;s/.*mylongstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + title="$(sed <<< "${2}" '/mytitlestartshere /!d;s/.*mytitlestartshere //;s/ *my[kfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + address="$(sed <<< "${2}" '/myaddressstartshere /!d;s/.*myaddressstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + } + if [ "$no_keyboard" != "" ]; then + remove_keyboard "$1" "$text" + sent=y + fi + if [ "$keyboard" != "" ]; then + if [[ "$keyboard" != *"["* ]]; then # pre 0.60 style + keyboard="[ ${keyboard//\" \"/\" \] , \[ \"} ]" + fi + send_keyboard "$1" "$text" "$keyboard" + sent=y + fi + if [ "$btext" != "" ] && [ "$burl" != "" ]; then + send_button "$1" "$text" "$btext" "$burl" + sent=y + fi + if [ "$file" != "" ]; then + send_file "$1" "$file" "$text" + sent=y + fi + if [ "$lat" != "" ] && [ "$long" != "" ]; then + if [ "$address" != "" ] && [ "$title" != "" ]; then + send_venue "$1" "$lat" "$long" "$title" "$address" + else + send_location "$1" "$lat" "$long" + fi + sent=y + fi + if [ "$sent" != "y" ];then + send_text "$1" "$text" + fi + +} + +send_text() { + case "$2" in + html_parse_mode*) + send_html_message "$1" "${2//html_parse_mode}" + ;; + markdown_parse_mode*) + send_markdown_message "$1" "${2//markdown_parse_mode}" + ;; + *) + send_normal_message "$1" "$2" + ;; + esac +} + diff --git a/mycommands.sh b/mycommands.sh index 6584e8f..fb00731 100644 --- a/mycommands.sh +++ b/mycommands.sh @@ -2,38 +2,35 @@ # files: mycommands.sh.dist # copy to mycommands.sh and add all your commands and functions here ... # -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # -# shellcheck disable=SC2154 -# shellcheck disable=SC2034 - # uncomment the following lines to overwrite info and help messages # bashbot_info='This is bashbot, the Telegram bot written entirely in bash. #' # bashbot_help='*Available commands*: #' +res="" -if [ "$1" = "source" ];then - # Set INLINE to 1 in order to receive inline queries. - # To enable this option in your bot, send the /setinline command to @BotFather. - INLINE="0" - # Set to .* to allow sending files from all locations - FILE_REGEX='/home/user/allowed/.*' +# Set INLINE to 1 in order to receive inline queries. +# To enable this option in your bot, send the /setinline command to @BotFather. +export INLINE="0" +# Set to .* to allow sending files from all locations +export FILE_REGEX='/home/user/allowed/.*' -else +if [ "$1" != "source" ];then # your additional bahsbot commands # NOTE: command can have @botname attached, you must add * in case tests... mycommands() { - case "$MESSAGE" in + case "${MESSAGE}" in '/echo'*) # example echo command send_normal_message "${CHAT[ID]}" "$MESSAGE" ;; '/question'*) # start interactive questions checkproc if [ "$res" -gt 0 ] ; then - startproc "example/question" + startproc "examples/question.sh" else send_normal_message "${CHAT[ID]}" "$MESSAGE already running ..." fi @@ -42,7 +39,7 @@ else '/run_notify'*) # start notify background job myback="notify"; checkback "$myback" if [ "$res" -gt 0 ] ; then - background "example/notify 60" "$myback" # notify every 60 seconds + background "examples/notify.sh 60" "$myback" # notify every 60 seconds else send_normal_message "${CHAT[ID]}" "Background command $myback already running ..." fi @@ -64,6 +61,7 @@ else ####################### # Inline query examples, do not use them in production (exept image search ;-) # shellcheck disable=SC2128 + iQUERY="${iQUERY,,}" # all lowercase case "${iQUERY}" in "image "*) # search images with yahoo local search="${iQUERY#* }" @@ -107,6 +105,7 @@ else answer_inline_query "${iQUERY[ID]}" "cached_gif" "BQADBAADIwYAAmwsDAABlIia56QGP0YC" ;; esac +set +x } # place your processing functions here @@ -116,7 +115,7 @@ else local image result sep="" count="1" result="$(wget --user-agent 'Mozilla/5.0' -qO - "https://images.search.yahoo.com/search/images?p=$1" | sed 's/>"${LOGFILE}" - if ! diff -q "${TESTDIR}/${file}" "${REFDIR}/${file}" >>"${LOGFILE}"; then echo "${NOSUCCESS} Fail diff ${file}!"; FAIL="1"; fi - + diff -q "${TESTDIR}/${file}" "${REFDIR}/${file}" >>"${LOGFILE}" || { echo "${NOSUCCESS} Fail diff ${file}!"; FAIL="1"; } done [ "${FAIL}" != "0" ] && exit "${FAIL}" echo "${SUCCESS}" -echo "Test Sourcing of bashbot.sh ..." trap exit 1 EXIT cd "${TESTDIR}" || exit +echo "Test if $JSONSHFILE exists ..." +[ ! -x "$JSONSHFILE" ] && { echo "${NOSUCCESS} Fail diff ${file}!"; exit 1; } + +echo "Test Sourcing of bashbot.sh ..." # shellcheck source=./bashbot.sh source "${TESTDIR}/bashbot.sh" source +source "${TESTDIR}/commands.sh" source + trap '' EXIT cd "${DIRME}" || exit 1 echo "${SUCCESS}" diff --git a/test/d-JSON.sh-test.sh b/test/d-JSON.sh-test.sh index b03d601..70be42f 100755 --- a/test/d-JSON.sh-test.sh +++ b/test/d-JSON.sh-test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # include common functions and definitions # shellcheck source=test/ALL-tests.inc.sh diff --git a/test/d-process_inline-test.sh b/test/d-process_inline-test.sh index ef5091c..3c36f23 100755 --- a/test/d-process_inline-test.sh +++ b/test/d-process_inline-test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # include common functions and definitions # shellcheck source=test/ALL-tests.inc.sh @@ -12,7 +12,7 @@ cd "${TESTDIR}" || exit 1 # shellcheck source=./bashbot.sh source "${TESTDIR}/bashbot.sh" source # shellcheck source=./bashbot.sh -source "${TESTDIR}/modules/inline.sh" source +source "${TESTDIR}/modules/answerInline.sh" source # overwrite get_file for test get_file() { diff --git a/test/d-process_message-test.sh b/test/d-process_message-test.sh index 0877c4b..6b13f25 100755 --- a/test/d-process_message-test.sh +++ b/test/d-process_message-test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # include common functions and definitions # shellcheck source=test/ALL-tests.inc.sh @@ -11,6 +11,8 @@ set -e cd "${TESTDIR}" || exit 1 # shellcheck source=./bashbot.sh source "${TESTDIR}/bashbot.sh" source +# shellcheck source=./bashbot.sh +source "${TESTDIR}/commands.sh" source # overwrite get_file for test get_file() { @@ -21,20 +23,14 @@ get_file() { export UPDATE UPDATE="$(cat "${INPUTFILE}")" -# run process_message with and without phyton +# run process_message echo "Check process_message ..." -for i in 1 2 -do - [ "${i}" = "1" ] && ! which python >/dev/null 2>&1 && continue - [ "${i}" = "1" ] && echo " ... with JsonDecode Phyton" && unset BASHBOT_DECODE - [ "${i}" = "2" ] && echo " ... with JsonDecode Bash" && export BASHBOT_DECODE="yes" - set -x - { process_message "0"; set +x; } >>"${LOGFILE}" 2>&1; +set -x +{ process_message "0"; set +x; } >>"${LOGFILE}" 2>&1; - # output processed input - print_array "USER" "CHAT" "REPLYTO" "FORWARD" "URLS" "CONTACT" "CAPTION" "LOCATION" "MESSAGE" "VENUE" >"${OUTPUTFILE}" - diff -c "${REFFILE}" "${OUTPUTFILE}" || exit 1 - echo "${SUCCESS}" -done +# output processed input +print_array "USER" "CHAT" "REPLYTO" "FORWARD" "URLS" "CONTACT" "CAPTION" "LOCATION" "MESSAGE" "VENUE" >"${OUTPUTFILE}" +diff -c "${REFFILE}" "${OUTPUTFILE}" || exit 1 +echo "${SUCCESS}" cd "${DIRME}" || exit 1 diff --git a/test/d-send_message-test.sh b/test/d-send_message-test.sh index 856d999..1f7759b 100755 --- a/test/d-send_message-test.sh +++ b/test/d-send_message-test.sh @@ -1,34 +1,42 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # include common functions and definitions # shellcheck source=test/ALL-tests.inc.sh source "./ALL-tests.inc.sh" set -e +set +f cd "${TESTDIR}" || exit 1 # source bashbot.sh function, uncomment if you want to test functions # shellcheck source=./bashbot.sh source "${TESTDIR}/bashbot.sh" source -source "${TESTDIR}/modules/background.sh" +# shellcheck source=./bashbot.sh +source "${TESTDIR}/commands.sh" source + +_is_function send_message || echo "Send Message not found!" # start writing your tests here ... # over write sendJson to output parameter only +sendEmpty() { + printf 'chat:%s\tJSON:%s\nURL:%s\n\n' "${1}" "${2}" "${3}" +} + sendJson() { printf 'chat:%s\tJSON:%s\nURL:%s\n\n' "${1}" "${2}" "${3}" } # send text input to send_message -#set -x echo -n " Send line ..." -while IFS='' read -r line || [[ -n "$line" ]]; do + +while read -r line ; do echo -n "." send_message "123456" "$line" >>"${OUTPUTFILE}" -done < "${INPUTFILE}" 2>>"${LOGFILE}" +done < "${INPUTFILE}" #2>>"${LOGFILE}" echo " done." { diff -c "${REFFILE}" "${OUTPUTFILE}" || exit 1; } | cat -v diff --git a/test/d-send_message-test/d-send_message-test.input b/test/d-send_message-test/d-send_message-test.input index 0a1c6b2..1c17d70 100644 --- a/test/d-send_message-test/d-send_message-test.input +++ b/test/d-send_message-test/d-send_message-test.input @@ -8,10 +8,8 @@ markdown_parse_mode This is a *MARKDOWN* text mynewlinestartshere with a line br # test for keyboard, file, venue output Text plus keyboard will appear in chat mykeyboardstartshere [ "Yep, sure" , "No, highly unlikely" ] -Text plus file will appear in chat myfilelocationstartshere /home/user/doge.jpg Text plus location will appear in chat mylatstartshere la10 mylongstartshere lo20 Text plus vuene will appear in chat mylatstartshere la10 mylongstartshere lo20 mytitlestartshere my home myaddressstartshere Diagon Alley N. 37 -All in one will appear in chat mykeyboardstartshere [ "Yep, sure" , "No, highly unlikely" ] myfilelocationstartshere /home/user/doge.jpg mylatstartshere la10 mylongstartshere lo20 # test for new inline button Text plus keyboard will appear in chat mybtextstartshere Button Text myburlstartshere https://www... diff --git a/test/d-send_message-test/d-send_message-test.result b/test/d-send_message-test/d-send_message-test.result index 575d856..c0dfa66 100644 --- a/test/d-send_message-test/d-send_message-test.result +++ b/test/d-send_message-test/d-send_message-test.result @@ -28,24 +28,12 @@ URL:https://api.telegram.org/botbashbottestscript/sendMessage chat:123456 JSON:"text":"Text plus keyboard will appear in chat", "reply_markup": {"keyboard": [ [ "Yep, sure" , "No, highly unlikely" ] ] , "one_time_keyboard":true} URL:https://api.telegram.org/botbashbottestscript/sendMessage -chat:123456 JSON:"action": "upload_photo" -URL:https://api.telegram.org/botbashbottestscript/sendChatAction - chat:123456 JSON:"latitude": la10, "longitude": lo20 URL:https://api.telegram.org/botbashbottestscript/sendLocation chat:123456 JSON:"latitude": la10, "longitude": lo20, "address": "Diagon Alley N. 37", "title": "my home" URL:https://api.telegram.org/botbashbottestscript/sendVenue -chat:123456 JSON:"text":"All in one will appear in chat", "reply_markup": {"keyboard": [ [ "Yep, sure" , "No, highly unlikely" ] ] , "one_time_keyboard":true} -URL:https://api.telegram.org/botbashbottestscript/sendMessage - -chat:123456 JSON:"action": "upload_photo" -URL:https://api.telegram.org/botbashbottestscript/sendChatAction - -chat:123456 JSON:"latitude": la10, "longitude": lo20 -URL:https://api.telegram.org/botbashbottestscript/sendLocation - chat:123456 JSON:"text":"# test for new inline button" URL:https://api.telegram.org/botbashbottestscript/sendMessage diff --git a/test/d-user_is-test.sh b/test/d-user_is-test.sh index cc1472b..7c2b750 100755 --- a/test/d-user_is-test.sh +++ b/test/d-user_is-test.sh @@ -1,17 +1,20 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # include common functions and definitions # shellcheck source=test/ALL-tests.inc.sh source "./ALL-tests.inc.sh" set -e +set +f cd "${TESTDIR}" || exit 1 # source bashbot.sh function, uncomment if you want to test functions # shellcheck source=./bashbot.sh source "${TESTDIR}/bashbot.sh" source +# shellcheck source=./bashbot.sh +source "${TESTDIR}/commands.sh" source # start writing your tests here ... diff --git a/test/e-env-test.sh b/test/e-env-test.sh index a079317..5f03074 100755 --- a/test/e-env-test.sh +++ b/test/e-env-test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v0.72-1-g67c47ac +#### $$VERSION$$ v0.76-1-ge8a1fd0 # include common functions and definitions # shellcheck source=test/ALL-tests.inc.sh