telebot/README.md

490 lines
14 KiB
Markdown
Raw Normal View History

2015-06-25 19:43:19 +00:00
# Telebot
2017-11-30 09:01:52 +00:00
>"I never knew creating Telegram bots could be so _sexy_!"
2015-06-25 19:43:19 +00:00
2022-01-19 14:09:27 +00:00
[![GoDoc](https://godoc.org/gopkg.in/telebot.v3?status.svg)](https://godoc.org/gopkg.in/telebot.v3)
2022-02-06 12:40:19 +00:00
[![GitHub Actions](https://github.com/tucnak/telebot/actions/workflows/go.yml/badge.svg)](https://github.com/tucnak/telebot/actions)
2022-01-19 13:29:28 +00:00
[![codecov.io](https://codecov.io/gh/tucnak/telebot/coverage.svg?branch=v3)](https://codecov.io/gh/tucnak/telebot)
2021-01-23 13:30:44 +00:00
[![Discuss on Telegram](https://img.shields.io/badge/telegram-discuss-0088cc.svg)](https://t.me/go_telebot)
2017-11-17 06:41:48 +00:00
2017-11-21 02:49:37 +00:00
```bash
2022-01-19 13:29:28 +00:00
go get -u gopkg.in/telebot.v3
2017-11-21 02:49:37 +00:00
```
2017-11-28 22:27:23 +00:00
* [Overview](#overview)
* [Getting Started](#getting-started)
2022-01-19 13:29:28 +00:00
- [Context](#context)
- [Middleware](#middleware)
2017-11-28 22:27:23 +00:00
- [Poller](#poller)
- [Commands](#commands)
- [Files](#files)
- [Sendable](#sendable)
- [Editable](#editable)
- [Keyboards](#keyboards)
- [Inline mode](#inline-mode)
* [Contributing](#contributing)
* [Donate](#donate)
* [License](#license)
# Overview
2017-12-02 11:25:09 +00:00
Telebot is a bot framework for [Telegram Bot API](https://core.telegram.org/bots/api).
2017-11-24 13:26:11 +00:00
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
2022-01-19 13:29:28 +00:00
the beauty of API and performance. Some strong sides of Telebot are:
2017-11-29 20:33:50 +00:00
* Real concise API
2017-11-28 22:27:23 +00:00
* Command routing
* Middleware
* Transparent File API
* Effortless bot callbacks
2022-01-19 13:29:28 +00:00
All the methods of Telebot API are _extremely_ easy to memorize and get used to. Also, consider Telebot a
2017-11-28 22:27:23 +00:00
highload-ready solution. I'll test and benchmark the most popular actions and if necessary, optimize
against them without sacrificing API quality.
# Getting Started
2022-01-19 13:29:28 +00:00
Let's take a look at the minimal Telebot setup:
2015-06-27 11:30:35 +00:00
```go
2017-11-28 22:27:23 +00:00
package main
2015-06-27 11:30:35 +00:00
import (
2020-05-22 11:52:56 +00:00
"log"
2022-01-19 13:29:28 +00:00
"os"
2020-05-22 11:52:56 +00:00
"time"
2017-11-28 22:27:23 +00:00
2022-01-19 13:29:28 +00:00
tele "gopkg.in/telebot.v3"
2015-06-27 11:30:35 +00:00
)
func main() {
2022-01-19 13:29:28 +00:00
pref := tele.Settings{
Token: os.Getenv("TOKEN"),
2020-07-25 21:01:39 +00:00
Poller: &tele.LongPoller{Timeout: 10 * time.Second},
2022-01-19 13:29:28 +00:00
}
2017-11-21 02:49:37 +00:00
2022-01-19 13:29:28 +00:00
b, err := tele.NewBot(pref)
2017-11-28 22:27:23 +00:00
if err != nil {
log.Fatal(err)
return
}
2017-11-21 02:49:37 +00:00
2022-01-19 13:29:28 +00:00
b.Handle("/hello", func(c tele.Context) error {
return c.Send("Hello!")
2017-11-28 22:27:23 +00:00
})
2017-11-21 02:49:37 +00:00
2017-11-28 22:27:23 +00:00
b.Start()
2015-06-27 11:30:35 +00:00
}
2017-11-28 22:27:23 +00:00
2015-06-27 11:30:35 +00:00
```
Simple, innit? Telebot's routing system takes care of delivering updates
2017-11-24 13:26:11 +00:00
to their endpoints, so in order to get to handle any meaningful event,
2022-01-19 13:29:28 +00:00
all you got to do is just plug your function into one of the Telebot-provided
2017-11-28 22:27:23 +00:00
endpoints. You can find the full list
[here](https://godoc.org/gopkg.in/telebot.v3#pkg-constants).
2022-01-19 13:29:28 +00:00
There are dozens of supported endpoints (see package consts). Let me know
if you'd like to see some endpoint or endpoint ideas implemented. This system
is completely extensible, so I can introduce them without breaking
backwards compatibility.
## Context
Context is a special type that wraps a huge update structure and represents
the context of the current event. It provides several helpers, which allow
getting, for example, the chat that this update had been sent in, no matter
what kind of update this is.
```go
2022-01-19 13:29:28 +00:00
b.Handle(tele.OnText, func(c tele.Context) error {
// All the text messages that weren't
// captured by existing handlers.
var (
user = c.Sender()
text = c.Text()
)
2022-01-19 13:29:28 +00:00
// Use full-fledged bot's functions
// only if you need a result:
msg, err := b.Send(user, text)
if err != nil {
return err
}
// Instead, prefer a context short-hand:
return c.Send(text)
2018-01-08 16:41:00 +00:00
})
2022-01-19 13:29:28 +00:00
b.Handle(tele.OnChannelPost, func(c tele.Context) error {
// Channel posts only.
msg := c.Message()
2018-01-08 16:41:00 +00:00
})
2022-01-19 13:29:28 +00:00
b.Handle(tele.OnPhoto, func(c tele.Context) error {
// Photos only.
photo := c.Message().Photo
2017-11-21 12:55:46 +00:00
})
2022-01-19 13:29:28 +00:00
b.Handle(tele.OnQuery, func(c tele.Context) error {
// Incoming inline queries.
return c.Answer(...)
2017-11-21 12:55:46 +00:00
})
```
2022-01-19 13:29:28 +00:00
## Middleware
Telebot has a simple and recognizable way to set up middleware — chained functions with access to `Context`, called before the handler execution.
Import a `middleware` package to get some basic out-of-box middleware
implementations:
```go
import "gopkg.in/telebot.v3/middleware"
```
```go
// Global-scoped middleware:
b.Use(middleware.Logger())
b.Use(middleware.AutoRespond())
// Group-scoped middleware:
2022-02-08 10:58:03 +00:00
adminOnly := b.Group()
adminOnly.Use(middleware.Whitelist(adminIDs...))
2022-01-19 13:29:28 +00:00
adminOnly.Handle("/ban", onBan)
adminOnly.Handle("/kick", onKick)
// Handler-scoped middleware:
b.Handle(tele.OnText, onText, middleware.IgnoreVia())
```
Custom middleware example:
```go
// AutoResponder automatically responds to every callback update.
func AutoResponder(next tele.HandlerFunc) tele.HandlerFunc {
return func(c tele.Context) error {
if c.Callback() != nil {
defer c.Respond()
}
return next(c) // continue execution chain
}
}
```
2017-11-21 13:26:59 +00:00
2017-11-28 22:27:23 +00:00
## Poller
Telebot doesn't really care how you provide it with incoming updates, as long
2022-01-19 13:29:28 +00:00
as you set it up with a Poller, or call ProcessUpdate for each update:
2020-05-01 14:56:51 +00:00
2017-11-28 22:27:23 +00:00
```go
// 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{})
}
```
## 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](https://core.telegram.org/bots#privacy-mode) is off.
2022-01-19 13:29:28 +00:00
For simplified deep-linking, Telebot also extracts payload:
2017-11-28 22:27:23 +00:00
```go
// Command: /start <PAYLOAD>
2022-01-19 13:29:28 +00:00
b.Handle("/start", func(c tele.Context) error {
fmt.Println(c.Message().Payload) // <PAYLOAD>
})
```
2017-11-29 20:33:50 +00:00
2022-01-19 13:29:28 +00:00
For multiple arguments use:
```go
// Command: /tags <tag1> <tag2> <...>
b.Handle("/tags", func(c tele.Context) error {
tags := c.Args() // list of arguments splitted by a space
for _, tag := range tags {
// iterate through passed arguments
}
2017-11-28 22:27:23 +00:00
})
```
## Files
2022-01-19 13:29:28 +00:00
>Telegram allows files up to 50 MB in size.
2017-11-28 22:27:23 +00:00
2022-01-19 13:29:28 +00:00
Telebot allows to both upload (from disk or by URL) and download (from Telegram)
files in bot's scope. Also, sending any kind of media with a File created
2017-11-28 22:27:23 +00:00
from disk will upload the file to Telegram automatically:
```go
2020-07-25 21:01:39 +00:00
a := &tele.Audio{File: tele.FromDisk("file.ogg")}
2017-11-28 22:27:23 +00:00
fmt.Println(a.OnDisk()) // true
fmt.Println(a.InCloud()) // false
2022-01-19 13:29:28 +00:00
// Will upload the file from disk and send it to the recipient
b.Send(recipient, a)
2017-11-28 22:27:23 +00:00
// Next time you'll be sending this very *Audio, Telebot won't
// re-upload the same file but rather utilize its Telegram FileID
2022-01-19 13:29:28 +00:00
b.Send(otherRecipient, a)
2017-11-28 22:27:23 +00:00
fmt.Println(a.OnDisk()) // true
fmt.Println(a.InCloud()) // true
2022-01-19 13:29:28 +00:00
fmt.Println(a.FileID) // <Telegram file ID>
2017-11-28 22:27:23 +00:00
```
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 undoubtedly the most important method in Telebot. `Send()` accepts a
`Recipient` (could be user, group or a channel) and a `Sendable`. Other types other than
2022-01-19 13:29:28 +00:00
the Telebot-provided media types (`Photo`, `Audio`, `Video`, etc.) are `Sendable`.
If you create composite types of your own, and they satisfy the `Sendable` interface,
2017-11-28 22:27:23 +00:00
Telebot will be able to send them out.
2017-11-21 13:26:59 +00:00
```go
// Sendable is any object that can send itself.
//
// This is pretty cool, since it lets bots implement
// custom Sendables for complex kinds of media or
2017-11-21 13:26:59 +00:00
// chat objects spanning across multiple messages.
type Sendable interface {
2020-05-22 11:52:56 +00:00
Send(*Bot, Recipient, *SendOptions) (*Message, error)
2017-11-21 13:26:59 +00:00
}
```
2017-11-28 22:27:23 +00:00
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:
```go
2020-07-25 21:01:39 +00:00
p := &tele.Photo{File: tele.FromDisk("chicken.jpg")}
v := &tele.Video{File: tele.FromURL("http://video.mp4")}
2017-11-28 22:27:23 +00:00
2020-07-25 21:01:39 +00:00
msgs, err := b.SendAlbum(user, tele.Album{p, v})
2017-11-28 22:27:23 +00:00
```
### 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:
```go
// regular send options
2020-07-25 21:01:39 +00:00
b.Send(user, "text", &tele.SendOptions{
2017-11-28 22:27:23 +00:00
// ...
})
// ReplyMarkup is a part of SendOptions,
// but often it's the only option you need
2020-07-25 21:01:39 +00:00
b.Send(user, "text", &tele.ReplyMarkup{
2017-11-28 22:27:23 +00:00
// ...
})
// flags: no notification && no web link preview
2020-07-25 21:01:39 +00:00
b.Send(user, "text", tele.Silent, tele.NoPreview)
2017-11-28 22:27:23 +00:00
```
Full list of supported option-flags you can find
[here](https://pkg.go.dev/gopkg.in/telebot.v3#Option).
2017-11-28 22:27:23 +00:00
## Editable
2017-11-21 13:26:59 +00:00
If you want to edit some existing message, you don't really need to store the
2017-12-02 11:25:09 +00:00
original `*Message` object. In fact, upon edit, Telegram only requires `chat_id`
2022-01-19 13:29:28 +00:00
and `message_id`. So you don't really need the Message as a whole. Also, you
2017-12-02 11:25:09 +00:00
might want to store references to certain messages in the database, so I thought
it made sense for *any* Go struct to be editable as a Telegram message, to implement
`Editable`:
2017-11-21 13:26:59 +00:00
```go
// 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 columns: msg_id,chat_id
2017-11-21 13:26:59 +00:00
// 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)
}
```
2017-12-02 11:25:09 +00:00
For example, `Message` type is Editable. Here is the implementation of `StoredMessage`
2022-01-19 13:29:28 +00:00
type, provided by Telebot:
2017-11-21 13:26:59 +00:00
```go
// 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
}
```
2017-11-24 13:26:11 +00:00
Why bother at all? Well, it allows you to do things like this:
2017-11-21 13:26:59 +00:00
```go
// just two integer columns in the database
2020-07-25 21:01:39 +00:00
var msgs []tele.StoredMessage
2017-11-21 13:26:59 +00:00
db.Find(&msgs) // gorm syntax
for _, msg := range msgs {
2020-04-26 18:48:20 +00:00
bot.Edit(&msg, "Updated text")
2017-11-28 22:27:23 +00:00
// or
bot.Delete(&msg)
2017-11-21 13:26:59 +00:00
}
```
I find it incredibly neat. Worth noting, at this point of time there exists
another method in the Edit family, `EditCaption()` which is of a pretty
2017-11-28 22:27:23 +00:00
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.
2017-11-21 13:26:59 +00:00
```go
var m *Message
// change caption of a photo, audio, etc.
bot.EditCaption(m, "new caption")
```
2017-11-21 12:55:46 +00:00
2017-11-28 22:27:23 +00:00
## Keyboards
Telebot supports both kinds of keyboards Telegram provides: reply and inline
2022-01-19 13:29:28 +00:00
keyboards. Any button can also act as endpoints for `Handle()`.
2016-11-10 19:31:32 +00:00
2015-07-03 16:56:35 +00:00
```go
2022-01-19 13:29:28 +00:00
var (
// Universal markup builders.
menu = &tele.ReplyMarkup{ResizeKeyboard: true}
selector = &tele.ReplyMarkup{}
2017-11-28 22:27:23 +00:00
2022-01-19 13:29:28 +00:00
// Reply buttons.
btnHelp = menu.Text(" Help")
btnSettings = menu.Text("⚙ Settings")
2017-11-28 22:27:23 +00:00
2022-01-19 13:29:28 +00:00
// Inline buttons.
//
// Pressing it will cause the client to
// send the bot a callback.
//
// Make sure Unique stays unique as per button kind
// since it's required for callback routing to work.
//
btnPrev = selector.Data("⬅", "prev", ...)
btnNext = selector.Data("➡", "next", ...)
)
2017-11-28 22:27:23 +00:00
2022-01-19 13:29:28 +00:00
menu.Reply(
menu.Row(btnHelp),
menu.Row(btnSettings),
)
selector.Inline(
selector.Row(btnPrev, btnNext),
)
2020-05-22 11:52:56 +00:00
2022-01-19 13:29:28 +00:00
b.Handle("/start", func(c tele.Context) error {
return c.Send("Hello!", menu)
})
2020-05-22 11:52:56 +00:00
2022-01-19 13:29:28 +00:00
// On reply button pressed (message)
b.Handle(&btnHelp, func(c tele.Context) error {
return c.Edit("Here is some help: ...")
})
2017-11-28 22:27:23 +00:00
2022-01-19 13:29:28 +00:00
// On inline button pressed (callback)
b.Handle(&btnPrev, func(c tele.Context) error {
return c.Respond()
})
```
2022-01-19 13:29:28 +00:00
You can use markup constructor for every type of possible button:
2020-05-22 11:52:56 +00:00
```go
2022-01-19 13:29:28 +00:00
r := b.NewMarkup()
2020-05-22 11:52:56 +00:00
// Reply buttons:
r.Text("Hello!")
r.Contact("Send phone number")
r.Location("Send location")
2020-07-25 21:01:39 +00:00
r.Poll(tele.PollQuiz)
2020-05-22 11:52:56 +00:00
// Inline buttons:
r.Data("Show help", "help") // data is optional
r.Data("Delete item", "delete", item.ID)
r.URL("Visit", "https://google.com")
r.Query("Search", query)
r.QueryChat("Share", query)
2020-07-25 21:01:39 +00:00
r.Login("Login", &tele.Login{...})
2020-05-22 11:52:56 +00:00
```
2017-11-28 22:27:23 +00:00
## Inline mode
2020-07-25 21:01:39 +00:00
So if you want to handle incoming inline queries you better plug the `tele.OnQuery`
2017-11-29 20:33:50 +00:00
endpoint and then use the `Answer()` method to send a list of inline queries
2022-01-19 13:29:28 +00:00
back. I think at the time of writing, Telebot supports all of the provided result
types (but not the cached ones). This is what it looks like:
2017-11-29 20:33:50 +00:00
```go
2022-01-19 13:29:28 +00:00
b.Handle(tele.OnQuery, func(c tele.Context) error {
2017-11-29 20:33:50 +00:00
urls := []string{
"http://photo.jpg",
"http://photo2.jpg",
}
2020-07-25 21:01:39 +00:00
results := make(tele.Results, len(urls)) // []tele.Result
2017-11-29 20:33:50 +00:00
for i, url := range urls {
2020-07-25 21:01:39 +00:00
result := &tele.PhotoResult{
2022-01-19 13:29:28 +00:00
URL: url,
ThumbURL: url, // required for photos
2017-11-29 20:33:50 +00:00
}
results[i] = result
2020-04-26 18:48:20 +00:00
// needed to set a unique string ID for each result
results[i].SetResultID(strconv.Itoa(i))
2017-11-29 20:33:50 +00:00
}
2022-01-19 13:29:28 +00:00
return c.Answer(&tele.QueryResponse{
2020-04-26 18:48:20 +00:00
Results: results,
2017-11-29 20:33:50 +00:00
CacheTime: 60, // a minute
})
})
```
There's not much to talk about really. It also supports some form of authentication
2017-11-29 20:33:50 +00:00
through deep-linking. For that, use fields `SwitchPMText` and `SwitchPMParameter`
of `QueryResponse`.
# Contributing
1. Fork it
2022-01-19 13:29:28 +00:00
2. Clone v3: `git clone -b v3 https://github.com/tucnak/telebot`
3. Create your feature branch: `git checkout -b v3-feature`
2017-11-29 20:33:50 +00:00
4. Make changes and add them: `git add .`
2022-01-19 13:29:28 +00:00
5. Commit: `git commit -m "add some feature"`
6. Push: `git push origin v3-feature`
2017-11-29 20:33:50 +00:00
7. Pull request
# Donate
2022-01-19 13:29:28 +00:00
I do coding for fun, but I also try to search for interesting solutions and
2017-11-29 20:33:50 +00:00
optimize them as much as possible.
If you feel like it's a good piece of software, I wouldn't mind a tip!
2021-05-25 12:03:07 +00:00
Litecoin: `ltc1qskt5ltrtyg7esfjm0ftx6jnacwffhpzpqmerus`
Ethereum: `0xB78A2Ac1D83a0aD0b993046F9fDEfC5e619efCAB`
2017-11-29 20:33:50 +00:00
# License
2017-11-21 02:49:37 +00:00
2017-11-29 20:33:50 +00:00
Telebot is distributed under MIT.