We're happy to finally announce V3 of Telebot framework — the best of its kind API for interacting with Telegram bots. There were many changes and innovations based on our and community's negative experience, which had a place while using V2. We've tried to include all common and most convenient routing, handling, and middleware patterns. Although this branch was widely tested and is production-ready, many planned but unimplemented things are left, so we're going to introduce even more allure features in future minor versions.
Especial thanks to all our contributors who helped through these years in fixing bugs, improving, and maintaining API.
Changelog
gopkg.in/tucnak.v3
is the new import pathtele
is the new recommended alias- Bot API 5.3, 5.4, 5.5, 5.6, 5.7
- Completed list of API errors
- Better and clarified naming (#301)
- Added missed events (cb12b17)
- Got rid of
github.com/pkg/errors
package (#462) - Tons of other minor fixes and improvements
Introducing Context
Let's define a simple handler which responds to the /start
command using v2:
b.Handle("/start", func(m *tele.Message) {
_, err := b.Send(m.Sender, "Hello!")
if err != nil {
log.Println(err)
return
}
})
There'll be lots of similar calls in any advanced stateful Telegram bot, not only with b
bot instance but also with databases and other dependencies. The default handler function signature does not imply errors handling, which is wrong. So, the first thing that suggests itself is a way to minimize errors check routine. We also need one strictly defined place where all errors come together with a corresponding event.
Secondly, while developing a complex bot, you often need to perform the same logic for different endpoint types. As an example, to send a help message whether replying to the user's command or responding to the button pressing. In v2 it will be two separate functions: one taking *Message
and another with *Callback
.
What if we combine all the possible handler types and encapsulate the different logic for different events? Here comes a Context
— an interface type, which implementation wraps an incoming update and provides a number of useful short-hands and helper functions.
b.Handle("/start", func(c tele.Context) error {
return c.Send("Hello!")
})
Isn't it handy? It's a one-line v3 versus 4-5 lines v2. Now, we force a user to return the error, so most likely it will be recorded. For this purpose, we can override a special OnError
callback:
b.OnError = func(err error, c tele.Context) {
if err != tele.ErrTrueResult {
log.Println(c.Sender().ID, err)
}
}
It also doesn't matter which type the event is — context can handle it and send the message in the current chat:
b.Handle("/start", sendHello)
b.Handle(&btnHello, sendHello)
We can go even further and let's say, edit the message if it is editable; otherwise, send it:
func sendHello(c tele.Context) error {
return c.EditOrSend("Hello!")
}
The same applies to all the functions in the Context
interface. Check them out here.
Introducing Middleware
You might notice that the context approach is somewhat similar to what we experience in most advanced HTTP server frameworks like echo
or gin
. What we're missing is an easier way to define and use middleware. The only method to do it was to wrap a poller with custom MiddlewarePoller
which performs necessary logic processing incoming updates. When we have piles and piles of extra middleware functionality it becomes clumsy.
So how do we do it now? To register your own or some pre-defined middlewares from the telebot/middleware
package, there's a Use
function.
import "gopkg.in/telebot.v3/middleware"
// Global-scoped middleware:
b.Use(middleware.Logger()) // logs every incoming handled event
b.Use(middleware.AutoRespond()) // automatically responds to every callback
// Group-scoped middleware:
adminOnly := b.Group()
adminOnly.Use(middleware.Whitelist(adminIDs...))
adminOnly.Handle("/ban", onBan)
adminOnly.Handle("/kick", onKick)
// Handler-scoped middleware:
b.Handle(tele.OnText, onText, middleware.IgnoreVia())
It comes clear how to implement your own middleware looking at built-in straightforward examples.
New OnMedia
endpoint
All media types are now associated with a Media
interface and all InputMedia
types — with an Inputtable
interface. Here perfectly fits the OnMedia
event, which matches any kind of media that wasn't handled by a specific handler.
b.Handle(tele.OnMedia, func(c tele.Context) error {
media := c.Message().Media()
// Now you can store media's kind and file ID:
kind := media.MediaType()
file := media.MediaFile()
// ...
})
Several useful helpers
A handy and my favorite Split
helper splits a list of buttons into the rows of them by a given maximum capacity for each row:
b.Handle("/things", func(c tele.Context) error {
var btns []tele.Btn
for _, thing := range things {
btns = append(btns, /* prepare a button */)
}
markup := b.NewMarkup()
markup.Inline(markup.Split(3, btns)...)
return c.Send("Choose a thing:", markup)
})
An input_field_placeholder
wrapper:
b.Handle("/name", func(c tele.Context) error {
return c.Send(
"Input your new name:",
tele.Placeholder("John Doe"),
)
})
Message's text/caption entities parser via EntityText
:
b.Handle(tele.OnText, func(c tele.Context) error {
m := c.Message()
var mentions []string
for _, ent := range m.Entities {
if ent.Type == tele.EntityMention {
mentions = append(mentions, m.EntityText(ent))
}
}
return c.Send(fmt.Sprintf("Mentions: %s.", strings.Join(mentions, ", ")))
})
Postponed message deletion:
b.Handle("/start", func(c tele.Context) error {
c.DeleteAfter(10 * time.Second)
return c.Send(...)
})
Introducing telebot/layout
This part, I believe, deserves another comprehensive description. It provides a lot of use-cases and allows you to separate the code so the business logic from a bot's content. A godoc
documentation is available here.
Check out some examples
People often complain about the lack of examples. We've prepared plenty of them: github.com/handybots. These are real and working bots opened for the good of the community.