How I wrote my first telegram bot server using simple getUpdates? 

 

 

Webhook:

This method requires a registered public domain name to listen for messages from Telegram bot so I kept it for later once I have my personal basic bot.

 

 

Polling(getUpdates):

This method doesn’t have any pre-requirements. So I started right away

 

 

Create a bot:

If new to telegram open an account using mobile number and create a bot by following https://core.telegram.org/bots#3-how-do-i-create-a-bot

(Optional) Install Desktop app and do a web login to your account to avoid switching between dev setup and phone

 

 

Run backend server:

After immense google search came up with tbot (github.com/yanzay/tbot) which is a good golang library for both Webhook and Polling with wrapper functions over telegram APIs and structs.

 

But it was a bit high level(that’s what libraries are for!) where the basic http GET, POST queries are hidden within good wrappers which makes our work much easier. To learn on what really is happening behind the scenes I started with mkdir and a main.go file with the telegram API page in side https://core.telegram.org/bots/api

 

 

Steps to copy any version code and run:

Set your bot token as environment variable TELEGRAM_TOKEN and run the code

$ export TELEGRAM_TOKEN=<token>

$ go run main.go

 

 

 

Basic Version:

The following is the first version of my main.go which does a http GET query on the telegram API “getUpdates”.

 

package main

 

import (

    "io/ioutil"

    "log"

    "net/http"

    "os"

)

 

func main() {

    token := os.Getenv("TELEGRAM_TOKEN")

    if token == "" {

        log.Fatal("Not a valid token")

    }

    query := "https://api.telegram.org/bot" + token + "/getUpdates"

    resperr := http.Get(query)

    if err != nil {

        log.Fatal("Error during Get: " + err.Error())

        return

    }

    bodyerr := ioutil.ReadAll(resp.Body)

    err = resp.Body.Close()

    if err != nil {

        log.Fatal("Error during response body close: " + err.Error())

    }

    log.Print("Response: "string(body))

}

 

OUTPUT:

RATHEGS-M-C3XA:src rathegs$ go run main.go

2020/05/02 17:01:10 Response: {"ok":true,"result":[{"update_id":332016300,

"message":{"message_id":54,"from":{"id":1060392138,"is_bot":false,"first_name":"Rathega","language_code":"en"},"chat":{"id":1060392138,"first_name":"Rathega","type":"private"},"date":1588406550,"text":"yo"}},{"update_id":332016301,

"message":{"message_id":55,"from":{"id":1060392138,"is_bot":false,"first_name":"Rathega","language_code":"en"},"chat":{"id":1060392138,"first_name":"Rathega","type":"private"},"date":1588406550,"text":"hi"}}]}

 

 

 

 

Dumb Reply Version:

Once I saw the text I entered I am now curious to reply to this message and see it in my bot. This can be achived using “sendMessage” that takes two parameters as mandatory chat_id, text

chat_id            - should be taken from getUpdates response

text                  - any string we want to send as reply

 

To parse the response I got from previous GET I created struct for the visible fields by keeping  getUpdates and Update object as reference and came up with this dumb reply version.

 

package main

 

import (

    "encoding/json"

    "io/ioutil"

    "log"

    "net/http"

    "net/url"

    "os"

    "strconv"

)

 

type Response struct {

    OK     bool     `json:"ok"`

    Result []Update `json:"result"`

}

 

type Update struct {

    UpdateID int     `json:"update_id"`

    Message  Message `json:"message"`

}

 

type Message struct {

    MessageID int    `json:"message_id"`

    From      User   `json:"from"`

    Chat      Chat   `json:"Chat"`

    Date      int    `json:"date"`

    Text      string `json:"text"`

}

 

type User struct {

    ID           int    `json:"id"`

    IsBot        bool   `json:"is_bot"`

    FirstName    string `json:"first_name"`

    LanguageCode string `json:"language_code"`

}

 

type Chat struct {

    ID        int    `json:"id"`

    FirstName string `json:"first_name"`

    Type      string `json:"type"`

}

 

func main() {

    token := os.Getenv("TELEGRAM_TOKEN")

    if token == "" {

        log.Fatal("Not a valid token")

    }

    // getUpdates

    query := "https://api.telegram.org/bot" + token + "/getUpdates"

    resperr := http.Get(query)

    if err != nil {

        log.Fatal("Error during getUpdates: " + err.Error())

        return

    }

    bodyerr := ioutil.ReadAll(resp.Body)

    err = resp.Body.Close()

    if err != nil {

        log.Fatal("Error during response body close: " + err.Error())

    }

    log.Print("Response: "string(body))

    data := Response{}

    err = json.Unmarshal(body, &data)

    if err != nil {

        log.Fatal("Error during response unmarshal: " + err.Error())

    }

    log.Print("Response struct: ", data)

 

    // sendMessage

    reply := "hello"

    query = "https://api.telegram.org/bot" + token + "/sendMessage"

    for _update := range data.Result {

        chatID := update.Message.Chat.ID

        vals := url.Values{}

        vals.Set("chat_id", strconv.Itoa(chatID))

        vals.Set("text", reply)

        resperr = http.PostForm(query, vals)

        log.Print("Sending reply as ", reply, " to chatID ", chatID)

        if err != nil {

            log.Fatal("Error during sendMessage: " + err.Error())

        }

    }

}

 

OUTPUT:

RATHEGS-M-C3XA:src rathegs$ go run main.go

2020/05/02 17:36:42 Response: {"ok":true,"result":[{"update_id":332016300,

"message":{"message_id":54,"from":{"id":1060392138,"is_bot":false,"first_name":"Rathega","language_code":"en"},"chat":{"id":1060392138,"first_name":"Rathega","type":"private"},"date":1588406550,"text":"yo"}},{"update_id":332016301,

"message":{"message_id":55,"from":{"id":1060392138,"is_bot":false,"first_name":"Rathega","language_code":"en"},"chat":{"id":1060392138,"first_name":"Rathega","type":"private"},"date":1588406550,"text":"hi"}}]}

2020/05/02 17:36:42 Response struct: {true [{332016300 {54 {1060392138 false Rathega en} {1060392138 Rathega private} 1588406550 yo}} {332016301 {55 {1060392138 false Rathega en} {1060392138 Rathega private} 1588406550 hi}}]}

2020/05/02 17:36:42 Sending reply as hello to chatID 1060392138

2020/05/02 17:36:43 Sending reply as hello to chatID 1060392138

 

The above program will send one hello to each of the text that was sent to our bot before.

 

A screenshot of a cell phone screen with bot screen

 

 

 

 

Smart Reply Version:

Instead of sending hello to each of the message I want to greet persons who say only hi to bot. For all others I wanted to reply as Say hi message.

The same APIs are used as before with two changes

 

package main

 

import (

    "encoding/json"

    "io/ioutil"

    "log"

    "net/http"

    "net/url"

    "os"

    "strconv"

    "strings"

)

 

type Response struct {

    OK     bool     `json:"ok"`

    Result []Update `json:"result"`

}

 

type Update struct {

    UpdateID int     `json:"update_id"`

    Message  Message `json:"message"`

}

 

type Message struct {

    MessageID int    `json:"message_id"`

    From      User   `json:"from"`

    Chat      Chat   `json:"Chat"`

    Date      int    `json:"date"`

    Text      string `json:"text"`

}

 

type User struct {

    ID           int    `json:"id"`

    IsBot        bool   `json:"is_bot"`

    FirstName    string `json:"first_name"`

    LanguageCode string `json:"language_code"`

}

 

type Chat struct {

    ID        int    `json:"id"`

    FirstName string `json:"first_name"`

    Type      string `json:"type"`

}

 

func main() {

    token := os.Getenv("TELEGRAM_TOKEN")

    if token == "" {

        log.Fatal("Not a valid token")

    }

    // getUpdates

    query := "https://api.telegram.org/bot" + token + "/getUpdates"

    resperr := http.Get(query)

    if err != nil {

        log.Fatal("Error during getUpdates: " + err.Error())

        return

    }

    bodyerr := ioutil.ReadAll(resp.Body)

    err = resp.Body.Close()

    if err != nil {

        log.Fatal("Error during response body close: " + err.Error())

    }

    log.Print("Response: "string(body))

    data := Response{}

    err = json.Unmarshal(body, &data)

    if err != nil {

        log.Fatal("Error during response unmarshal: " + err.Error())

    }

    log.Print("Response struct: ", data)

 

    // sendMessage

    reply := ""

    query = "https://api.telegram.org/bot" + token + "/sendMessage"

    for _update := range data.Result {

        chatID := update.Message.Chat.ID

        msgID := update.Message.MessageID // enhancement2

        inpMsg := strings.ToLower(update.Message.Text)

        if strings.Contains(inpMsg, "hi") { // enhancement1

            reply = "hello"

        } else {

            reply = "say hi"

        }

        vals := url.Values{}

        vals.Set("chat_id", strconv.Itoa(chatID))

        vals.Set("text", reply)

        vals.Set("reply_to_message_id", strconv.Itoa(msgID)) // enhancement2

        resperr = http.PostForm(query, vals)

        log.Print("Sending reply as ", reply, " to chatID ", chatID, "msgID ", msgID)

        if err != nil {

            log.Fatal("Error during sendMessage: " + err.Error())

        }

    }

}

OUTPUT:

RATHEGS-M-C3XA:src rathegs$ go run main.go

2020/05/02 18:00:36 Response: {"ok":true,"result":[{"update_id":332016300,

"message":{"message_id":54,"from":{"id":1060392138,"is_bot":false,"first_name":"Rathega","language_code":"en"},"chat":{"id":1060392138,"first_name":"Rathega","type":"private"},"date":1588406550,"text":"yo"}},{"update_id":332016301,

"message":{"message_id":55,"from":{"id":1060392138,"is_bot":false,"first_name":"Rathega","language_code":"en"},"chat":{"id":1060392138,"first_name":"Rathega","type":"private"},"date":1588406550,"text":"hi"}}]}

2020/05/02 18:00:36 Response struct: {true [{332016300 {54 {1060392138 false Rathega en} {1060392138 Rathega private} 1588406550 yo}} {332016301 {55 {1060392138 false Rathega en} {1060392138 Rathega private} 1588406550 hi}}]}

2020/05/02 18:00:37 Sending reply as say hi to chatID 1060392138msgID 54

2020/05/02 18:00:38 Sending reply as hello to chatID 1060392138msgID 55

 

The above program sends hello only when hi is sent or will reply as say hi

 

A screenshot of a cell phone screen with bot screen

 

 

 

Notes:

 

 

This basic experiment helped me on how to read the telegram bot API manual and implement the required functionalities. Hope it helps someone!