Merge pull request #628 from tucnak/v3-middleware-fix

middleware: fix the append's capacity reusing
pull/606/head v3.1.4
demget 7 months ago committed by GitHub
commit 2f38e4952f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -160,21 +160,20 @@ var (
//
// Example:
//
// b.Handle("/start", func (c tele.Context) error {
// return c.Reply("Hello!")
// })
// b.Handle("/start", func (c tele.Context) error {
// return c.Reply("Hello!")
// })
//
// b.Handle(&inlineButton, func (c tele.Context) error {
// return c.Respond(&tele.CallbackResponse{Text: "Hello!"})
// })
// b.Handle(&inlineButton, func (c tele.Context) error {
// return c.Respond(&tele.CallbackResponse{Text: "Hello!"})
// })
//
// Middleware usage:
//
// b.Handle("/ban", onBan, middleware.Whitelist(ids...))
//
// b.Handle("/ban", onBan, middleware.Whitelist(ids...))
func (b *Bot) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) {
if len(b.group.middleware) > 0 {
m = append(b.group.middleware, m...)
m = appendMiddleware(b.group.middleware, m)
}
handler := func(c Context) error {
@ -256,16 +255,16 @@ func (b *Bot) NewContext(u Update) Context {
// some Sendable (or string!) and optional send options.
//
// NOTE:
// Since most arguments are of type interface{}, but have pointer
// method receivers, make sure to pass them by-pointer, NOT by-value.
//
// What is a send option exactly? It can be one of the following types:
// Since most arguments are of type interface{}, but have pointer
// method receivers, make sure to pass them by-pointer, NOT by-value.
//
// - *SendOptions (the actual object accepted by Telegram API)
// - *ReplyMarkup (a component of SendOptions)
// - Option (a shortcut flag for popular options)
// - ParseMode (HTML, Markdown, etc)
// What is a send option exactly? It can be one of the following types:
//
// - *SendOptions (the actual object accepted by Telegram API)
// - *ReplyMarkup (a component of SendOptions)
// - Option (a shortcut flag for popular options)
// - ParseMode (HTML, Markdown, etc)
func (b *Bot) Send(to Recipient, what interface{}, opts ...interface{}) (*Message, error) {
if to == nil {
return nil, ErrBadRecipient
@ -437,14 +436,13 @@ func (b *Bot) Copy(to Recipient, msg Editable, options ...interface{}) (*Message
//
// Use cases:
//
// b.Edit(m, m.Text, newMarkup)
// b.Edit(m, "new <b>text</b>", tele.ModeHTML)
// b.Edit(m, &tele.ReplyMarkup{...})
// b.Edit(m, &tele.Photo{File: ...})
// b.Edit(m, tele.Location{42.1337, 69.4242})
// b.Edit(c, "edit inline message from the callback")
// b.Edit(r, "edit message from chosen inline result")
//
// b.Edit(m, m.Text, newMarkup)
// b.Edit(m, "new <b>text</b>", tele.ModeHTML)
// b.Edit(m, &tele.ReplyMarkup{...})
// b.Edit(m, &tele.Photo{File: ...})
// b.Edit(m, tele.Location{42.1337, 69.4242})
// b.Edit(c, "edit inline message from the callback")
// b.Edit(r, "edit message from chosen inline result")
func (b *Bot) Edit(msg Editable, what interface{}, opts ...interface{}) (*Message, error) {
var (
method string
@ -503,7 +501,6 @@ func (b *Bot) Edit(msg Editable, what interface{}, opts ...interface{}) (*Messag
//
// If edited message is sent by the bot, returns it,
// otherwise returns nil and ErrTrueResult.
//
func (b *Bot) EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, error) {
msgID, chatID := msg.MessageSig()
params := make(map[string]string)
@ -537,7 +534,6 @@ func (b *Bot) EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, erro
//
// If edited message is sent by the bot, returns it,
// otherwise returns nil and ErrTrueResult.
//
func (b *Bot) EditCaption(msg Editable, caption string, opts ...interface{}) (*Message, error) {
msgID, chatID := msg.MessageSig()
@ -571,9 +567,8 @@ func (b *Bot) EditCaption(msg Editable, caption string, opts ...interface{}) (*M
//
// Use cases:
//
// b.EditMedia(m, &tele.Photo{File: tele.FromDisk("chicken.jpg")})
// b.EditMedia(m, &tele.Video{File: tele.FromURL("http://video.mp4")})
//
// b.EditMedia(m, &tele.Photo{File: tele.FromDisk("chicken.jpg")})
// b.EditMedia(m, &tele.Video{File: tele.FromURL("http://video.mp4")})
func (b *Bot) EditMedia(msg Editable, media Inputtable, opts ...interface{}) (*Message, error) {
var (
repr string
@ -655,15 +650,14 @@ func (b *Bot) EditMedia(msg Editable, media Inputtable, opts ...interface{}) (*M
// Delete removes the message, including service messages.
// This function will panic upon nil Editable.
//
// - A message can only be deleted if it was sent less than 48 hours ago.
// - A dice message in a private chat can only be deleted if it was sent more than 24 hours ago.
// - Bots can delete outgoing messages in private chats, groups, and supergroups.
// - Bots can delete incoming messages in private chats.
// - Bots granted can_post_messages permissions can delete outgoing messages in channels.
// - If the bot is an administrator of a group, it can delete any message there.
// - If the bot has can_delete_messages permission in a supergroup or a
// channel, it can delete any message there.
//
// - A message can only be deleted if it was sent less than 48 hours ago.
// - A dice message in a private chat can only be deleted if it was sent more than 24 hours ago.
// - Bots can delete outgoing messages in private chats, groups, and supergroups.
// - Bots can delete incoming messages in private chats.
// - Bots granted can_post_messages permissions can delete outgoing messages in channels.
// - If the bot is an administrator of a group, it can delete any message there.
// - If the bot has can_delete_messages permission in a supergroup or a
// channel, it can delete any message there.
func (b *Bot) Delete(msg Editable) error {
msgID, chatID := msg.MessageSig()
@ -685,7 +679,6 @@ func (b *Bot) Delete(msg Editable) error {
//
// Currently, Telegram supports only a narrow range of possible
// actions, these are aligned as constants of this package.
//
func (b *Bot) Notify(to Recipient, action ChatAction) error {
if to == nil {
return ErrBadRecipient
@ -705,10 +698,9 @@ func (b *Bot) Notify(to Recipient, action ChatAction) error {
//
// Example:
//
// b.Ship(query) // OK
// b.Ship(query, opts...) // OK with options
// b.Ship(query, "Oops!") // Error message
//
// b.Ship(query) // OK
// b.Ship(query, opts...) // OK with options
// b.Ship(query, "Oops!") // Error message
func (b *Bot) Ship(query *ShippingQuery, what ...interface{}) error {
params := map[string]string{
"shipping_query_id": query.ID,
@ -761,9 +753,8 @@ func (b *Bot) Accept(query *PreCheckoutQuery, errorMessage ...string) error {
//
// Example:
//
// b.Respond(c)
// b.Respond(c, response)
//
// b.Respond(c)
// b.Respond(c, response)
func (b *Bot) Respond(c *Callback, resp ...*CallbackResponse) error {
var r *CallbackResponse
if resp == nil {
@ -821,7 +812,6 @@ func (b *Bot) AnswerWebApp(query *Query, r Result) (*WebAppMessage, error) {
//
// Usually, Telegram-provided File objects miss FilePath so you might need to
// perform an additional request to fetch them.
//
func (b *Bot) FileByID(fileID string) (File, error) {
params := map[string]string{
"file_id": fileID,
@ -901,7 +891,6 @@ func (b *Bot) File(file *File) (io.ReadCloser, error) {
//
// If the message is sent by the bot, returns it,
// otherwise returns nil and ErrTrueResult.
//
func (b *Bot) StopLiveLocation(msg Editable, opts ...interface{}) (*Message, error) {
msgID, chatID := msg.MessageSig()
@ -926,7 +915,6 @@ func (b *Bot) StopLiveLocation(msg Editable, opts ...interface{}) (*Message, err
//
// It supports ReplyMarkup.
// This function will panic upon nil Editable.
//
func (b *Bot) StopPoll(msg Editable, opts ...interface{}) (*Poll, error) {
msgID, chatID := msg.MessageSig()
@ -966,7 +954,6 @@ func (b *Bot) Leave(chat *Chat) error {
//
// It supports Silent option.
// This function will panic upon nil Editable.
//
func (b *Bot) Pin(msg Editable, opts ...interface{}) error {
msgID, chatID := msg.MessageSig()
@ -1011,7 +998,6 @@ func (b *Bot) UnpinAll(chat *Chat) error {
//
// Including current name of the user for one-on-one conversations,
// current username of a user, group or channel, etc.
//
func (b *Bot) ChatByID(id int64) (*Chat, error) {
return b.ChatByUsername(strconv.FormatInt(id, 10))
}
@ -1109,9 +1095,8 @@ func (b *Bot) MenuButton(chat *User) (*MenuButton, error) {
//
// It accepts two kinds of menu button arguments:
//
// - MenuButtonType for simple menu buttons (default, commands)
// - MenuButton complete structure for web_app menu button type
//
// - MenuButtonType for simple menu buttons (default, commands)
// - MenuButton complete structure for web_app menu button type
func (b *Bot) SetMenuButton(chat *User, mb interface{}) error {
params := map[string]interface{}{
"chat_id": chat.Recipient(),

@ -373,6 +373,116 @@ func TestBotOnError(t *testing.T) {
assert.True(t, ok)
}
func TestBotMiddleware(t *testing.T) {
t.Run("calling order", func(t *testing.T) {
var trace []string
handler := func(name string) HandlerFunc {
return func(c Context) error {
trace = append(trace, name)
return nil
}
}
middleware := func(name string) MiddlewareFunc {
return func(next HandlerFunc) HandlerFunc {
return func(c Context) error {
trace = append(trace, name+":in")
err := next(c)
trace = append(trace, name+":out")
return err
}
}
}
b, err := NewBot(Settings{Synchronous: true, Offline: true})
if err != nil {
t.Fatal(err)
}
b.Use(middleware("global1"), middleware("global2"))
b.Handle("/a", handler("/a"), middleware("handler1a"), middleware("handler2a"))
group := b.Group()
group.Use(middleware("group1"), middleware("group2"))
group.Handle("/b", handler("/b"), middleware("handler1b"))
b.ProcessUpdate(Update{
Message: &Message{Text: "/a"},
})
assert.Equal(t, []string{
"global1:in", "global2:in",
"handler1a:in", "handler2a:in",
"/a",
"handler2a:out", "handler1a:out",
"global2:out", "global1:out",
}, trace)
trace = trace[:0]
b.ProcessUpdate(Update{
Message: &Message{Text: "/b"},
})
assert.Equal(t, []string{
"global1:in", "global2:in",
"group1:in", "group2:in",
"handler1b:in",
"/b",
"handler1b:out",
"group2:out", "group1:out",
"global2:out", "global1:out",
}, trace)
})
fatal := func(next HandlerFunc) HandlerFunc {
return func(c Context) error {
t.Fatal("fatal middleware should not be called")
return nil
}
}
nop := func(next HandlerFunc) HandlerFunc {
return func(c Context) error {
return next(c)
}
}
t.Run("combining with global middleware", func(t *testing.T) {
b, err := NewBot(Settings{Synchronous: true, Offline: true})
if err != nil {
t.Fatal(err)
}
// Pre-allocate middleware slice to make sure
// it has extra capacity after group-level middleware is added.
b.group.middleware = make([]MiddlewareFunc, 0, 2)
b.Use(nop)
b.Handle("/a", func(c Context) error { return nil }, nop)
b.Handle("/b", func(c Context) error { return nil }, fatal)
b.ProcessUpdate(Update{Message: &Message{Text: "/a"}})
})
t.Run("combining with group middleware", func(t *testing.T) {
b, err := NewBot(Settings{Synchronous: true, Offline: true})
if err != nil {
t.Fatal(err)
}
g := b.Group()
// Pre-allocate middleware slice to make sure
// it has extra capacity after group-level middleware is added.
g.middleware = make([]MiddlewareFunc, 0, 2)
g.Use(nop)
g.Handle("/a", func(c Context) error { return nil }, nop)
g.Handle("/b", func(c Context) error { return nil }, fatal)
b.ProcessUpdate(Update{Message: &Message{Text: "/a"}})
})
}
func TestBot(t *testing.T) {
if b == nil {
t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)")

@ -4,6 +4,15 @@ package telebot
// which get called before the endpoint group or specific handler.
type MiddlewareFunc func(HandlerFunc) HandlerFunc
func appendMiddleware(a, b []MiddlewareFunc) []MiddlewareFunc {
if len(a) == 0 {
return b
}
m := make([]MiddlewareFunc, 0, len(a)+len(b))
return append(m, append(a, b...)...)
}
func applyMiddleware(h HandlerFunc, m ...MiddlewareFunc) HandlerFunc {
for i := len(m) - 1; i >= 0; i-- {
h = m[i](h)
@ -25,5 +34,5 @@ func (g *Group) Use(middleware ...MiddlewareFunc) {
// Handle adds endpoint handler to the bot, combining group's middleware
// with the optional given middleware.
func (g *Group) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) {
g.b.Handle(endpoint, h, append(g.middleware, m...)...)
g.b.Handle(endpoint, h, appendMiddleware(g.middleware, m)...)
}

Loading…
Cancel
Save