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/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultBufferSize = 100
|
||||
|
@ -26,9 +25,6 @@ type BotAPI struct {
|
|||
config BotConfigI
|
||||
client HTTPClientI
|
||||
readonly bool
|
||||
|
||||
buffer int
|
||||
shutdownChannel chan interface{}
|
||||
}
|
||||
|
||||
// NewBot creates a new BotAPI instance.
|
||||
|
@ -46,8 +42,6 @@ func NewBotWithClient(config BotConfigI, client HTTPClientI) *BotAPI {
|
|||
bot := &BotAPI{
|
||||
config: config.(*BotConfig),
|
||||
client: client,
|
||||
buffer: DefaultBufferSize,
|
||||
shutdownChannel: make(chan interface{}),
|
||||
}
|
||||
|
||||
return bot
|
||||
|
@ -383,6 +377,20 @@ func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
|
|||
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.
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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)
|
||||
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
|
||||
// a large backlog of old messages
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
updates.Clear()
|
||||
updCh.Clear()
|
||||
|
||||
for update := range updates {
|
||||
for update := range updCh {
|
||||
if update.Message == nil {
|
||||
continue
|
||||
}
|
||||
|
@ -86,10 +88,12 @@ func ExampleNewWebhook() {
|
|||
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)
|
||||
|
||||
for update := range updates {
|
||||
for update := range updCh {
|
||||
fmt.Printf("%+v\n", update)
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +132,7 @@ func ExampleWebhookHandler() {
|
|||
}
|
||||
|
||||
http.HandleFunc("/"+cfg.GetToken(), func(w http.ResponseWriter, r *http.Request) {
|
||||
update, err := bot.HandleUpdate(r)
|
||||
update, err := tgbotapi.UnmarshalUpdate(r)
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err.Error())
|
||||
} else {
|
||||
|
@ -157,7 +161,10 @@ func ExampleInlineConfig() {
|
|||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
|
||||
updates := bot.GetUpdatesChan(u)
|
||||
updates, err := tgbotapi.NewPollingHandler(bot, u).InitUpdatesChannel()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for update := range updates {
|
||||
if update.InlineQuery == nil { // if no inline query, ignore it
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue