You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
5.2 KiB
201 lines
5.2 KiB
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 |
|
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 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 |
|
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 `json:"url"` |
|
MaxConnections int `json:"max_connections"` |
|
AllowedUpdates []string `json:"allowed_updates"` |
|
|
|
// (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"` |
|
|
|
IP string `json:"ip_address"` |
|
DropUpdates bool `json:"drop_pending_updates"` |
|
|
|
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.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{}) { |
|
_, ok := <-stop |
|
if ok { |
|
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 |
|
} |
|
|
|
// GetWebhook returns current webhook status. |
|
func (b *Bot) GetWebhook() (*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, 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 |
|
}
|
|
|