telebot/webhook.go
2019-12-26 01:55:51 +03:00

151 lines
4.1 KiB
Go

package telebot
import (
"context"
"encoding/json"
"fmt"
"net/http"
)
// A WebhookTLS specifies the path to a key and a cert so the poller can open
// a TLS listener
type WebhookTLS struct {
Key string
Cert string
}
// A WebhookEndpoint describes the endpoint to which telegram will send its requests.
// This must be a public URL and can be a loadbalancer or something similar. If the
// endpoint uses TLS and the certificate is selfsigned you have to add the certificate
// path of this certificate so telegram will trust it. This field can be ignored if you
// have a trusted certifcate (letsencrypt, ...).
type WebhookEndpoint struct {
PublicURL string
Cert string
}
// A Webhook configures the poller for webhooks. It opens a port on the given
// listen address. If TLS is filled, the listener will use the key and cert to open
// a secure port. Otherwise it will use plain HTTP.
// If you have a loadbalancer ore other infrastructure in front of your service, you
// must fill the Endpoint structure so this poller will send this data to telegram. If
// you leave these values empty, your local address will be sent to telegram which is mostly
// not what you want (at least while developing). If you have a single instance of your
// bot you should consider to use the LongPoller instead of a WebHook.
// You can also leave the Listen field empty. In this case it is up to the caller to
// add the Webhook to a http-mux.
type Webhook struct {
Listen string
TLS *WebhookTLS
Endpoint *WebhookEndpoint
dest chan<- Update
bot *Bot
}
type registerResult struct {
Ok bool `json:"ok"`
ErrorCode int `json:"error_code"`
Description string `json:"description"`
}
func (h *Webhook) getFiles() map[string]File {
m := make(map[string]File)
if h.TLS != nil {
m["certificate"] = FromDisk(h.TLS.Cert)
}
// check if it is overwritten by an endpoint
if h.Endpoint != nil {
if h.Endpoint.Cert == "" {
// this can be the case if there is a loadbalancer or reverseproxy in
// front with a public cert. in this case we do not need to upload it
// to telegram. we delete the certificate from the map, because someone
// can have an internal TLS listener with a private cert
delete(m, "certificate")
} else {
// someone configured a certificate
m["certificate"] = FromDisk(h.Endpoint.Cert)
}
}
return m
}
func (h *Webhook) getParams() map[string]string {
param := make(map[string]string)
if h.TLS != nil {
param["url"] = "https://" + h.Listen
} else {
// this will not work with telegram, they want TLS
// but i allow this because telegram will send an error
// when you register this hook. in their docs they write
// that port 80/http is allowed ...
param["url"] = "http://" + h.Listen
}
if h.Endpoint != nil {
param["url"] = h.Endpoint.PublicURL
}
return param
}
func (h *Webhook) Poll(b *Bot, dest chan Update, stop chan struct{}) {
res, err := b.sendFiles("setWebhook", h.getFiles(), h.getParams())
if err != nil {
b.debug(fmt.Errorf("setWebhook failed %q: %v", string(res), err))
close(stop)
return
}
var result registerResult
err = json.Unmarshal(res, &result)
if err != nil {
b.debug(fmt.Errorf("bad json data %q: %v", string(res), err))
close(stop)
return
}
if !result.Ok {
b.debug(fmt.Errorf("cannot register webhook: %s", result.Description))
close(stop)
return
}
// store the variables so the HTTP-handler can use 'em
h.dest = dest
h.bot = b
if h.Listen == "" {
h.waitForStop(stop)
return
}
s := &http.Server{
Addr: h.Listen,
Handler: h,
}
go func(stop chan struct{}) {
h.waitForStop(stop)
s.Shutdown(context.Background())
}(stop)
if h.TLS != nil {
s.ListenAndServeTLS(h.TLS.Cert, h.TLS.Key)
} else {
s.ListenAndServe()
}
}
func (h *Webhook) waitForStop(stop chan struct{}) {
<-stop
close(stop)
}
// The handler simply reads the update from the body of the requests
// and writes them to the update channel.
func (h *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var update Update
err := json.NewDecoder(r.Body).Decode(&update)
if err != nil {
h.bot.debug(fmt.Errorf("cannot decode update: %v", err))
return
}
h.dest <- update
}