telebot/webhook.go

208 lines
5.6 KiB
Go
Raw Permalink Normal View History

2023-02-01 12:08:20 +03:00
package telebot
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
)
// A WebhookTLS specifies the path to a key and a cert so the poller can open
// a TLS listener.
type WebhookTLS struct {
Key string `json:"key"`
Cert string `json:"cert"`
}
// 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 self-signed 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 certificate (letsencrypt, ...).
type WebhookEndpoint struct {
PublicURL string `json:"public_url"`
Cert string `json:"cert"`
}
// 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 `json:"url"`
MaxConnections int `json:"max_connections"`
AllowedUpdates []string `json:"allowed_updates"`
IP string `json:"ip_address"`
DropUpdates bool `json:"drop_pending_updates"`
SecretToken string `json:"secret_token"`
// (WebhookInfo)
HasCustomCert bool `json:"has_custom_certificate"`
PendingUpdates int `json:"pending_update_count"`
ErrorUnixtime int64 `json:"last_error_date"`
ErrorMessage string `json:"last_error_message"`
SyncErrorUnixtime int64 `json:"last_synchronization_error_date"`
TLS *WebhookTLS
Endpoint *WebhookEndpoint
dest chan<- Update
bot *Bot
}
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 {
params := make(map[string]string)
if h.MaxConnections != 0 {
params["max_connections"] = strconv.Itoa(h.MaxConnections)
}
if len(h.AllowedUpdates) > 0 {
data, _ := json.Marshal(h.AllowedUpdates)
params["allowed_updates"] = string(data)
}
if h.IP != "" {
params["ip_address"] = h.IP
}
if h.DropUpdates {
params["drop_pending_updates"] = strconv.FormatBool(h.DropUpdates)
}
if h.SecretToken != "" {
params["secret_token"] = h.SecretToken
}
if h.TLS != nil {
params["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 ...
params["url"] = "http://" + h.Listen
}
if h.Endpoint != nil {
params["url"] = h.Endpoint.PublicURL
}
return params
}
func (h *Webhook) Poll(b *Bot, dest chan Update, stop chan struct{}) {
if err := b.SetWebhook(h); err != nil {
b.debug(err)
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) {
if h.SecretToken != "" && r.Header.Get("X-Telegram-Bot-Api-Secret-Token") != h.SecretToken {
h.bot.debug(fmt.Errorf("invalid secret token in request"))
return
}
var update Update
if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
h.bot.debug(fmt.Errorf("cannot decode update: %v", err))
return
}
h.dest <- update
}
// Webhook returns the current webhook status.
func (b *Bot) Webhook() (*Webhook, error) {
data, err := b.Raw("getWebhookInfo", nil)
if err != nil {
return nil, err
}
var resp struct {
Result Webhook
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return &resp.Result, nil
}
// SetWebhook configures a bot to receive incoming
// updates via an outgoing webhook.
func (b *Bot) SetWebhook(w *Webhook) error {
_, err := b.sendFiles("setWebhook", w.getFiles(), w.getParams())
return err
}
// RemoveWebhook removes webhook integration.
func (b *Bot) RemoveWebhook(dropPending ...bool) error {
drop := false
if len(dropPending) > 0 {
drop = dropPending[0]
}
_, err := b.Raw("deleteWebhook", map[string]bool{
"drop_pending_updates": drop,
})
return err
}