Bashful Bot
Goal: Create a Modularized Script
When you are in the mood, the growth of code become escalated quickly. We know, how, a long script can looks complicated. Before you know it, it become unmaintainable. Even with BASH, it is a good idea to start with, an example of code skeleton.
Single script also good when it comes to other thing such as performance. The thing is, I have a brain issue, especially when reading long script, I suddenly feeling stupid in the corner.
Feeling stupid, alone in the corner
So let’s compartmentalize each part into chunk. Make a separate script is good to understand how it works.
Preparation
Before You begin, you need to get a telegram bot token. It is already discussed in previous article.
Previous Guidance
We have already see a few bash command tricks in previous article. Now, let’s gather these commands together into a script. Continue the previous lesson.
Our Very First Script
Every journey has a begining. This is a script, only to observe the telegram update.
01-main-simple.bash
#!/usr/bin/env bash
### -- config --
# $token variable here in config.sh
config_file=~/.config/cupubot/config.sh
if [ ! -f $config_file ];
then
echo "Config not found!" && exit 0
else
source $config_file
fi
tele_url="https://api.telegram.org/bot${token}"
### -- main --
updates=$(curl -s "${tele_url}/getUpdates")
count_update=$(echo $updates | jq -r ".result | length")
for ((i=0; i<$count_update; i++)); do
update=$(echo $updates | jq -r ".result[$i]")
echo "$update\n"
done
Source:
How does it works ?
What matters here is the for loop (using C style).
for ((i=0; i<$count_update; i++)); do
...
done
Execute
Say something with your bot in your smartphone. And run the script.
% ./01-main-simple.bash
Simple Script with Section
We can rewrite this script in a more elegant fashion. Just like mowadays coding, using function, and separate it with sections.
02-main-single.bash
#!/usr/bin/env bash
### -- config --
# $token variable here in config.sh
config_file=~/.config/cupubot/config.sh
if [ ! -f $config_file ];
then
echo "Config not found!" && exit 0
else
source $config_file
fi
tele_url="https://api.telegram.org/bot${token}"
### -- task --
function process_observe() {
local i update
local updates=$(curl -s "${tele_url}/getUpdates")
local count_update=$(echo $updates | jq -r ".result | length")
for ((i=0; i<$count_update; i++)); do
update=$(echo $updates | jq -r ".result[$i]")
echo "$update\n"
done
}
### -- controller --
function do_observe() {
# no loop
process_observe
}
### -- main --
do_observe
Source:
Do not worry about the controller, It will be more clear later, after some few codes.
Execute
Run the script. No need to say something with your bot in your smartphone.
% ./02-main-single.bash
Simple Modular Script
Now, consider compartmentalize each section, refactor each chunk into script.
03-main-modular.bash
#!/usr/bin/env bash
### -- module --
DIR=$(dirname "$0")
. ${DIR}/03-config.bash
. ${DIR}/03-controller.bash
. ${DIR}/03-task-observe.bash
### -- main --
do_observe
Source:
03-config.bash
#!/usr/bin/env bash
# no need sha-bang for the script to run,
# but needed, so file manager can detect its type.
### -- config --
# $token variable here in config.sh
config_file=~/.config/cupubot/config.sh
if [ ! -f $config_file ];
then
## exit success (0)
echo "Config not found!" && exit 0
else
source $config_file
fi
tele_url="https://api.telegram.org/bot${token}"
Source:
03-controller.bash
#!/usr/bin/env bash
### -- task controller --
function do_observe() {
process_observe
}
Source:
03-task-observe.bash
#!/usr/bin/env bash
function process_observe() {
local i update
local updates=$(curl -s "${tele_url}/getUpdates")
local count_update=$(echo $updates | jq -r ".result | length")
for ((i=0; i<$count_update; i++)); do
update=$(echo $updates | jq -r ".result[$i]")
echo "$update\n"
done
}
Source:
Execute
Run the script. No need to say something with your bot in your smartphone.
% ./03-main-modular.bash
This our first modular skeleton.
Modular Script without Loop
Now we are ready to a more useful Telegram Bot API, to reply a message. Just one message. We are going to reply many messages later in a loop.
Now, consider compartmentalize each section, refactor each chunk into script.
04-main-noloop.bash
#!/usr/bin/env bash
### -- module --
DIR=$(dirname "$0")
. ${DIR}/03-config.bash # no change
. ${DIR}/04-controller.bash
. ${DIR}/03-task-observe.bash # no need
. ${DIR}/04-task-reply.bash
### -- main --
do_reply
Source:
04-controller.bash
#!/usr/bin/env bash
### -- task controller --
function do_observe() {
process_observe
}
function do_reply() {
process_reply
}
Source:
04-task-reply.bash
We can summarize all previous lessons, in this short script.
#!/usr/bin/env bash
function process_reply() {
local i update message_id chat_id
local updates=$(curl -s "${tele_url}/getUpdates")
local count_update=$(echo $updates | jq -r ".result | length")
for ((i=0; i<$count_update; i++)); do
update=$(echo $updates | jq -r ".result[$i]")
message_id=$(echo $update | jq -r ".message.message_id")
chat_id=$(echo $update | jq -r ".message.chat.id")
result=$(curl -s "${tele_url}/sendMessage" \
--data-urlencode "chat_id=${chat_id}" \
--data-urlencode "reply_to_message_id=${message_id}" \
--data-urlencode "text=Thank you for your message."
);
done
}
Source:
Execute
Run the script. No need to say something with your bot in your smartphone.
% ./04-no-loop.bash
I know it looks like it does nothing. But hey, let’s have a look here at the smartphone.
Modular Script with Loop
We are still have to wrap all the messages in a loop, so all messages can be replied.
OMG long script !
05-main-loop.bash
#!/usr/bin/env bash
### -- module --
DIR=$(dirname "$0")
. ${DIR}/05-config.bash
. ${DIR}/05-controller.bash
. ${DIR}/03-task-observe.bash # no need
. ${DIR}/05-task-reply.bash
### -- main --
loop_reply
Source:
05-config.bash
Add this line to config below tele_url.
...
tele_url="https://api.telegram.org/bot${token}"
### -- last update --
last_id_file=~/.config/cupubot/id.txt
last_id=0
if [ ! -f $last_id_file ];
then
touch $last_id_file
echo 0 > $last_id_file
else
last_id=$(cat $last_id_file)
# echo "last id = $last_id"
fi
Source:
05-controller.bash
Remove the previous do_reply and change to loop_reply.
#!/usr/bin/env bash
### -- task controller --
function do_observe() {
process_observe
}
function loop_reply() {
while true; do
process_reply
sleep 1
done
}
Source:
05-task-observe.bash
And finally this long script. This script is self explanatory. And I also add some hints about global and local variable.
#!/usr/bin/env bash
function process_reply() {
# global-project : last_id
# global-module : _
local i update message_id chat_id text
local updates=$(curl -s "${tele_url}/getUpdates?offset=$last_id")
local count_update=$(echo $updates | jq -r ".result | length")
[[ $count_update -eq 0 ]] && echo -n "."
for ((i=0; i<$count_update; i++)); do
update=$(echo $updates | jq -r ".result[$i]")
last_id=$(echo $update | jq -r ".update_id")
message_id=$(echo $update | jq -r ".message.message_id")
chat_id=$(echo $update | jq -r ".message.chat.id")
get_feedback_reply "$update"
result=$(curl -s "${tele_url}/sendMessage" \
--data-urlencode "chat_id=${chat_id}" \
--data-urlencode "reply_to_message_id=${message_id}" \
--data-urlencode "text=$return_feedback"
);
last_id=$(($last_id + 1))
echo $last_id > $last_id_file
echo -e "\n: ${text}"
done
}
function get_feedback_reply() {
# global-module : return_feedback
local update=$1
text=$(echo $update | jq -r ".message.text")
local first_word=$(echo $text | head -n 1 | awk '{print $1;}')
return_feedback='Good message !'
case $first_word in
'/id')
username=$(echo $update | jq -r ".message.chat.username")
return_feedback="You are the mighty @${username}"
;;
*)
return_feedback='Thank you for your message.'
;;
esac
}
Source:
Execute
Run the script. No need to say something with your bot in your smartphone.
% ./05-main-loop.bash
I know it looks like it does nothing. But hey, let’s have a look here at the smartphone.
How Does it works ?
What matters here are:
The while loop.
while true; do
...
done
The Function, Wrapping Process
function parse_reply() {
...
for ((i=0; i<$count_update; i++)); do
...
done
}
The Function, Bot Feedback Text
This function, made to handle
-
command: /id
-
or text
function get_feedback_reply()
...
}
I think that is all.
What is Next ?
We will make the script more usable for other people, by adding help usage .
Thank you for reading.