Article Series

This is a multiple-parts article. There are few sections here.

Where to Discuss?

Local Group

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

BASH: Telegram Bot: Observe Script


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

BASH: Telegram Bot: Simple Script No Loop

I know it looks like it does nothing. But hey, let’s have a look here at the smartphone.

BASH: Telegram Bot: Simple Script on 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

BASH: Telegram Bot: Simple Script with Loop

I know it looks like it does nothing. But hey, let’s have a look here at the smartphone.

BASH: Telegram Bot: Script Feedback on 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.