gabrielleeyj
Navigate back to the homepage
About Me

Project: Location Sharing Telegram Bot

Gabriel Lee
December 19th, 2020 · 3 min read

In this segment, I will be explaining how you can build a Simple Telegram Bot with Golang. Before I start, I will explain the package choice for the bot. Bot Code Examples if you are interested in building a Telegram Bot there are many supported languages on the official Telegram documentation.

However, for Golang there are two different packages available. The first go-telegram-bot-api and the other telebot. In my project, I’ll be using telebot as I found that the package had all the functionality and was easier for me to understand.

Building the bot: Setting up

First to setup the bot, you’ll need to install the package.

1go get -u gopkg.in/tucnak/telebot.v2

Then you will need to seek the Botfather’s permission to build a bot. Contact @botfather, use the command /newbot and the BotFather will ask you for a name and username, if it is not already available it will prompt you to enter a new username. Once successful it will generate the API token for your bot.

Building the bot: Starting code

For a start, the minimal code required to get the bot working based on Telebot configuration is:

1package main
2
3import (
4 "log"
5 "time"
6
7 tb "gopkg.in/tucnak/telebot.v2"
8)
9
10func main() {
11 b, err := tb.NewBot(tb.Settings{
12 // You can also set custom API URL.
13 // If field is empty it equals to "https://api.telegram.org".
14 URL: "http://195.129.111.17:8012",
15
16 Token: "TOKEN_HERE",
17 Poller: &tb.LongPoller{Timeout: 10 * time.Second},
18 })
19
20 if err != nil {
21 log.Fatal(err)
22 return
23 }
24
25 b.Handle("/hello", func(m *tb.Message) {
26 b.Send(m.Sender, "Hello World!")
27 })
28
29 b.Start()
30}

So lets talk about how to build a bot that accepts a shared location and stores the data into memory. I’ll skip the explanation of the package naming and imports and talk about the bot code itself. First the setup of the bot, starts with this portion of the code.

1func main() {
2 // bot settings
3 b, err := tb.NewBot(tb.Settings{
4 URL: "",
5 Verbose: true,
6 Token: os.Getenv("TELEGRAM_TOKEN"),
7 Poller: &tb.LongPoller{Timeout: 60 * time.Second},
8 })
9 if err != nil {
10 log.Fatal(err)
11 }
12
13}

Here we have to initialize the new Bot. I left the URL blank as it will be fetching information directly from Telegram API. Verbose is set to True for debugging purposes to show in your terminal what information you are receiving from the response. Once it is in production you can remove Verbose or set it to False.

Token is the API key generated from BotFather. Of course you won’t want to publish it and let everyone know your code so you should save it in an .env file. Becareful of this are you will also need to add a .gitignore to prevent your .env file from being uploaded too.

Create a .gitignore file. Add the following to it:

1#ignore
2.env

Next Poller has been modified from the original Timeout: 10 * time.Second to Timeout: 60 * time.Second. That is because I do not want to be constantly updating every 10 seconds to the API. In telebot example the purpose of poller can be scaled into existing bot infrastructure or chain them together with a middleware.

And lastly a good habit in Golang is to always check for errors. And because the function tb.NewBot returns an error.

1if err != nil {
2 log.Fatal(err)
3 }

Building the bot: Creating the Handlers

telebot has a myriad of handlers think of it as routes in a http service. You can refer to this list here which shows the supported telebot handles.

For this example, i will be using only 2 handles. The first handle will be called when a user first starts the bot. It will check if the chat is private, and then reply back to the user with a response and create a callback to generate keyboard buttons for the user.

1b.Handle("/start", func(m *tb.Message) {
2 if !m.Private() {
3 return
4 }
5 b.Send(m.Sender, "Hello! I'm a Telegram Bot!", makeButtons())
6})

The makeButtons() purpose is to create 2 buttons one to allow the user to share their current location. And another to allow the user to get a text link to view a map. This is the only way to get location data from a user, based on my understanding of how telegram works.

1// makeButtons calls the function within tbot to create buttons for Telegram Chat
2func makeButtons() *tb.ReplyMarkup {
3
4 // shareLoc - Button to share location information
5 shareLoc := tb.ReplyButton{
6 Text: "Share Location?",
7 Location: true,
8 }
9
10 // getMap - returns URL of the Populated Map Data via Handler.
11 getMap := tb.ReplyButton{
12 Text: "Get Map",
13 }
14
15 return &tb.ReplyMarkup{
16 ReplyKeyboard: [][]tb.ReplyButton{
17 []tb.ReplyButton{shareLoc},
18 []tb.ReplyButton{getMap}},
19 ResizeReplyKeyboard: true,
20 OneTimeKeyboard: true,
21 }
22}

Next we will need to handle the user response for sharing the location data. As telebot stores location information as a float32 we will need to cast the value into a float64 to ensure compatibility with a database. We can ignore the casting by commenting out this portion of the code.

1// casting of value to ensure compatibility with the db package
2 lat := m.Location.Lat
3 valuelat := float64(lat)
4
5 lng := m.Location.Lng
6 valuelng := float64(lng)

As you can see, telebot will handle tb.OnLocation events from the *tb.Message structure of data. Once again check if the chat is private. Cast the value to float64, and initialize the data first by casting the value, and then appending it into the array. We will talk about the Post structure later.

1b.Handle(tb.OnLocation, func(m *tb.Message) {
2 // if not private stop
3 if !m.Private() {
4 return
5 }
6
7 // casting of value to ensure compatibility with the db package
8 lat := m.Location.Lat
9 valuelat := float64(lat)
10
11 lng := m.Location.Lng
12 valuelng := float64(lng)
13
14 // initialize the data model for posting
15 data := Post{
16 ChatID: m.Chat.ID,
17 Locations: Location{
18 Lat: valuelat,
19 Lng: valuelng,
20 Name: m.Chat.Username,
21 },
22 }
23 // appends the data to the array
24 db = append(db, data)
25
26
27 // return confirmation message
28 b.Send(m.Sender, "Received Location")
29 b.Send(m.Sender, "You may view the mapdata 📍<a href=\"<<<this is your map link>>>">here</a>", &tb.SendOptions{ParseMode: "HTML"})
30 })

Building the bot: Modelling the data

Next up, we need to create a model to represent the data we will need to perhaps store it somewhere. Referencing to the Telegram Message Documentation. You can find that there some basic information about how data is parsed.

Compare it with the Telebot Package, you can see some resemblance of the structure. So what we need is a portion of the message that contains the information of the location and possibily the ID of the user.

We will create a new go file called model.go this will contain the structure of the data we will need and also store it in an array on memory. this is for testing purposes

In an actual application, you will want to store the data into a database. Which I will not be covering in this example.

1package main
2
3import (
4 "context"
5 "fmt"
6 "time"
7)
8
9// Post struct represents the structure of the data to Post.
10type Post struct {
11 ChatID int64 `json:"-"`
12 Locations Location `json:"locations,omitempty"`
13}
14
15// Location is the data associated with the Post struct
16type Location struct {
17 Lat float64 `json:"lat"`
18 Lng float64 `json:"lng"`
19 Name string `json:"name"`
20}

So when we combine the entire codebase together you will have something like this.

main.go

1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "time"
8
9 tb "gopkg.in/tucnak/telebot.v2"
10)
11
12// main invokes the connection command
13func main() {
14 // bot settings
15 b, err := tb.NewBot(tb.Settings{
16 // You can also set custom API URL.
17 // If field is empty it equals to "https://api.telegram.org".
18 URL: "",
19 // listen to the request / debugging purposes
20 Verbose: true,
21 Token: os.Getenv("TELEGRAM_TOKEN"),
22 Poller: &tb.LongPoller{Timeout: 60 * time.Second},
23 })
24 if err != nil {
25 log.Fatal(err)
26 }
27
28 // Initialize the post data to store in memory.
29 db := make([]Post, 0)
30
31 // handles the response when user starts the bot
32 b.Handle("/start", func(m *tb.Message) {
33 if !m.Private() {
34 return
35 }
36 b.Send(m.Sender, "Hello! I'm a location sharing bot.", makeButtons())
37 })
38
39 // handles the response when Get Map is called
40 b.Handle("Get Map", func(m *tb.Message) {
41 b.Send(m.Sender, "You may view the mapdata 📍<a href=\"<<<this is your map link>>>">here</a>", &tb.SendOptions{ParseMode: "HTML"})
42 })
43
44 // On reply button pressed (message)
45 b.Handle(tb.OnLocation, func(m *tb.Message) {
46 // if not private stop
47 if !m.Private() {
48 return
49 }
50
51 // casting of value to ensure compatibility with the db package
52 lat := m.Location.Lat
53 valuelat := float64(lat)
54
55 lng := m.Location.Lng
56 valuelng := float64(lng)
57
58 // initialize the data model for posting
59 data := Post{
60 ChatID: m.Chat.ID,
61 Locations: Location{
62 Lat: valuelat,
63 Lng: valuelng,
64 Name: m.Chat.Username,
65 },
66 }
67 // appends the data to the array
68 db = append(db, data)
69
70
71 // return confirmation message
72 b.Send(m.Sender, "Received Location")
73 b.Send(m.Sender, "You may view the mapdata 📍<a href=\"<<<this is your map link>>>">here</a>", &tb.SendOptions{ParseMode: "HTML"})
74 })
75
76 // Starts the bot connection
77 b.Start()
78}

callback.go

1package main
2
3import tb "gopkg.in/tucnak/telebot.v2"
4
5// makeButtons calls the function within tbot to create buttons for Telegram Chat
6func makeButtons() *tb.ReplyMarkup {
7
8 // shareLoc - Button to share location information
9 shareLoc := tb.ReplyButton{
10 Text: "Share Location?",
11 Location: true,
12 }
13
14 // getMap - returns URL of the Populated Map Data via Handler.
15 getMap := tb.ReplyButton{
16 Text: "Get Map",
17 }
18
19 return &tb.ReplyMarkup{
20 ReplyKeyboard: [][]tb.ReplyButton{
21 []tb.ReplyButton{shareLoc},
22 []tb.ReplyButton{getMap}},
23 ResizeReplyKeyboard: true,
24 OneTimeKeyboard: true,
25 }
26}

So thats about how the entire bot should work together. Of course we are still lacking on other core components to make your bot work, like a database and a http server to manage the map service.

More articles from gabrielleeyj

Lessons of a Gopher

It's the end of the year as we know it. 2020 has been a tumultuous year for many people, myself included. It has been one that not only made…

December 15th, 2020 · 3 min read

Still alive & well

Looking ahead Its almost the end of the last semester of my studies! And I've finally said Good-bye to my old career. One that has helped me…

November 22nd, 2020 · 1 min read
© 2020–2021 gabrielleeyj
Link to $mailto:hello@gabrielleeyj.comLink to $https://github.com/kayigeLink to $https://www.linkedin.com/in/gabrielleeyj/Link to $https://www.instagram.com/gabrielleeyjLink to $https://www.facebook.com/melchsee