bashbot-lib/bashbot-lib.sh

215 lines
8.1 KiB
Bash
Executable File

#!/bin/bash
# this is just a library
# you will need to provide the following in the implementation:
# 1. the api url with it's corresponding token as
# api_url="api.telegram.org/bot[insert your api token]/"
# pay attention to the trailing forward slash
# 2. the bot id as
# bot_id="[insert your bot_id]"
# 3. the following files to read and write from
# updates.txt
# sentmsgs.txt
# delmsgs.txt
# 4. (optional) a directory for the bot's files as
# bot_tmpdir="/some/path/"
# pay attention to the trailing forward slash
# IMPORTANT: this library is not asynchronous, only one message
# is read at a time and is done so in order
# IMPORTANT: this library uses the getUpdates method with
# tcp_keepalive,NOT webhooks
# attempts to grab the last update's update_id and prints it to stdout
calc_offset() {
local current="$(bc -l <<< "$(tail -n 1 "${bot_tmpdir}updates.txt" | \
grep -om1 "\"update_id\":[0-9]\+" | \
grep -om1 "[0-9]\+") + 1" 2>/dev/null)"
[ ! -z "${current}" ] && printf "%d" "${current}"
}
# captures output of calc_offset to use as offset and fetches a single update with that offset
# uses tcp_keepalive method for 300 seconds, but telegram only keeps the connection open for 60 seconds
# prints result of fetching update if not empty and formats it for better use with the other functions
getupd() {
local offset="$(calc_offset)"
local update="$(curl -sX GET --keepalive-time 300 "${api_url}getUpdates" -d "offset=${offset}" \
-d "limit=1" -d "timeout=300")"
update="$(sed 's/{"ok":true,"result":\[\]}\|^{"ok":false.*\|^{"ok":true,"result":\[{\?//g;:a;N;$!ba;s/\n//g;s/}\]}$//g;' <<< "${update}")"
[ ! -z "${update}" ] && printf "%s\n" "${update}" >> "${bot_tmpdir}updates.txt"
}
getmsg_id() {
tail -n1 "${bot_tmpdir}${1}" | grep -om1\
"\"message\":{\"message_id\":[0-9]\+\|^\"message_id\":[0-9]\+" | grep -om1 "[0-9]\+"
}
getusr_id() {
tail -n1 "${bot_tmpdir}${1}" | grep -om1\
"\"from\":{\"id\":[0-9]\+" | grep -om1 "[0-9]\+"
}
getchat_id() {
tail -n1 "${bot_tmpdir}${1}" | grep -om1\
"\"chat\":{\"id\":-\?[0-9]\+" | grep -om1 "\-\?[0-9]\+"
}
# makes decoding emojis possible, important for sending them too
utf-16-surrogate-pair-decode() {
# shamelessly stolen from stackoverflow
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 ))"
U="$(( ( W1 | W2 ) + 0x10000 ))"
remain="$(printf '\\U%8.8x' "${U}")${BASH_REMATCH[4]}${remain}"
out="${BASH_REMATCH[1]}"
done
echo -e "${out}${remain}"
}
gettext() {
local text="$(tail -n 1 "${bot_tmpdir}${1}" | grep -om1\
'{\?\("text"\|"caption"\):"\(\\"\|[^"]*\)*"\(,\|}\)' | grep -v "^{" | tail -n1)"
grep "^\"text\"" <<< "${text}" >/dev/null && utf-16-surrogate-pair-decode "${text:8:-2}"\
|| {
grep "^\"caption\"" <<< "${text}" >/dev/null && utf-16-surrogate-pair-decode "${text:11:-2}"
}
}
getusr_name(){
local user_name="$(tail -n 1 "${bot_tmpdir}${1}" | grep -om1\
'"first_name":"\(\\"\|[^"]*\)*",\("username"\|"language_code"\)' | head -n1)"
grep "\"username\"$" <<< "${user_name}" >/dev/null && utf-16-surrogate-pair-decode "${user_name:14:-12}"\
|| {
grep "\"language_code\"$" <<< "${user_name}" >/dev/null && utf-16-surrogate-pair-decode "${user_name:14:-17}"
}
}
getusrname(){
local username="$(tail -n 1 "${bot_tmpdir}${1}" | grep -om1\
'"username":"\(\\"\|[^"]*\)*"\(,"language_code"\|},"chat"\)' | head -n1)"
grep "\"language_code\"$" <<< "${username}" >/dev/null && utf-16-surrogate-pair-decode "${username:12:-17}"\
|| {
grep "},\"chat\"$" <<< "${username}" >/dev/null && utf-16-surrogate-pair-decode "${username:12:-9}"
}
}
getchat_title(){
local chat_title="$(tail -n 1 "${bot_tmpdir}${1}" | grep -om1\
'"title":"\(\\"\|[^"]*\)*","type":"group"' | head -n1)"
[ ! -z "${chat_title}" ] && utf-16-surrogate-pair-decode "${chat_title:9:-16}"\
|| getusr_name "${1}"
}
getcbk_data() {
local callbacks="$(tail -n 1 "${bot_tmpdir}${1}"\
| grep "\"callback_query\"" | grep -om1 '"data":"\(\\"\|[^"]*\)*"}')"
[ ! -z "${callbacks}" ] && utf-16-surrogate-pair-decode "${callbacks:8:-2}"
}
# $1 is name of a bash array with the number of columns in every row such as ( 1 1 3 ) for a 3 row keyboard in which the first and second row have 1 column, and the third has 3
# $2 is name of a bash array with as many text entries as there are columns, they are added left to right, up to down, to the rows/columns configuration passed as first arg
# $3 is name of a bash array with as many data entries as there are columns, they are added left to right, up to down, to the rows/columns configuration passed as first arg
mkinline_kbd(){
local -n rows="${1}"
local -n text="${2}"
local -n data="${3}"
local kbd="{\"inline_keyboard\":[]}"
local unit="\"text\":\"\",\"callback_data\":\"\""
local rowformat="[]"
local colformat="{}"
local rowfirstpass=1
local colfirstpass=1
for (( i=0; i<"${#rows[@]}"; i++ )); do
[ "${rowfirstpass}" -eq 1 ] && {
kbd="$(sed "s/]}$/${rowformat}]}/" <<< "${kbd}")"
rowfirstpass=0
} || kbd="$(sed "s/]}$/,${rowformat}]}/" <<< "${kbd}")"
for (( j=0; j<"${rows[${i}]}"; j++)); do
[ "${colfirstpass}" -eq 1 ] && {
kbd="$(sed "s/]]}$/${colformat}]]}/" <<< "${kbd}")"
colfirstpass=0
} || kbd="$(sed "s/]]}$/,${colformat}]]}/" <<< "${kbd}")"
kbd="$(sed "s/}]]}$/${unit}}]]}/" <<< "${kbd}")"
done
colfirstpass=1
done
for i in "${text[@]}"; do
kbd="$(sed "s/\"text\":\"\"/\"text\":\"${i}\"/" <<< "${kbd}")"
done
for i in "${data[@]}"; do
kbd="$(sed "s/\"callback_data\":\"\"/\"callback_data\":\"${i}\"/" <<< "${kbd}")"
done
sed "s/ /+/g;:a;N;$!ba;s/\n/%0A/g" <<< "${kbd}"
}
# $1 is target chat_id
# $2 is text
# $3 is inline keyboard (optional)
sendmsg() {
local sentmsg
[ -z "${3}" ] && sentmsg="$(curl -sX GET "${api_url}sendMessage" -d "chat_id=${1}" \
-d "text=${2}")" \
|| sentmsg="$(curl -sX GET "${api_url}sendMessage" -d "chat_id=${1}" \
-d "text=${2}" \
-d "reply_markup=${3}")"
[ ! -z "${sentmsg}" ] && sed 's/^{"ok":false.*\|^{"ok":true,"result":{\|}$//g'\
<<< "${sentmsg}" >> "${bot_tmpdir}sentmsgs.txt"
}
# $1 is target chat_id
# $2 is target msg_id
# $3 is text
# $4 is inline keyboard (optional)
replymsg() {
local sentmsg
[ -z "${4}" ] &&\
sentmsg="$(curl -sX GET "${api_url}sendMessage" -d "chat_id=${1}" -d "reply_to_message_id=${2}"\
-d "text=${3}")" ||
sentmsg="$(curl -sX GET "${api_url}sendMessage" -d "chat_id=${1}" -d "reply_to_message_id=${2}"\
-d "text=${3}" -d "reply_markup=${4}")"
[ ! -z "${sentmsg}" ] && sed 's/^{"ok":false.*\|^{"ok":true,"result":{\|}$//g'\
<<< "${sentmsg}" >> "${bot_tmpdir}sentmsgs.txt"
}
# $1 is target chat_id
# $2 is target msg_id
# $3 is text
# $4 is inline keyboard (optional)
editmsg() {
local editmsg
[ -z "${3}" ] && editmsg="$(curl -sX GET "${api_url}editMessageText" -d "chat_id=${1}" -d "message_id=${2}" \
-d "text=${3}")" || editmsg="$(curl -sX GET "${api_url}editMessageText" -d "chat_id=${1}" -d "message_id=${2}" \
-d "text=${3}" -d "reply_markup=${4}")"
[ ! -z "${editmsg}" ] && sed 's/^{"ok":false.*\|^{"ok":true,"result":{\|}$//g'\
<<< "${editmsg}" >> "${bot_tmpdir}sentmsgs.txt"
}
# $1 is target chat_id
# $2 is target msg_id
delmsg() {
local delmsg
delmsg="$(curl -sX GET "${api_url}deleteMessage" -d "chat_id=${1}"\
-d "message_id=${2}")"
[ ! -z "${delmsg}" ] && printf "deleted message %s from chat %s\n" "${2}" "${1}" >> "${bot_tmpdir}delmsgs.txt"
}
# $1 is the name of a bash associative array to be filled with the message contents
getmsg_content(){
local -n _REF="${1}"
_REF[user_id]="$(getusr_id "${2}")"
_REF[user_name]="$(getusr_name "${2}")"
_REF[username]="$(getusrname "${2}")"
_REF[chat_id]="$(getchat_id "${2}")"
_REF[chat_title]="$(getchat_title "${2}")"
_REF[msg_id]="$(getmsg_id "${2}")"
_REF[text]="$(gettext "${2}")"
_REF[callback]="$(getcbk_data "${2}")"
}