2023-03-21 16:37:41 -03:00
#!/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
2023-03-26 14:15:20 -03:00
# sentmsgs.txt
# delmsgs.txt
2023-03-21 16:37:41 -03:00
# 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
2023-03-26 14:15:20 -03:00
# attempts to grab the last update's update_id and prints it to stdout
2023-03-21 16:37:41 -03:00
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 } "
}
2023-03-26 14:15:20 -03:00
# 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
2023-03-21 16:37:41 -03:00
getupd( ) {
local offset = " $( calc_offset) "
local update = " $( curl -sX GET --keepalive-time 300 " ${ api_url } getUpdates " -d " offset= ${ offset } " \
2023-03-26 13:38:40 -03:00
-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 } " ) "
2023-03-21 16:37:41 -03:00
[ ! -z " ${ update } " ] && printf "%s\n" " ${ update } " >> " ${ bot_tmpdir } updates.txt "
}
getmsg_id( ) {
2023-03-21 20:41:56 -03:00
tail -n1 " ${ bot_tmpdir } ${ 1 } " | grep -om1\
2023-03-22 11:41:33 -03:00
"\"message\":{\"message_id\":[0-9]\+\|^\"message_id\":[0-9]\+" | grep -om1 "[0-9]\+"
2023-03-21 16:37:41 -03:00
}
getusr_id( ) {
2023-03-21 20:41:56 -03:00
tail -n1 " ${ bot_tmpdir } ${ 1 } " | grep -om1\
2023-03-21 16:37:41 -03:00
"\"from\":{\"id\":[0-9]\+" | grep -om1 "[0-9]\+"
}
getchat_id( ) {
2023-03-21 20:41:56 -03:00
tail -n1 " ${ bot_tmpdir } ${ 1 } " | grep -om1\
2023-03-21 16:37:41 -03:00
"\"chat\":{\"id\":-\?[0-9]\+" | grep -om1 "\-\?[0-9]\+"
}
2023-03-26 14:15:20 -03:00
# makes decoding emojis possible, important for sending them too
2023-03-21 16:37:41 -03:00
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 = " $(( ( 0 xd${ BASH_REMATCH [2] } & 0 x3ff) <<10 )) "
local W2 = " $(( 0 xd${ BASH_REMATCH [3] } & 0 x3ff )) "
U = " $(( ( W1 | W2 ) + 0 x10000 )) "
remain = " $( printf '\\U%8.8x' " ${ U } " ) ${ BASH_REMATCH [4] } ${ remain } "
out = " ${ BASH_REMATCH [1] } "
done
echo -e " ${ out } ${ remain } "
}
gettext( ) {
2023-03-21 20:41:56 -03:00
local text = " $( tail -n 1 " ${ bot_tmpdir } ${ 1 } " | grep -om1\
2023-03-22 08:14:48 -03:00
'{\?\("text"\|"caption"\):"\(\\"\|[^"]*\)*"\(,\|}\)' | grep -v "^{" | tail -n1) "
2023-03-21 16:37:41 -03:00
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( ) {
2023-03-21 20:41:56 -03:00
local user_name = " $( tail -n 1 " ${ bot_tmpdir } ${ 1 } " | grep -om1\
2023-03-21 16:37:41 -03:00
'"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( ) {
2023-03-21 20:41:56 -03:00
local username = " $( tail -n 1 " ${ bot_tmpdir } ${ 1 } " | grep -om1\
2023-03-22 11:41:33 -03:00
'"username":"\(\\"\|[^"]*\)*"\(,"language_code"\|},"chat"\)' | head -n1) "
2023-03-22 22:16:58 -03:00
grep " \"language_code\" $" <<< " ${ username } " >/dev/null && utf-16-surrogate-pair-decode " ${ username : 12 :- 17 } " \
2023-03-22 11:41:33 -03:00
|| {
grep " },\"chat\" $" <<< " ${ username } " >/dev/null && utf-16-surrogate-pair-decode " ${ username : 12 :- 9 } "
}
2023-03-21 16:37:41 -03:00
}
getchat_title( ) {
2023-03-21 20:41:56 -03:00
local chat_title = " $( tail -n 1 " ${ bot_tmpdir } ${ 1 } " | grep -om1\
2023-03-21 16:37:41 -03:00
'"title":"\(\\"\|[^"]*\)*","type":"group"' | head -n1) "
[ ! -z " ${ chat_title } " ] && utf-16-surrogate-pair-decode " ${ chat_title : 9 :- 16 } " \
2023-03-22 15:12:57 -03:00
|| getusr_name " ${ 1 } "
2023-03-21 16:37:41 -03:00
}
2023-03-22 19:08:42 -03:00
getcbk_data( ) {
2023-03-21 20:41:56 -03:00
local callbacks = " $( tail -n 1 " ${ bot_tmpdir } ${ 1 } " \
2023-03-22 19:08:42 -03:00
| grep "\"callback_query\"" | grep -om1 '"data":"\(\\"\|[^"]*\)*"}' ) "
[ ! -z " ${ callbacks } " ] && utf-16-surrogate-pair-decode " ${ callbacks : 8 :- 2 } "
2023-03-21 16:37:41 -03:00
}
2023-03-25 21:01:53 -03:00
# $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 } "
2023-03-26 09:00:42 -03:00
local kbd = "{\"inline_keyboard\":[]}"
2023-03-25 21:01:53 -03:00
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
2023-03-26 09:00:42 -03:00
sed " s/ /+/g;:a;N; $! ba;s/\n/%0A/g " <<< " ${ kbd } "
2023-03-21 16:37:41 -03:00
}
2023-03-25 21:01:53 -03:00
# $1 is target chat_id
# $2 is text
# $3 is inline keyboard (optional)
sendmsg( ) {
2023-03-26 13:38:40 -03:00
local sentmsg
2023-03-25 21:01:53 -03:00
[ -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 } " \
2023-03-26 09:00:42 -03:00
-d " reply_markup= ${ 3 } " ) "
2023-03-27 00:34:40 -03:00
sentmsg = " $( sed 's/^{"ok":false.*\|^{"ok":true,"result":{\|}$//g' <<< " ${ sentmsg } " ) "
[ ! -z " ${ sentmsg } " ] && printf "%s\n" " ${ sentmsg } " >> " ${ bot_tmpdir } sentmsgs.txt "
2023-03-21 16:37:41 -03:00
}
2023-03-26 13:38:40 -03:00
# $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 } " ) "
2023-03-27 00:34:40 -03:00
sentmsg = " $( sed 's/^{"ok":false.*\|^{"ok":true,"result":{\|}$//g' <<< " ${ sentmsg } " ) "
[ ! -z " ${ sentmsg } " ] && printf "%s\n" " ${ sentmsg } " >> " ${ bot_tmpdir } sentmsgs.txt "
2023-03-26 13:38:40 -03:00
}
2023-03-21 16:37:41 -03:00
2023-03-26 14:15:20 -03:00
# $1 is target chat_id
# $2 is target msg_id
# $3 is text
# $4 is inline keyboard (optional)
editmsg( ) {
local editmsg
2023-03-27 00:34:40 -03:00
[ -z " ${ 4 } " ] && editmsg = " $( curl -sX GET " ${ api_url } editMessageText " -d " chat_id= ${ 1 } " -d " message_id= ${ 2 } " \
2023-03-26 14:15:20 -03:00
-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 } " ) "
2023-03-27 00:34:40 -03:00
editmsg = " $( sed 's/^{"ok":false.*\|^{"ok":true,"result":{\|}$//g' <<< " ${ editmsg } " ) "
[ ! -z " ${ editmsg } " ] && printf "%s\n" " ${ editmsg } " >> " ${ bot_tmpdir } sentmsgs.txt "
2023-03-26 14:15:20 -03:00
}
# $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
2023-03-21 16:37:41 -03:00
getmsg_content( ) {
local -n _REF = " ${ 1 } "
2023-03-21 20:41:56 -03:00
_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 } " ) "
2023-03-22 19:08:42 -03:00
_REF[ callback] = " $( getcbk_data " ${ 2 } " ) "
2023-03-21 16:37:41 -03:00
}