Skip to content

Telebot V3

Compare
Choose a tag to compare
@demget demget released this 08 Feb 00:08
· 111 commits to v3 since this release

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 the 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 major and 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 path
  • tele 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 error 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 a 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 by 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 and 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.

Discussion

Discuss on Telegram