Project: Location Sharing Telegram Bot
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 main2
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 return23 }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 settings3 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#ignore2.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 return4 }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 Chat2func makeButtons() *tb.ReplyMarkup {3
4 // shareLoc - Button to share location information5 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 package2 lat := m.Location.Lat3 valuelat := float64(lat)4
5 lng := m.Location.Lng6 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 stop3 if !m.Private() {4 return5 }6
7 // casting of value to ensure compatibility with the db package8 lat := m.Location.Lat9 valuelat := float64(lat)10
11 lng := m.Location.Lng12 valuelng := float64(lng)13
14 // initialize the data model for posting15 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 array24 db = append(db, data)25
26
27 // return confirmation message28 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 main2
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 struct16type 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 main2
3import (4 "fmt"5 "log"6 "os"7 "time"8
9 tb "gopkg.in/tucnak/telebot.v2"10)11
12// main invokes the connection command13func main() {14 // bot settings15 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 purposes20 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 bot32 b.Handle("/start", func(m *tb.Message) {33 if !m.Private() {34 return35 }36 b.Send(m.Sender, "Hello! I'm a location sharing bot.", makeButtons())37 })38
39 // handles the response when Get Map is called40 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 stop47 if !m.Private() {48 return49 }50
51 // casting of value to ensure compatibility with the db package52 lat := m.Location.Lat53 valuelat := float64(lat)54
55 lng := m.Location.Lng56 valuelng := float64(lng)57
58 // initialize the data model for posting59 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 array68 db = append(db, data)69
70
71 // return confirmation message72 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 connection77 b.Start()78}
callback.go
1package main2
3import tb "gopkg.in/tucnak/telebot.v2"4
5// makeButtons calls the function within tbot to create buttons for Telegram Chat6func makeButtons() *tb.ReplyMarkup {7
8 // shareLoc - Button to share location information9 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.