Skip to content

Releases: tucnak/telebot

Telebot V3.2

20 Nov 13:53
Compare
Choose a tag to compare

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 the ChatMember (#615)
  • Added missed custom_emoji_id field to the Sticker (#579)
  • Fixed Results.MarshalJSON addressing inside for-loop (#589)

Discussion

Discuss on Telegram

Telebot V3.1

06 Oct 20:54
Compare
Choose a tag to compare

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 of map[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 using viper 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, use Settings.OnError instead once at initialization
  • Aforementioned panic-recovering behavior is optional
  • File.FileSize changed the int type to the int64
  • voice_chat_* fields renamed to video_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 missed Sticker 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

Discuss on Telegram

Telebot V3.0.1

01 Oct 23:22
Compare
Choose a tag to compare

Changelog

  • Sticker is treated as a Media now
  • DeleteCommands is working properly now
  • is_premium field is added to the Chat
  • Stop() behavior improvements (#532)
  • New tele.M alias type for the map[string]any to be used with layout package
  • middleware.Recover() is added instead of the default panic-recovering behavior
  • Bot.OnError is read-only now, instead pass the error handler once through tele.Settings

Telebot V3

08 Feb 00:08
Compare
Choose a tag to compare

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

Telebot V2.5

08 Dec 18:17
Compare
Choose a tag to compare

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

Discuss on Telegram

Telebot V2.4

13 Aug 13:08
Compare
Choose a tag to compare

Changelog

  • Bot API 5.0, 5.1, 5.2
  • SendAlbum improvements (#398)

Telebot V2.3

13 Jun 11:05
Compare
Choose a tag to compare

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

  • Updating methods unmarshalling fix (#298)
  • SuccessfulPayment currency type fix (#299)
  • Stickers API minor fixes

Telebot V2.2

13 Jun 11:05
Compare
Choose a tag to compare

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 Buttons
  • b.Forward() changed to take Editable instead of *Message

Telebot V2.1

13 Jun 11:04
59629fe
Compare
Choose a tag to compare

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

  • Use b.Edit() to invoke editMessageLiveLocation method (#239)
  • OnPinned also fires on channel posts (#244)
  • Exposed Webhook API (#214, #265)
  • LongPoller is now stoppable (#266)
  • Fixed a typo in DeleteGroupPhoto method
  • A little more nil checks to prevent unexpected panics

Telebot V2

29 Nov 20:41
Compare
Choose a tag to compare

"I never knew creating Telegram bots could be so sexy!"

GoDoc

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 Files 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...
Read more