So you want to create a Telegram bot and host it somewhere you don't have to pay much attention to. Here's a quick and (somewhat) dirty solution for you, using Go and Vercel, the serverless hosting solution.
Do your homework
Well, you already know you have to open your Vercel (and also GitHub) account (there's a good intro to it), obtain a token, and so on, and also create the basic Vercel layout Won't get into that, I told you it was going to be fast.
Here's the function
What I wanted to do is to have a Telegram bot that told my students how long until the next assignment. Incidentally, I wanted to provide them with an example of serverless functions, so I ate my own dogfood and eventually crafted this
package handler
import (
"fmt"
"net/http"
"time"
"encoding/json"
"github.com/go-telegram-bot-api/telegram-bot-api"
"log"
"io/ioutil"
)
type Hito struct {
URI string
Title string
fecha time.Time
}
type Response struct {
Msg string `json:"text"`
ChatID int64 `json:"chat_id"`
Method string `json:"method"`
}
var hitos = []Hito {
Hito {
URI: "0.Repositorio",
Title: "Datos básicos y repo",
fecha: time.Date(2020, time.September, 29, 11, 30, 0, 0, time.UTC),
},
Hito {
URI: "1.Infraestructura",
Title: "HUs y entidad principal",
fecha: time.Date(2020, time.October, 6, 11, 30, 0, 0, time.UTC),
},
Hito {
URI: "2.Tests",
Title: "Tests iniciales",
fecha: time.Date(2020, time.October, 16, 11, 30, 0, 0, time.UTC),
},
Hito {
URI: "3.Contenedores",
Title: "Contenedores",
fecha: time.Date(2020, time.October, 26, 11, 30, 0, 0, time.UTC),
},
Hito {
URI: "4.CI",
Title: "Integración continua",
fecha: time.Date(2020, time.November, 6, 23, 59, 0, 0, time.UTC),
},
Hito {
URI: "5.Serverless",
Title: "Trabajando con funciones serverless",
fecha: time.Date(2020, time.November, 24, 11, 30, 0, 0, time.UTC),
},
}
func Handler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, _ := ioutil.ReadAll(r.Body)
var update tgbotapi.Update
if err := json.Unmarshal(body,&update); err != nil {
log.Fatal("Error en el update →", err)
}
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
currentTime := time.Now()
var next int
var queda time.Duration
for indice, hito := range hitos {
if ( hito.fecha.After( currentTime ) ) {
next = indice
queda = hito.fecha.Sub( currentTime )
}
}
if update.Message.IsCommand() {
text := ""
if ( next == 0 ) {
text = "Ninguna entrega próxima"
} else {
switch update.Message.Command() {
case "kk":
text = queda.String()
case "kekeda":
text = fmt.Sprintf( "→ Próximo hito %s\n🔗 https://jj.github.io/IV/documentos/proyecto/%s\n📅 %s",
hitos[next].Title,
hitos[next].URI,
hitos[next].fecha.String(),
)
default:
text = "Usa /kk para lo que queda para el próximo hito, /kekeda para + detalle"
}
}
data := Response{ Msg: text,
Method: "sendMessage",
ChatID: update.Message.Chat.ID }
msg, _ := json.Marshal( data )
log.Printf("Response %s", string(msg))
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w,string(msg))
}
}
This will have to go into an api/kekeda_iv.go
file, which you will eventually deploy running vercel
.
First, I needed to put all data into the same file. Go packages are tricky to handle external modules, so it does not really matter. All dates are into its own data structure. I can just change that data structure and redeploy every time I need to add a new date.
The package needs to be handler
, and Vercel is going to call the Handler
function, receiving a request in r
and a pointer to a http.ResponseWriter
in w
. We'll need to read from the later, write to the former.
Fortunately, Telegram has a way of binding, through the token, a bot to an endpoint via webhooks. That's what we're going to do. Instead of having our program poll Telegram for updates, Telegram is going to dutifully call that endpoint with a payload. We obtain that payload in these bunch of lines:
defer r.Body.Close()
body, _ := ioutil.ReadAll(r.Body)
var update tgbotapi.Update
if err := json.Unmarshal(body,&update); err != nil {
log.Fatal("Error en el update →", err)
}
Telegram will invoke our endpoint with a JSON payload, so we read it from the body and convert it to a tgbotapi.Update
data structure provided by the Go Telegram Bot API. That's quite convenient, and is in fact the only thing we use from that library.
A big part of the rest of the function is our "business" logic: find out the next date, and put the index to the array in a variable we can use later.
And then comes the Telegram logic: we only get into it if there's actually a Telegram "slash" command, of which we define two. That's pretty much boilerplate, get more info, for instance in this tutorial. By the end of that, we have the message we have to send into the text
variable.
Call back
So we need to create a response, which we do in the last few lines:
data := Response{ Msg: text,
Method: "sendMessage",
ChatID: update.Message.Chat.ID }
msg, _ := json.Marshal( data )
log.Printf("Response %s", string(msg))
w.Header().Add("Content-Type", "application/json")
fmt.Fprintf(w,string(msg))
We need to put the response into a JSON message; so we first create a Response
struct. That struct needs to have a Method
field that will tell Telegram what to do, a Msg
with the message, and a ChatID
that will tell Telegram where to send it. You can put more things into that, but that's the bare essentials. In the next line we convert it to a JSON []byte
, which we stringify and log.
Next line's the catch. You need to tell the type of the response, so you set the header to the correct type: w.Header().Add("Content-Type", "application/json")
This took me a while and this question in StackOverflow to realize. Problem is, there's no log or response-to-response to check if something goes wrong.
Finally, you print the JSON to that writer. And that's it!
Provided you have deployed it, and set the hook. You need set the hook to a vercel endpoint just the once.
If you need to create more bots, just deploy more functions. Vercel has a generous free tier, so it's ideal for the kind of purposes I'm using it, teaching people.
Top comments (2)
Can you kindly give a repo link for this code
It's linked up there, but here you go. Thanks for your comment.