208 lines
5.6 KiB
Go
208 lines
5.6 KiB
Go
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
|
|
}
|