Decouple bot handlers.
This commit is contained in:
parent
1b1af39b61
commit
1ef07c39bf
3 changed files with 181 additions and 135 deletions
138
bot.go
138
bot.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultBufferSize = 100
|
const DefaultBufferSize = 100
|
||||||
|
@ -26,9 +25,6 @@ type BotAPI struct {
|
||||||
config BotConfigI
|
config BotConfigI
|
||||||
client HTTPClientI
|
client HTTPClientI
|
||||||
readonly bool
|
readonly bool
|
||||||
|
|
||||||
buffer int
|
|
||||||
shutdownChannel chan interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBot creates a new BotAPI instance.
|
// NewBot creates a new BotAPI instance.
|
||||||
|
@ -46,8 +42,6 @@ func NewBotWithClient(config BotConfigI, client HTTPClientI) *BotAPI {
|
||||||
bot := &BotAPI{
|
bot := &BotAPI{
|
||||||
config: config.(*BotConfig),
|
config: config.(*BotConfig),
|
||||||
client: client,
|
client: client,
|
||||||
buffer: DefaultBufferSize,
|
|
||||||
shutdownChannel: make(chan interface{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bot
|
return bot
|
||||||
|
@ -383,6 +377,20 @@ func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWebhookInfo allows you to fetch information about a webhook and if
|
||||||
|
// one currently is set, along with pending update count and error messages.
|
||||||
|
func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
|
||||||
|
resp, err := bot.MakeRequest("getWebhookInfo", nil)
|
||||||
|
if err != nil {
|
||||||
|
return WebhookInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var info WebhookInfo
|
||||||
|
err = json.Unmarshal(resp.Result, &info)
|
||||||
|
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetUpdates fetches updates.
|
// GetUpdates fetches updates.
|
||||||
// If a WebHook is set, this will not return any data!
|
// If a WebHook is set, this will not return any data!
|
||||||
//
|
//
|
||||||
|
@ -402,124 +410,6 @@ func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
|
||||||
return updates, err
|
return updates, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWebhookInfo allows you to fetch information about a webhook and if
|
|
||||||
// one currently is set, along with pending update count and error messages.
|
|
||||||
func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
|
|
||||||
resp, err := bot.MakeRequest("getWebhookInfo", nil)
|
|
||||||
if err != nil {
|
|
||||||
return WebhookInfo{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var info WebhookInfo
|
|
||||||
err = json.Unmarshal(resp.Result, &info)
|
|
||||||
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUpdatesChan starts and returns a channel for getting updates.
|
|
||||||
func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
|
|
||||||
ch := make(chan Update, bot.buffer)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-bot.shutdownChannel:
|
|
||||||
close(ch)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
updates, err := bot.GetUpdates(config)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
log.Println("Failed to get updates, retrying in 3 seconds...")
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, update := range updates {
|
|
||||||
if update.UpdateID >= config.Offset {
|
|
||||||
config.Offset = update.UpdateID + 1
|
|
||||||
ch <- update
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopReceivingUpdates stops the go routine which receives updates
|
|
||||||
func (bot *BotAPI) StopReceivingUpdates() {
|
|
||||||
if bot.config.GetDebug() {
|
|
||||||
log.Println("Stopping the update receiver routine...")
|
|
||||||
}
|
|
||||||
close(bot.shutdownChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: get rid of bot dependancy
|
|
||||||
// ListenForWebhook registers a http handler for a webhook.
|
|
||||||
func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
|
|
||||||
ch := make(chan Update, bot.buffer)
|
|
||||||
|
|
||||||
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
update, err := bot.HandleUpdate(r)
|
|
||||||
if err != nil {
|
|
||||||
errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, _ = w.Write(errMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ch <- *update
|
|
||||||
})
|
|
||||||
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove dependancy on bot
|
|
||||||
// ListenForWebhookRespReqFormat registers a http handler for a single incoming webhook.
|
|
||||||
func (bot *BotAPI) ListenForWebhookRespReqFormat(w http.ResponseWriter, r *http.Request) UpdatesChannel {
|
|
||||||
ch := make(chan Update, bot.buffer)
|
|
||||||
|
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
update, err := bot.HandleUpdate(r)
|
|
||||||
if err != nil {
|
|
||||||
errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, _ = w.Write(errMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ch <- *update
|
|
||||||
}(w, r)
|
|
||||||
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move it outside of bot struct, it's not related to bot
|
|
||||||
|
|
||||||
// HandleUpdate parses and returns update received via webhook
|
|
||||||
func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
err := errors.New("wrong HTTP method required POST")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var update Update
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&update)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &update, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToHTTPResponse writes the request to the HTTP ResponseWriter.
|
// WriteToHTTPResponse writes the request to the HTTP ResponseWriter.
|
||||||
//
|
//
|
||||||
// It doesn't support uploading files.
|
// It doesn't support uploading files.
|
||||||
|
|
149
bot_handler.go
Normal file
149
bot_handler.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerConfig struct {
|
||||||
|
shutdownChannel chan struct{}
|
||||||
|
bufferSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultConfig = HandlerConfig{
|
||||||
|
shutdownChannel: make(chan struct{}),
|
||||||
|
bufferSize: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
type PollingHandler struct {
|
||||||
|
bot *BotAPI
|
||||||
|
HandlerConfig
|
||||||
|
updateConfig UpdateConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPollingHandler(bot *BotAPI, updateConfig UpdateConfig) *PollingHandler {
|
||||||
|
return &PollingHandler{
|
||||||
|
bot: bot,
|
||||||
|
HandlerConfig: defaultConfig,
|
||||||
|
updateConfig: updateConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookHandler struct {
|
||||||
|
bot *BotAPI
|
||||||
|
HandlerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebhookHandler(bot *BotAPI) *WebhookHandler {
|
||||||
|
return &WebhookHandler{
|
||||||
|
bot: bot,
|
||||||
|
HandlerConfig: defaultConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitUpdatesChannel starts and returns a channel for getting updates.
|
||||||
|
func (h *PollingHandler) InitUpdatesChannel() (UpdatesChannel, error) {
|
||||||
|
w, err := h.bot.GetWebhookInfo()
|
||||||
|
if err == nil && w.IsSet() {
|
||||||
|
return nil, errors.New("webhook was set, can't use polling")
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan Update, h.bufferSize)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-h.shutdownChannel:
|
||||||
|
close(ch)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
updates, err := h.bot.GetUpdates(h.updateConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
log.Println("Failed to get updates, retrying in 3 seconds...")
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, update := range updates {
|
||||||
|
if update.UpdateID >= h.updateConfig.Offset {
|
||||||
|
h.updateConfig.Offset = update.UpdateID + 1
|
||||||
|
ch <- update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the go routine which receives updates
|
||||||
|
func (h *PollingHandler) Stop() {
|
||||||
|
if h.bot.GetConfig().GetDebug() {
|
||||||
|
log.Println("Stopping the update receiver routine...")
|
||||||
|
}
|
||||||
|
close(h.shutdownChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenForWebhook registers a http handler for a webhook.
|
||||||
|
func (h *WebhookHandler) ListenForWebhook(pattern string) UpdatesChannel {
|
||||||
|
ch := make(chan Update, h.bufferSize)
|
||||||
|
|
||||||
|
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
update, err := UnmarshalUpdate(r)
|
||||||
|
if err != nil {
|
||||||
|
errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write(errMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- *update
|
||||||
|
})
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenForWebhookRespReqFormat registers a http handler for a single incoming webhook.
|
||||||
|
func (h *WebhookHandler) ListenForWebhookRespReqFormat(w http.ResponseWriter, r *http.Request) UpdatesChannel {
|
||||||
|
ch := make(chan Update, h.bufferSize)
|
||||||
|
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer close(ch)
|
||||||
|
|
||||||
|
update, err := UnmarshalUpdate(r)
|
||||||
|
if err != nil {
|
||||||
|
errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write(errMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- *update
|
||||||
|
}(w, r)
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalUpdate parses and returns update received via webhook
|
||||||
|
func UnmarshalUpdate(r *http.Request) (*Update, error) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
err := errors.New("wrong HTTP method required POST")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var update Update
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&update)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &update, nil
|
||||||
|
}
|
|
@ -22,17 +22,19 @@ func ExampleNewBotAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Authorized on account %s", usr.UserName)
|
fmt.Printf("Authorized on account %s", usr.UserName)
|
||||||
u := tgbotapi.NewUpdate(0)
|
|
||||||
u.Timeout = 60
|
|
||||||
|
|
||||||
updates := bot.GetUpdatesChan(u)
|
updCh, err := tgbotapi.NewPollingHandler(bot, tgbotapi.NewUpdate(0)).
|
||||||
|
InitUpdatesChannel()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Optional: wait for updates and clear them if you don't want to handle
|
// Optional: wait for updates and clear them if you don't want to handle
|
||||||
// a large backlog of old messages
|
// a large backlog of old messages
|
||||||
time.Sleep(time.Millisecond * 500)
|
time.Sleep(time.Millisecond * 500)
|
||||||
updates.Clear()
|
updCh.Clear()
|
||||||
|
|
||||||
for update := range updates {
|
for update := range updCh {
|
||||||
if update.Message == nil {
|
if update.Message == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -86,10 +88,12 @@ func ExampleNewWebhook() {
|
||||||
fmt.Printf("failed to set webhook: %s", info.LastErrorMessage)
|
fmt.Printf("failed to set webhook: %s", info.LastErrorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
updates := bot.ListenForWebhook("/" + cfg.GetToken())
|
updCh := tgbotapi.NewWebhookHandler(bot).
|
||||||
|
ListenForWebhook("/bot")
|
||||||
|
|
||||||
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
|
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
|
||||||
|
|
||||||
for update := range updates {
|
for update := range updCh {
|
||||||
fmt.Printf("%+v\n", update)
|
fmt.Printf("%+v\n", update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +132,7 @@ func ExampleWebhookHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/"+cfg.GetToken(), func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/"+cfg.GetToken(), func(w http.ResponseWriter, r *http.Request) {
|
||||||
update, err := bot.HandleUpdate(r)
|
update, err := tgbotapi.UnmarshalUpdate(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%+v\n", err.Error())
|
fmt.Printf("%+v\n", err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -157,7 +161,10 @@ func ExampleInlineConfig() {
|
||||||
u := tgbotapi.NewUpdate(0)
|
u := tgbotapi.NewUpdate(0)
|
||||||
u.Timeout = 60
|
u.Timeout = 60
|
||||||
|
|
||||||
updates := bot.GetUpdatesChan(u)
|
updates, err := tgbotapi.NewPollingHandler(bot, u).InitUpdatesChannel()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
for update := range updates {
|
for update := range updates {
|
||||||
if update.InlineQuery == nil { // if no inline query, ignore it
|
if update.InlineQuery == nil { // if no inline query, ignore it
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue