Releases: tucnak/telebot
Telebot V3.2
Changelog
- Bot API 6.3, 6.4, 6.5
- Group's forum topics support
- Other minor fixes and improvements
Bot API 6.3, 6.4: Topics
// 1. Create a forum topic with specified settings
topic, err := b.CreateTopic(chat, &tele.Topic{Name: "Discussions"})
if err != nil {
return err
}
// 2. Use topic methods
b.CloseTopic(chat, topic)
b.ReopenTopic(chat, topic)
b.DeleteTopic(chat, topic)
b.HideGeneralTopic(chat)
b.UnhideGeneralTopic(chat)
...
// 3. Handle topics-related events
b.Handle(tele.OnTopicCreated, func(c tele.Context) error {
topic := c.Topic()
topic.Name
topic.ThreadID
})
Bot API 6.5: ReplyRecipient
// 1. Use a ReplyRecipient constructor for both
// `request_user` and `request_chat` params
var (
replyUser = &tele.ReplyRecipient{
ID: 1, // unique
Bot: tele.Flag(false),
Premium: tele.Flag(true),
}
replyChat = &tele.ReplyRecipient{
ID: 1, // unique
Channel: false, // required
Username: tele.Flag(true),
}
)
markup := &tele.ReplyMarkup{}
markup.Reply(
markup.Row(markup.User(replyUser)),
markup.Row(markup.Chat(replyChat)),
)
b.Send(to, "Share with me...", markup)
// 2. Handle the shared user or chat
b.Handle(tele.OnUserShared, func(c tele.Context) error {
user := c.Message().UserShared
log.Println("Shared user ID:", user.UserID)
})
Use a Flag
helper that returns a pointer to the given bool:
// Flag returns a pointer to the given bool.
// Useful for passing the three-state flags to a Bot API.
// For example, see ReplyRecipient type.
func Flag(b bool) *bool {
return &b
}
Fixes and Improvements
Bot.SetWebhook
error is loggable by default (#559)telebot/layout
fixes (7065335, 4c17ca7)- Fixed the middleware applying for groups (#588)
- More errors (#570, 699e5db)
- Added missed
is_member
field to theChatMember
(#615) - Added missed
custom_emoji_id
field to theSticker
(#579) - Fixed
Results.MarshalJSON
addressing inside for-loop (#589)
Discussion
Telebot V3.1
Changelog
- Bot API 6.0, 6.1, 6.2
telebot/layout
improvements- Changed panic-recovering behavior
- Source restructure and refactoring
- Other minor fixes and improvements
Web apps support
Use the new OnWebApp
event along with the WebApp
buttons field to interact with the new stunning web app functionality.
webApp := &tele.WebApp{
URL: "https://mywebapp.com",
}
b.Handle("/start", func(c tele.Context) error {
markup := b.NewMarkup()
markup.Inline(markup.WebApp("Open", webApp))
return c.Send("Open this app!", markup)
})
b.Handle(tele.OnWebApp, func(c tele.Context) error {
webapp := c.Message().WebAppData
// Use the data fields:
// webapp.Data
// webapp.Text
}
New Recover
middleware
Be careful with allowing panics inside your handlers after the v3.0.1
release, since panics' recovering by default was removed. Instead, make sure you've added the Recover
middleware in case you really need it:
b.Use(middleware.Recover())
b.Use(middleware.Logger())
// ...
telebot/layout
improvements
- Use new
tele.M
alias in your layout calls instead ofmap[string]any
declaration:
lt.Text(c, "start", tele.M{...})
- The new
CommandsLocale
function for localized commands:
# bot.yml
commands:
/help: '{{ text `cmd_help` }}'
# en.yml
cmd_help: Show the help message
# fr.yml
cmd_help: Afficher le message d'aide
for _, locale := range lt.Locales() {
b.SetCommands(locale, lt.CommandsLocale(locale))
}
layout.Config
is now usingviper
under the hood
config:
tiers:
default: standard
all:
- name: standard
limit: 5
delay: 10m
- name: premium
limit: 50
delay: 1h
// Instead of an old sequential method,
defaultTier := lt.Get("tiers").String("default")
// you can use viper's dot notation:
defaultTier := lt.String("tiers.default")
// Some clumsy methods were removed,
// so instead of lt.Each and lt.Index use:
for _, tier := range lt.Slice("tiers.all") {
dur := tier.Duration("delay")
}
// You can also unmarshal any key directly:
var tiers []struct {
Name string
Limit int
Delay time.Duration
}
lt.UnmarshalKey("tiers.all", &tiers)
Backward incompatible changes
Unfortunately, some of them had a place in the v3.1.0
release and we can't guarantee compatibility through all the minor versions after this. The main reason for it is the fact that Telegram itself allows the breaking changes to appear in their API, however in some cases you can't help it, and breaking becomes inevitable. We break the contract beforehand so that you're previously ready for the future breaking Bot API update.
Some of the changes you should be aware of:
Bot.OnError
field won't be exposed, useSettings.OnError
instead once at initialization- Aforementioned panic-recovering behavior is optional
File.FileSize
changed theint
type to theint64
voice_chat_*
fields renamed tovideo_chat_*
*ChatJoinRequest
functions renamed to*JoinRequest
Fixes
- One minute is the default HTTP client timeout now (#478)
- Handled video response from
sendDocument
(#481) Message.Media()
includes missedSticker
type (#520)- Cancel
Raw()
execution on bot stop (#532) DeleteCommands
function is fixed (#533)- Fixed the order in which layout template's
Funcs
are applied (#546)
Discussion
Telebot V3.0.1
Changelog
Sticker
is treated as aMedia
nowDeleteCommands
is working properly nowis_premium
field is added to theChat
Stop()
behavior improvements (#532)- New
tele.M
alias type for themap[string]any
to be used with layout package middleware.Recover()
is added instead of the default panic-recovering behaviorBot.OnError
is read-only now, instead pass the error handler once throughtele.Settings
Telebot V3
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 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 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
Telebot V2.5
Changelog
User identifiers can now be bigger than 2^31 - 1 and it is no longer possible to store them in a signed 32-bit integer type. User identifiers have up to 52 significant bits, so a 64-bit integer or double-precision float type is safe for storing them. Please make sure that your code can correctly handle such user identifiers.
Announcement
Telebot V2 is no longer maintained — this is the last major version. Currently, we're working on V3, which is much more advanced and has tons of helpful features and continuous support. As there're not that many breaking changes between two APIs, consider migrating to the latest v3
branch as soon as the beta will be released.
Discussion
Telebot V2.4
Changelog
- Bot API 5.0, 5.1, 5.2
SendAlbum
improvements (#398)
Telebot V2.3
Changelog
-
Bot API 4.9
-
Special
ErrTrueResult
error for updating methods -
Extended
b.Edit()
function:b.Edit(m, m.Text, newMarkup) b.Edit(m, "new <b>text</b>", tb.ModeHTML) b.Edit(m, &tb.ReplyMarkup{...}) b.Edit(m, &tb.Photo{File: ...}) b.Edit(m, tb.Location{42.1337, 69.4242})
-
Default parse mode:
pref := tb.Settings{ Token: "", ParseMode: tb.ModeMarkdown, } b, _ := tb.NewBot(pref) b.Send(to, "*text*") // default markdown mode b.Send(to, "<b>text</b>", tb.ModeHTML) // forced html mode
-
Verbose mode for outcoming requests:
pref := tb.Settings{ Token: "", Verbose: true, } b, _ := tb.NewBot(pref) b.Notify(to, tb.Typing)
[verbose] telebot: sent request Method: sendChatAction Params: { "action": "typing", "chat_id": "..." } Response: { "ok": true, "result": true }
Fixes
Telebot V2.2
For this release, aside from Bot API implementation, we've prepared many new useful features.
Changelog
- Completed Webhook, Games, Stickers and Payments APIs
- Synchronous mode and serverless support (#219)
- Keyboard constructors (#290)
- Special
ChatID
recipient type (#263)
Serverless
Now it's possible to use telebot
together with serverless providers like AWS, Azure, etc... Thanks to one of the package's contributors, you can check out the exhaustive example in examples/awslambdaechobot.
pref := tb.Settings{
Token: "",
Synchronous: true,
}
b, err := tb.NewBot(pref)
// ...
Keyboard constructors
Use special helper functions and types to decrease boilerplate across the code. The new keyboards builder make their definition easier. Look into updated README Keyboards
section for details.
menu := &ReplyMarkup{ResizeReplyKeyboard: true}
var (
// Reply buttons.
btnHelp = menu.Text("ℹ Help")
btnSettings = menu.Text("⚙ Settings")
)
menu.Reply(
menu.Row(btnHelp),
menu.Row(btnSettings),
)
b.Handle(&btnHelp, func(m *tb.Message) {...})
b.Handle(&btnSettings, func(m *tb.Message) {...})
ChatID
type
It was a bit confusing to work with specific chat IDs. You had to wrap integer into &tb.Chat{}
every time you wanted to send a message. Imagine you have a config file with a bunch of defined group or channel IDs. Now you can use your int64
values directly with functions which accept Recipient
interface.
type Config struct {
AdminGroup tb.ChatID `yaml:"admin_group_id"`
AdsChannel tb.ChatID `yaml:"ads_channel_id"`
}
b.Send(conf.AdminGroup, "message")
b.Send(conf.AdsChannel, "message")
Fixes
- Added
OnAnimation
endpoint - Fixed regular expression for errors handling
Action
fields were removed from Buttonsb.Forward()
changed to takeEditable
instead of*Message
Telebot V2.1
Finally, a new Telebot v2.1
release with a lot of minor fixes, additions and new methods implementations.
Changelog
- Practically full implemented Bot API 4.8
- Started code coverage process (now it's about 30%)
- A convenient way to handle Telegram errors
- A lot of bug fixes
Errors
From this release, you will be able to easily check for specific Telegram errors. We have a bunch of pre-defined Bot API errors and their descriptions in errors.go
file. The work is still in progress, so feel free to contribute and to add missed entries. All errors are prefixed with telebot:
if it's telebot-related or with telegram:
if it's an API response.
msg, err := b.Send(user, "Hello World!")
if err != nil {
if err == tb.ErrBlockedByUser {
// remove user from database
}
// ...
}
Fixes
Telebot V2
"I never knew creating Telegram bots could be so sexy!"
go get -u gopkg.in/tucnak/telebot.v2
Overview
Telebot is a bot framework for Telegram Bot API. This package provides the best of its kind API for command routing, inline query requests and keyboards, as well as callbacks. Actually, I went a couple steps further, so instead of making a 1:1 API wrapper I chose to focus on the beauty of API and performance. Some of the strong sides of telebot are:
- Real concise API
- Command routing
- Middleware
- Transparent File API
- Effortless bot callbacks
All the methods of telebot API are extremely easy to memorize and get used to. Also, consider Telebot a
highload-ready solution. I'll test and benchmark the most popular actions and if necessary, optimize
against them without sacrificing API quality.
Getting Started
Let's take a look at the minimal telebot setup:
package main
import (
"time"
tb "gopkg.in/tucnak/telebot.v2"
)
func main() {
b, err := tb.NewBot(tb.Settings{
Token: "TOKEN_HERE",
Poller: &tb.LongPoller{Timeout: 10 * time.Second},
})
if err != nil {
log.Fatal(err)
return
}
b.Handle("/hello", func(m *tb.Message) {
b.Send(m.Sender, "hello world")
})
b.Start()
}
Simple, innit? Telebot's routing system takes care of deliviering updates to their endpoints, so in order to get to handle any meaningful event, all you got to do is just plug your function to one of the Telebot-provided endpoints. You can find the full list here.
b, _ := tb.NewBot(settings)
b.Handle(tb.OnText, func(m *tb.Message) {
// all the text messages that weren't
// captured by existing handlers
}
b.Handle(tb.OnPhoto, func(m *tb.Message) {
// photos only
}
b.Handle(tb.OnChannelPost, func (m *tb.Message) {
// channel posts only
})
b.Handle(tb.Query, func (q *tb.Query) {
// incoming inline queries
})
Now there's a dozen of supported endpoints (see package consts). Let me know if you'd like to see some endpoint or endpoint idea implemented. This system is completely extensible, so I can introduce them without braking backwards-compatibity.
Poller
Telebot doesn't really care how you provide it with incoming updates, as long
as you set it up with a Poller:
// Poller is a provider of Updates.
//
// All pollers must implement Poll(), which accepts bot
// pointer and subscription channel and start polling
// synchronously straight away.
type Poller interface {
// Poll is supposed to take the bot object
// subscription channel and start polling
// for Updates immediately.
//
// Poller must listen for stop constantly and close
// it as soon as it's done polling.
Poll(b *Bot, updates chan Update, stop chan struct{})
}
Telegram Bot API supports long polling and webhook integration. I don't really care about webhooks, so the only concrete Poller you'll find in the library is the LongPoller
. Poller means you can plug telebot into whatever existing bot infrastructure (load balancers?) you need, if you need to. Another great thing about pollers is that you can chain them, making some sort of middleware:
poller := &tb.LongPoller{Timeout: 15 * time.Second}
spamProtected := tb.NewMiddlewarePoller(poller, func(upd *tb.Update) bool {
if upd.Message == nil {
return true
}
if strings.Contains(upd.Message.Text, "spam") {
return false
}
return true
})
bot, _ := tb.NewBot(tb.Settings{
// ...
Poller: spamProtected,
})
// graceful shutdown
go func() {
<-time.After(N * time.Second)
bot.Stop()
})()
bot.Start() // blocks until shutdown
fmt.Println(poller.LastUpdateID) // 134237
Commands
When handling commands, Telebot supports both direct (/command
) and group-like syntax (/command@botname
) and will never deliver messages addressed to some other bot, even if privacy mode is off. For simplified deep-linking, telebot also extracts payload:
// Command: /start <PAYLOAD>
b.Handle("/start", func(m *tb.Message) {
if !m.Private() {
return
}
fmt.Println(m.Payload) // <PAYLOAD>
})
Files
Telegram allows files up to 20 MB in size.
Telebot allows to both upload (from disk / by URL) and download (from Telegram) and files in bot's scope. Also, sending any kind of media with a File created from disk will upload the file to Telegram automatically:
a := &tb.Audio{File: tb.FromDisk("file.ogg")}
fmt.Println(a.OnDisk()) // true
fmt.Println(a.InCloud()) // false
// Will upload the file from disk and send it to recipient
bot.Send(recipient, a)
// Next time you'll be sending this very *Audio, Telebot won't
// re-upload the same file but rather utilize its Telegram FileID
bot.Send(otherRecipient, a)
fmt.Println(a.OnDisk()) // true
fmt.Println(a.InCloud()) // true
fmt.Println(a.FileID) // <telegram file id: ABC-DEF1234ghIkl-zyx57W2v1u123ew11>
You might want to save certain File
s in order to avoid re-uploading. Feel free to marshal them into whatever format, File
only contain public fields, so no data will ever be lost.
Sendable
Send is undoubteldy the most important method in Telebot. Send()
accepts a
Recipient
(could be user, group or a channel) and a Sendable
. FYI, not only
all telebot-provided media types (Photo
, Audio
, Video
, etc.) are Sendable
,
but you can create composite types of your own. As long as they satisfy Sendable
,
Telebot will be able to send them out.
// Sendable is any object that can send itself.
//
// This is pretty cool, since it lets bots implement
// custom Sendables for complex kind of media or
// chat objects spanning across multiple messages.
type Sendable interface {
Send(*Bot, Recipient, *SendOptions) (*Message, error)
}
The only type at the time that doesn't fit Send()
is Album
and there is a reason for that. Albums were added not so long ago, so they are slightly quirky for backwards compatibilities sake. In fact, an Album
can be sent, but never received. Instead, Telegram returns a []Message
, one for each media object in the album:
p := &tb.Photo{File: tb.FromDisk("chicken.jpg")}
v := &tb.Video{File: tb.FromURL("http://video.mp4")}
msgs, err := b.SendAlbum(user, tb.Album{p, v})
Send options
Send options are objects and flags you can pass to Send()
, Edit()
and friends as optional arguments (following the recipient and the text/media). The most important one is called SendOptions
, it lets you control all the properties of the message supported by Telegram. The only drawback is that it's rather inconvenient to use at times, so Send()
supports multiple shorthands:
// regular send options
b.Send(user, "text", &tb.SendOptions{
// ...
})
// ReplyMarkup is a part of SendOptions,
// but often it's the only option you need
b.Send(user, "text", &tb.ReplyMarkup{
// ...
})
// flags: no notification && no web link preview
b.Send(user, "text", tb.Silent, tb.NoPreview)
Full list of supported option-flags you can find
here.
Editable
If you want to edit some existing message, you don't really need to store the original *Message
object. In fact, upon edit, Telegram only requires two IDs: ChatID and MessageID. And it doesn't really require the whole Message. Also you might want to store references to certain messages in the database, so for me it
made sense for any Go struct to be editable as Telegram message, to implement Editable
interface:
// Editable is an interface for all objects that
// provide "message signature", a pair of 32-bit
// message ID and 64-bit chat ID, both required
// for edit operations.
//
// Use case: DB model struct for messages to-be
// edited with, say two collums: msg_id,chat_id
// could easily implement MessageSig() making
// instances of stored messages editable.
type Editable interface {
// MessageSig is a "message signature".
//
// For inline messages, return chatID = 0.
MessageSig() (messageID int, chatID int64)
}
For example, Message
type is Editable. Here is an implementation of StoredMessage
type, provided by telebot:
// StoredMessage is an example struct suitable for being
// stored in the database as-is or being embedded into
// a larger struct, which is often the case (you might
// want to store some metadata alongside, or might not.)
type StoredMessage struct {
MessageID int `sql:"message_id" json:"message_id"`
ChatID int64 `sql:"chat_id" json:"chat_id"`
}
func (x StoredMessage) MessageSig() (int, int64) {
return x.MessageID, x.ChatID
}
Why bother at all? Well, it allows you to do things like this:
// just two integer columns in the database
var msgs []tb.StoredMessage
db.Find(&msgs) // gorm syntax
for _, msg := range msgs {
bot.Edit(&msg, "Updated text.")
// or
bot.Delete(&msg)
}
I find it incredibly neat. Worth noting, at this point of time there exists another method in the Edit family, EditCaption()
which is of pretty rare use, so I didn't bother including it to Edit()
, just like I did with SendAlbum()
as it would inevitably lead to unnecessary complications.
var m *Message
// change c...